config.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """
  2. Parse genome from config file.
  3. Only int and float genes are supported currently.
  4. Example config file:
  5. [x1]
  6. type = float
  7. randMin = -100.0
  8. randMax = 100.0
  9. mutProb = 0.1
  10. mutAmt = 0.1
  11. [x2]
  12. type = int
  13. randMin = -50
  14. randMax = 50
  15. mutProb = 0.2
  16. mutAmt = 1
  17. One section per gene.
  18. 'type' is necessary - other fields depends on the selected type
  19. """
  20. import ConfigParser
  21. from ConfigParser import NoOptionError
  22. from gene import ComplexGeneFactory
  23. from gene import IntGeneFactory, IntGeneExchangeFactory
  24. from gene import FloatGeneFactory, FloatGeneRandomFactory, FloatGeneMaxFactory
  25. from gene import FloatGeneExchangeFactory
  26. class LoaderError(Exception):
  27. pass
  28. # Casts to int and float (TODO: Add other)
  29. def _intcast(section, key, value):
  30. "Parse string into int or None with correct exceptions"
  31. if not value: # '' and None are coerced to None
  32. return None
  33. try:
  34. return int(value)
  35. except ValueError:
  36. raise LoaderError("Invalid integer value '%s' in position %s / %s." % (
  37. value, section, key))
  38. def _floatcast(section, key, value):
  39. "Parse string into float or None with correct exceptions"
  40. if not value:
  41. return None
  42. try:
  43. return float(value)
  44. except ValueError:
  45. raise LoaderError("Invalid floating-point value '%s' in position %s / %s." % (
  46. value, section, key))
  47. class ConfigLoader(object):
  48. def __init__(self, filename, require_genes=[]):
  49. """
  50. Genome loader.
  51. Filename - path to configuration
  52. If require_genes are passed after the loading we ensure that
  53. they exist.
  54. """
  55. # Dictionary of supported types into casts and factories
  56. self.types = {
  57. 'int': (_intcast, IntGeneFactory),
  58. 'int_exchange': (_intcast, IntGeneExchangeFactory),
  59. 'float': (_floatcast, FloatGeneFactory),
  60. 'float_exchange': (_floatcast, FloatGeneExchangeFactory),
  61. 'float_random': (_floatcast, FloatGeneRandomFactory),
  62. 'float_max': (_floatcast, FloatGeneMaxFactory),
  63. 'complex': (_floatcast, ComplexGeneFactory),
  64. # 'char': (str, CharGeneFactory),
  65. # ... TODO: add other
  66. }
  67. self.genome = {}
  68. self.require_genes = require_genes
  69. self.config = ConfigParser.RawConfigParser()
  70. self.config.optionxform = str # Don't lower() names
  71. self.config.read(filename)
  72. # Do we have a population definition also?
  73. self._pre_parse_population()
  74. def register_type(self, typename, cast, factory):
  75. """
  76. User can register his types using this method
  77. """
  78. self.types[typename] = (cast, factory)
  79. def _pre_parse_population(self):
  80. self.has_population = self.config.has_section('population')
  81. try:
  82. genes = self.config.get('population', 'genes')
  83. genes = [gene.strip() for gene in genes.split(" ")]
  84. self.genes = genes if genes else None
  85. except NoOptionError:
  86. self.genes = None
  87. def load_population(self, name, species):
  88. """
  89. Parse population options and return a population
  90. """
  91. import new
  92. from population import Population
  93. if not self.has_population:
  94. raise LoaderError("No population is defined in the config file")
  95. args = {
  96. 'species': species
  97. }
  98. def parse(fun, name):
  99. if self.config.has_option('population', name):
  100. try:
  101. val = fun('population', name)
  102. args[name] = val
  103. except ValueError:
  104. raise LoaderError("Invalid value for population option " + name)
  105. parse(self.config.getint, 'initPopulation')
  106. parse(self.config.getint, 'childCull')
  107. parse(self.config.getint, 'childCount')
  108. parse(self.config.getint, 'incest')
  109. parse(self.config.getint, 'numNewOrganisms')
  110. parse(self.config.getboolean, 'mutateAfterMating')
  111. parse(self.config.getfloat, 'mutants')
  112. return new.classobj(name, (Population,), args)
  113. def _parse_gene(self, section):
  114. """
  115. parse_gene is called by parse_genome to parse
  116. a single gene from a config.
  117. config - ConfigParser instance
  118. section - gene name / config section name
  119. """
  120. # This check won't work because of configparser:
  121. if section in self.genome:
  122. raise LoaderError("Gene %s was already defined" % section_)
  123. try:
  124. typename = self.config.get(section, 'type')
  125. except NoOptionError:
  126. raise LoaderError(("Required field 'type' was not "
  127. "found in section/gene '%s'") % section)
  128. try:
  129. cast, factory = self.types[typename]
  130. except KeyError:
  131. raise LoaderError("Unhandled type in config file: " + typename)
  132. args = {}
  133. for key, value in self.config.items(section):
  134. if key == "type":
  135. continue
  136. if key == "mutProb": # Always float
  137. converted = _floatcast(section, key, value)
  138. else:
  139. converted = cast(section, key, value)
  140. args[key] = converted
  141. gene = factory(typename + "_" + section, **args)
  142. return gene
  143. def load_genome(self):
  144. """
  145. Load genome from config file
  146. """
  147. sections = self.genes if self.genes else self.config.sections()
  148. for section in sections:
  149. if section.lower() == 'population':
  150. continue
  151. elif self.config.has_option(section, 'alias'):
  152. alias = self.config.get(section, 'alias')
  153. if alias not in self.genome:
  154. raise LoaderError(("Gene %s is an alias for non-existing gene %s. "
  155. "Order matters!") % (section, alias))
  156. genome[section] = self.genome[alias]
  157. continue
  158. else:
  159. gene = self._parse_gene(section)
  160. self.genome[section] = gene
  161. for gene in self.require_genes:
  162. if gene not in self.genome:
  163. raise LoaderError("Required gene '%s' was not found in the config" % gene)
  164. #for gene in self.genome.itervalues():
  165. # print gene.__dict__
  166. return self.genome