lib/tools/interactive_key.rb
# Contains methods used to build an interactive key
class Tools::InteractiveKey
##### FILTER PARAMETERS #####
# @!observation_matrix_id
# @return [String]
# Required attribude to build the key
attr_accessor :observation_matrix_id
# @!project_id
# @return [String]
# Required attribute to build the key
attr_accessor :project_id
# @!language_id
# @return [String or null]
# Optional attribute to display the descriptors and character_states in a particular language (when translations are available)
attr_accessor :language_id
# @!keyword_ids
# @return [String or null]
# Optional attribute to provide a list of tagIDs to limit the set of descriptors to those taged: "keyword_ids=1|5|15"
attr_accessor :keyword_ids
# @!row_filter
# @return [String or null]
# Optional attribute to provide a list of rowIDs to limit the set "row_filter=1|5|10"
attr_accessor :row_filter
# @!otu_filter
# @return [String or null]
# Optional attribute to provide a list of otuIDs to limit the set "otu_filter=1|5|10"
attr_accessor :otu_filter
# @!sorting
# @return [String or null]
# Optional attribute to sort the list of descriptors. Options: 'ordered', 'weighted', 'optimized'. Optimized is a default if nothing is provided
attr_accessor :sorting
# @!eliminate_unknown
# @return [Boolean or null]
# Optional attribute to eliminate taxa with no scores on a used descriptor: 'false' - default or 'true'
# If true, the rows without scores will be eliminated
attr_accessor :eliminate_unknown
# @!error_tolerance
# @return [Integer or null]
# Optional attribute. Number of allowed errors during identification
attr_accessor :error_tolerance
# @!identified_to_rank
# @return [String or null]
# Optional attribute to limit identification to OTU or a particular nomenclatural rank. Valid values are 'otu', 'species', 'genus', etc.
attr_accessor :identified_to_rank
# @!selected_descriptors
# @return [String or null]
# Optional attribute: descriptors and states selected during identification "123:1|3||125:3|5||135:2||140:3-5"
# Each used descriptor is separated by '||'.
# States or values are separated from descriptors with ':'.
# Multiple selected character_states for one descriptor are separated by '|'.
# Sample states can use numerical ranges
attr_accessor :selected_descriptors
##### RETURNED DATA ######
# @!observation_matrix
# @return [Object]
# Returns observation_matrix as an object
attr_accessor :observation_matrix
# @!observation_matrix_citation
# @return [Object]
# Returns observation_matrix_citation as an object
attr_accessor :observation_matrix_citation
# @!descriptor_available_languages
# @return [Array of Objects or null]
# Returns the list of available Languages used as translations for descriptors and character_states (in translations are available)
attr_accessor :descriptor_available_languages
# @!descriptors
# @return [null]
# Temporary attribute. Used for validation.
attr_accessor :descriptors
# @!descriptor_available_keywords
# @return [Array of Objects or null]
# Returns the list of all Tags used with the descriptors. Descriptors could be filtered by tag_id
attr_accessor :descriptor_available_keywords
# @!language_to_use
# @return [Object or null]
# Returns Language as an object if the language_id was provided (used to display descriptors in a particular language)
attr_accessor :language_to_use
# @!descriptors_with_filter
# @return [null]
# Temporary attribute. Used for validation. List of descriptors reduced by keyword_ids
attr_accessor :descriptors_with_filter
# @!rows_with_filter
# @return [null]
# Temporary attribute. Used for validation. list of rows to be included into the matrix
attr_accessor :rows_with_filter
# @!row_id_filter_array
# @return [array]
# Array of row_ids in the @row_filter
attr_accessor :row_id_filter_array
# @!otu_id_filter_array
# @return [array]
# Array of otu_ids in the @otu_filter
attr_accessor :otu_id_filter_array
# @!list_of_descriptors
# @return [Array]
# Return the list of descriptors and their states. Translated (if needed) and Sorted
# Each descriptor has an attribute :status, which could be 'used', 'useful', 'useless' for further identification
attr_accessor :list_of_descriptors
# @!remaining
# @return [Array]
# Returns the list of objects not eliminated by previously used descriptors.
# The list may include collection_objects OR otus OR valid taxon_names
attr_accessor :remaining
# @!eliminated
# @return [Array]
# Returns the list of objects eliminated by previously used descriptors.
# The list may include collection_objects OR otus OR valid taxon_names
attr_accessor :eliminated
# @!selected_descriptors_hash
# @return [Hash]
# selected_descriptors String is converted into Hash
attr_accessor :selected_descriptors_hash
# @!row_hash
# @return [null]
# Temporary hash of rows; used for calculation of remaining and eliminated rows
attr_accessor :row_hash
# @!descriptors_hash
# @return [null]
#temporary hash of descriptors; used for calculation of useful and not useful descriptors and their states
attr_accessor :descriptors_hash
def initialize(
observation_matrix_id: nil,
project_id: nil,
language_id: nil,
keyword_ids: nil,
row_filter: nil,
otu_filter: nil,
sorting: 'weighted',
error_tolerance: 0,
identified_to_rank: nil,
eliminate_unknown: nil,
selected_descriptors: nil)
# raise if observation_matrix_id.blank? || project_id.blank?
@observation_matrix_id = observation_matrix_id
@project_id = project_id
@observation_matrix = ObservationMatrix.where(project_id: project_id).find(observation_matrix_id)
@observation_matrix_citation = @observation_matrix.source
@descriptor_available_languages = descriptor_available_languages
@language_id = language_id
@language_to_use = language_to_use
@keyword_ids = keyword_ids
@descriptor_available_keywords = descriptor_available_keywords
@descriptors_with_filter = descriptors_with_keywords
@row_filter = row_filter
@otu_filter = otu_filter
@row_id_filter_array = row_filter_array
@otu_id_filter_array = otu_filter_array
@rows_with_filter = get_rows_with_filter
@sorting = sorting
@error_tolerance = error_tolerance.to_i
@eliminate_unknown = eliminate_unknown == 'true' ? true : false
@identified_to_rank = identified_to_rank
@selected_descriptors = selected_descriptors
@selected_descriptors_hash = selected_descriptors_hash_initiate
@row_hash = row_hash_initiate
@descriptors_hash = descriptors_hash_initiate
###main_logic
@remaining = remaining_taxa
@eliminated = eliminated_taxa
@list_of_descriptors = useful_descriptors
### delete temporary data
@row_hash = nil #
@descriptors_hash = nil
@rows_with_filter = nil
@descriptors_with_filter = nil
end
def descriptors
if sorting == 'weighted'
observation_matrix.descriptors.not_weight_zero.order('descriptors.weight DESC, descriptors.position')
else
observation_matrix.descriptors.not_weight_zero.order(:position)
end
end
def descriptor_available_languages
descriptor_ids = descriptors.pluck(:id)
languages = Language.joins(:alternate_value_translations)
.where(alternate_values: {alternate_value_object_type: 'Descriptor', type: 'AlternateValue::Translation'})
.where('alternate_values.alternate_value_object_id IN (?)', descriptor_ids ).order('languages.english_name').distinct.to_a
unless languages.empty?
languages = Language.where(english_name: 'English').to_a + languages
end
languages
end
def language_to_use
return nil if language_id.blank?
l = Language.where(id: language_id).first
return nil if l.nil? || !descriptor_available_languages.to_a.include?(l)
l
end
def descriptor_available_keywords
descriptor_ids = descriptors.pluck(:id)
tags = Keyword.joins(:tags)
.where(tags: {tag_object_type: 'Descriptor'})
.where('tags.tag_object_id IN (?)', descriptor_ids ).order('name').distinct.to_a
end
def descriptors_with_keywords
if keyword_ids
descriptors.joins(:tags).where('tags.keyword_id IN (?)', keyword_ids.to_s.split('|').map(&:to_i) )
else
descriptors
end
end
def row_filter_array
row_filter.blank? ? nil : row_filter.to_s.split('|').map(&:to_i)
end
def otu_filter_array
otu_filter.blank? ? nil : otu_filter.to_s.split('|').map(&:to_i)
end
def get_rows_with_filter
observation_matrix.observation_matrix_rows.order(:position)
end
## row_hash: {otu_collection_object: {:object, ### (collection_object or OTU)
## :object_at_rank, ### (converted to OTU or TN)
## :row_id,
## :otu_id,
## :errors, ### (calculated number of errors)
## :status }} ### ('remaining', 'eliminated')
def row_hash_initiate
h = {}
rows_with_filter.each do |r|
otu_collection_object = r.observation_object_type + r.observation_object_id.to_s # r.otu_id.to_s + '|' + r.collection_object_id.to_s
h[otu_collection_object] = {}
h[otu_collection_object][:object] = r
if identified_to_rank == 'otu'
h[otu_collection_object][:object_at_rank] = r.current_otu || r
elsif identified_to_rank
h[otu_collection_object][:object_at_rank] = r&.current_taxon_name&.ancestor_at_rank(identified_to_rank, inlude_self = true) || r
else
h[otu_collection_object][:object_at_rank] = r
end
h[otu_collection_object][:otu_id] = r.observation_object_type == 'Otu' ? r.observation_object_id : r.current_otu.id
h[otu_collection_object][:errors] = 0
h[otu_collection_object][:error_descriptors] = []
h[otu_collection_object][:status] = 'remaining' ### if number of errors > @error_tolerance, replaced to 'eliminated'
end
h
end
## descriptors_hash: {descriptor.id: {:descriptor, ### (descriptor)
## :observations, ### (array of observations for )
## :state_ids, ### {hash of state_ids used in the particular matrix}
## }}
def descriptors_hash_initiate
h = {}
descriptors_with_keywords.each do |d|
h[d.id] = {}
h[d.id][:descriptor] = d
h[d.id][:weight_index] = 0
h[d.id][:state_ids] = {}
h[d.id][:count] = 0
h[d.id][:min] = 999999 if d.type == 'Descriptor::Continuous' || d.type == 'Descriptor::Sample' # min value used as continuous or sample
h[d.id][:max] = -999999 if d.type == 'Descriptor::Continuous' || d.type == 'Descriptor::Sample' # max value used as continuous or sample
if d.type == 'Descriptor::PresenceAbsence'
h[d.id][:state_ids]['true'] = {}
h[d.id][:state_ids]['true'][:rows] = {}
h[d.id][:state_ids]['true'][:status] = 'useless'
h[d.id][:state_ids]['false'] = {}
h[d.id][:state_ids]['false'][:rows] = {}
h[d.id][:state_ids]['true'][:status] = 'useless'
end
h[d.id][:observations] = {} # all observation for a particular
h[d.id][:observation_hash] = {} ### state_ids, true/false for a particular descriptor/otu_id/catalog_id combination (for PresenceAbsence or Qualitative or Continuous)
h[d.id][:status] = 'useless' ### 'used', 'useful', 'useless'
h[d.id][:status] = 'used' if selected_descriptors_hash[d.id]
end
t = ['Observation::Continuous', 'Observation::PresenceAbsence', 'Observation::Qualitative', 'Observation::Sample']
observation_matrix.observations.where('"observations"."type" IN (?)', t).each do |o|
if h[o.descriptor_id]
otu_collection_object = o.observation_object_type + o.observation_object_id.to_s # otu_id.to_s + '|' + o.collection_object_id.to_s
h[o.descriptor_id][:observations][otu_collection_object] = [] if h[o.descriptor_id][:observations][otu_collection_object].nil? #??????
h[o.descriptor_id][:observations][otu_collection_object] += [o] #??????
h[o.descriptor_id][:observation_hash][otu_collection_object] = [] if h[o.descriptor_id][:observation_hash][otu_collection_object].nil?
h[o.descriptor_id][:observation_hash][otu_collection_object] += [o.character_state_id.to_s] if o.character_state_id
h[o.descriptor_id][:observation_hash][otu_collection_object] += ["%g" % o.continuous_value] if o.continuous_value
h[o.descriptor_id][:observation_hash][otu_collection_object] += [o.presence.to_s] unless o.presence.nil?
end
end
h
end
# returns {123: ['1', '3'], 125: ['3', '5'], 135: ['2'], 136: ['true'], 140: ['5-10']}
# "123:1|3||125:3|5||135:2"
def selected_descriptors_hash_initiate
h = {}
return h if selected_descriptors.blank?
a = selected_descriptors.include?('||') ? selected_descriptors.to_s.split('||') : [selected_descriptors]
a.each do |i|
d = i.split(':')
h[d[0].to_i] = d[1].to_s.include?('|') ? d[1].split('|') : [d[1]]
end
h
end
# @error_tolerance - integer
# @eliminate_unknown 'true' or 'false'
# @descriptors_hash
def remaining_taxa
h = {}
language = language_id.blank? ? nil : language_id.to_i
row_hash.each do |r_key, r_value|
selected_descriptors_hash.each do |d_key, d_value|
otu_collection_object = r_value[:object].observation_object_type + r_value[:object].observation_object_id.to_s # otu_id.to_s + '|' + r_value[:object].collection_object_id.to_s
next if descriptors_hash[d_key].blank?
d_name = descriptors_hash[d_key][:descriptor].target_name(:key, language) + ': '
if eliminate_unknown && descriptors_hash[d_key][:observation_hash][otu_collection_object].nil?
r_value[:errors] += 1
r_value[:error_descriptors] += [d_name + 'unknown']
elsif descriptors_hash[d_key][:observation_hash][otu_collection_object].nil?
#character not scored but no error
else
case descriptors_hash[d_key][:descriptor].type
when 'Descriptor::Continuous'
if (descriptors_hash[d_key][:observation_hash][otu_collection_object] & d_value).empty?
r_value[:errors] += 1
str = d_name + descriptors_hash[d_key][:observations][otu_collection_object].collect{|o| "%g" % o.continuous_value}.join(' OR ')
r_value[:error_descriptors] += [str]
end
when 'Descriptor::PresenceAbsence'
if (descriptors_hash[d_key][:observation_hash][otu_collection_object] & d_value).empty?
r_value[:errors] += 1
str = d_name + descriptors_hash[d_key][:observations][otu_collection_object].collect{|o| o.presence}.join(' OR ')
r_value[:error_descriptors] += [str]
end
when 'Descriptor::Qualitative'
if (descriptors_hash[d_key][:observation_hash][otu_collection_object] & d_value).empty?
r_value[:errors] += 1
str = d_name + descriptors_hash[d_key][:observations][otu_collection_object].collect{|o| o.character_state.target_name(:key, language)}.join(' OR ')
r_value[:error_descriptors] += [str]
end
when 'Descriptor::Sample'
p = false
a = d_value.first.split('-')
d_min = a[0].to_f
d_max = a[1].nil? ? d_min : a[1].to_f
descriptors_hash[d_key][:observations][otu_collection_object].each do |o|
s_min = o.sample_min.to_f
s_max = o.sample_max.nil? ? s_min : o.sample_max.to_f
p = true if (d_min >= s_min && d_min <= s_max) || (d_max >= s_min && d_max <= s_max) || (d_min <= s_min && d_max >= s_max)
end
if p == false
r_value[:errors] += 1
str = d_name + descriptors_hash[d_key][:observations][otu_collection_object].collect{|o| o.sample_min.to_s + '–' + o.sample_max.to_s}.join(' OR ')
r_value[:error_descriptors] += [str]
end
end
end
end
obj = r_value[:object_at_rank].class.to_s + '|' + r_value[:object_at_rank].id.to_s
if (row_id_filter_array && !row_id_filter_array.include?(r_value[:object].id)) ||
(otu_id_filter_array && !otu_id_filter_array.include?(r_value[:otu_id]))
r_value[:status] = 'eliminated'
r_value[:errors] = 'F'
r_value[:error_descriptors] = ['Filtered out']
end
if r_value[:errors] == 'F' || r_value[:errors] > error_tolerance
r_value[:status] = 'eliminated'
elsif h[obj].nil?
h[obj] =
{object: r_value[:object_at_rank],
row_id: r_value[:object].id,
errors: r_value[:errors],
error_descriptors: r_value[:error_descriptors]
}
end
end
return h.values
end
def eliminated_taxa
h = {}
row_hash.each do |r_key, r_value|
obj = r_value[:object_at_rank].class.to_s + '|' + r_value[:object_at_rank].id.to_s
if r_value[:status] == 'eliminated' && !remaining.include?(r_value[:object_at_rank].class.to_s + '|' + r_value[:object_at_rank].id.to_s)
h[obj] =
{object: r_value[:object_at_rank],
row_id: r_value[:object].id,
errors: r_value[:errors],
error_descriptors: r_value[:error_descriptors]
} if h[obj].nil?
end
end
return h.values
end
def useful_descriptors
list_of_remaining_taxa = {}
language = language_id.blank? ? nil : language_id.to_i
row_hash.each do |r_key, r_value|
if r_value[:status] != 'eliminated'
list_of_remaining_taxa[r_value[:object_at_rank] ] = true
end
end
number_of_taxa = list_of_remaining_taxa.count.to_i
array_of_descriptors = []
descriptors_hash.each do |d_key, d_value|
taxa_with_unknown_character_states = {}
list_of_remaining_taxa.each do |key, value|
taxa_with_unknown_character_states[key] = value
end
# taxa_with_unknown_character_states = list_of_remaining_taxa if @eliminate_unknown == false
d_value[:observations].each do |otu_key, otu_value|
otu_collection_object = otu_key
if true #@row_hash[otu_collection_object]
otu_value.each do |o|
if o.character_state_id
d_value[:state_ids][o.character_state_id.to_s] = {} if d_value[:state_ids][o.character_state_id.to_s].nil?
d_value[:state_ids][o.character_state_id.to_s][:rows] = {} if d_value[:state_ids][o.character_state_id.to_s][:rows].nil? ## rows which this state identifies
d_value[:state_ids][o.character_state_id.to_s][:rows][ @row_hash[otu_collection_object][:object_at_rank] ] = true if @row_hash[otu_collection_object][:status] != 'eliminated'
if selected_descriptors_hash[d_key] && selected_descriptors_hash[d_key].include?(o.character_state_id.to_s)
d_value[:state_ids][o.character_state_id.to_s][:status] = 'used' ## 'used', 'useful', 'useless'
else
d_value[:state_ids][o.character_state_id.to_s][:status] = 'useful' ## 'used', 'useful', 'useless'
end
end
unless o.presence.nil?
#d_value[:state_ids][o.presence.to_s] = {} if d_value[:state_ids][o.presence.to_s].nil?
#d_value[:state_ids][o.presence.to_s][:rows] = {} if d_value[:state_ids][o.presence.to_s][:rows].nil? ## rows which this state identifies
d_value[:state_ids][o.presence.to_s][:rows][ @row_hash[otu_collection_object][:object_at_rank] ] = true if @row_hash[otu_collection_object][:status] != 'eliminated'
if selected_descriptors_hash[d_key] && selected_descriptors_hash[d_key].include?(o.presence.to_s)
d_value[:state_ids][o.presence.to_s][:status] = 'used' ## 'used', 'useful', 'useless'
else
d_value[:state_ids][o.presence.to_s][:status] = 'useful' ## 'used', 'useful', 'useless'
end
end
unless o.continuous_value.nil?
d_value[:state_ids][o.id] = true
if @row_hash[otu_collection_object][:status] != 'eliminated'
d_value[:count] +=1
d_value[:min] = o.continuous_value if d_value[:min] > o.continuous_value
d_value[:max] = o.continuous_value if d_value[:max] < o.continuous_value
end
end
unless o.sample_min.nil?
d_value[:state_ids][o.id] = {o_min: o.sample_min.to_f, o_max: o.sample_max.to_f}
if @row_hash[otu_collection_object][:status] != 'eliminated'
d_value[:count] +=1
d_value[:min] = o.sample_min if d_value[:min] > o.sample_min
if o.sample_max
d_value[:max] = o.sample_max if d_value[:max] < o.sample_max
else
d_value[:max] = o.sample_min if d_value[:max] < o.sample_min
end
end
end
taxa_with_unknown_character_states[ @row_hash[otu_collection_object][:object_at_rank] ] = false if @eliminate_unknown == false
end
end
end
number_of_taxa_with_unknown_character_states = 0
number_of_taxa_with_unknown_character_states = taxa_with_unknown_character_states.select{|key, value| value == true}.count if @eliminate_unknown == false
descriptor = {}
descriptor[:id] = d_key
descriptor[:type] = d_value[:descriptor].type
descriptor[:name] = d_value[:descriptor].target_name(:key, language)
descriptor[:weight] = d_value[:descriptor].weight
descriptor[:position] = d_value[:descriptor].position
descriptor[:usefulness] = 0
descriptor[:status] = d_value[:status] == 'used' ? 'used' : 'useless'
descriptor[:description] = d_value[:descriptor].description
descriptor[:depiction_ids] = d_value[:descriptor].depictions.order(:position).pluck(:id)
s = 0
case d_value[:descriptor].type
when 'Descriptor::Qualitative'
number_of_states = d_value[:state_ids].count.to_i
descriptor[:states] = []
d_value[:state_ids].each do |s_key, s_value|
c = CharacterState.find(s_key.to_i)
state = {}
state[:id] = c.id
state[:name] = c.target_name(:key, language)
state[:position] = c.position
state[:label] = c.label
state[:number_of_objects] = s_value[:rows].count + number_of_taxa_with_unknown_character_states
state[:status] = s_value[:status] == 'used' ? 'used' : 'useful'
n = s_value[:rows].count
if descriptor[:status] == 'used'
#do nothing
elsif n == number_of_taxa || n == 0
s_value[:status] = 'useless'
state[:status] = 'useless'
else
d_value[:status] = 'useful'
descriptor[:status] = 'useful'
end
state[:depiction_ids] = c.depictions.order(:position).pluck(:id)
# weight = rem_taxa/number_of_states + squer (sum (rem_taxa/number_of_states - taxa_in_each_state)^2)
s += (number_of_taxa / number_of_states - s_value[:rows].count) ** 2
descriptor[:states] += [state]
end
descriptor[:usefulness] = number_of_taxa / number_of_states + Math.sqrt(s) if number_of_states > 0
descriptor[:states].sort_by!{|i| i[:position]}
when 'Descriptor::Continuous'
descriptor[:default_unit] = d_value[:descriptor].default_unit
descriptor[:min] = d_value[:min]
descriptor[:max] = d_value[:max]
number_of_measurements = d_value[:count]
s = (d_value[:min] - (d_value[:min] / 10)) / (d_value[:max] + 0.0000001 - d_value[:min])
descriptor[:usefulness] = number_of_taxa * s * (2 - (number_of_measurements / number_of_taxa)) if number_of_taxa > 0
if descriptor[:status] != 'used' && descriptor[:min] != descriptor[:max]
d_value[:status] = 'useful'
descriptor[:status] = 'useful'
end
when 'Descriptor::Sample'
descriptor[:default_unit] = d_value[:descriptor].default_unit
descriptor[:min] = d_value[:min]
descriptor[:max] = d_value[:max]
number_of_measurements = d_value[:count]
# i = max - min ; if 0 then (numMax - numMin / 10)
# sum of all i
# if numMax = numMin then numMax = numMax + 0.00001
# weight = rem_taxa * (sum of i / number of measuments for taxon / (numMax - numMin) ) * (2 - number of measuments for taxon / rem_taxa)
if d_value[:max] != d_value[:min] && number_of_measurements > 0
d_value[:state_ids].each do |s_key, s_value|
if s_value[:o_min] == s_value[:o_max] || s_value[:o_max].blank?
s += (s_value[:o_min] - (s_value[:o_min] / 10)) / number_of_measurements / (d_value[:max] + 0.0000001 - d_value[:min])
else
s += (s_value[:o_max] - s_value[:o_min]) / number_of_measurements / (d_value[:max] + 0.0000001 - d_value[:min])
end
if descriptor[:status] != 'used' && (s_value[:o_min] != d_value[:min] || (!s_value[:o_max].blank? && s_value[:o_max] != d_value[:max]))
d_value[:status] = 'useful'
descriptor[:status] = 'useful'
end
end
end
descriptor[:usefulness] = number_of_taxa * s * (2 - (number_of_measurements / number_of_taxa)) if number_of_taxa > 0
when 'Descriptor::PresenceAbsence'
number_of_states = 2
descriptor[:states] = []
d_value[:state_ids].each do |s_key, s_value|
state = {}
state[:name] = s_key
state[:number_of_objects] = s_value[:rows].count + number_of_taxa_with_unknown_character_states
state[:status] = s_value[:status] == 'used' ? 'used' : 'useful'
n = s_value[:rows].count
if descriptor[:status] == 'used'
#do nothing
elsif n == number_of_taxa || n == 0
s_value[:status] = 'useless'
state[:status] = 'useless'
else
d_value[:status] = 'useful'
descriptor[:status] = 'useful'
end
s += (number_of_taxa / number_of_states - s_value[:rows].count) ** 2
descriptor[:states] += [state]
end
descriptor[:usefulness] = number_of_taxa / number_of_states + Math.sqrt(s) if number_of_states > 0
descriptor[:states].sort_by!{|i| -i[:name]}
end
descriptor[:min] = nil if descriptor[:min] == 999999
descriptor[:max] = nil if descriptor[:max] == -999999
descriptor[:min] = "%g" % descriptor[:min] if descriptor[:min]
descriptor[:max] = "%g" % descriptor[:max] if descriptor[:max]
array_of_descriptors += [descriptor]
end
case sorting
when 'ordered'
array_of_descriptors.sort_by!{|i| i[:position]}
when 'weighted'
array_of_descriptors.sort_by!{|i| [-i[:weight].to_i, i[:usefulness]] }
when 'optimized'
array_of_descriptors.sort_by!{|i| i[:usefulness]}
end
end
end