- Timestamp:
- 04/17/23 00:31:40 (21 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
framspy/FramsticksLib.py
r1204 r1218 59 59 frams.Simulator.expdef = "standard-eval" # this expdef (or fully compatible) must be used by EVALUATION_SETTINGS_FILE 60 60 if sim_settings_files is not None: 61 self.EVALUATION_SETTINGS_FILE = sim_settings_files.split(";") # over write defaults61 self.EVALUATION_SETTINGS_FILE = sim_settings_files.split(";") # override defaults. str becomes list 62 62 print('Basic tests OK. Using settings:', self.EVALUATION_SETTINGS_FILE) 63 63 print() 64 if not isinstance(self.EVALUATION_SETTINGS_FILE, list): # ensure settings file(s) are provided as a list65 raise ValueError("Evaluation settings file(s) '%s' should be provided as a list" % self.EVALUATION_SETTINGS_FILE)66 64 67 65 for simfile in self.EVALUATION_SETTINGS_FILE: … … 112 110 if ec.error_count._value() > 0: 113 111 print(ec.messages) # if errors occurred, output all caught messages for debugging 114 raise RuntimeError("[ERROR] %d error(s) and %d warning(s) while evaluating %d genotype(s)" % (ec.error_count._value(), ec.warning_count._value() - ec.error_count._value(), len(genotype_list))) # make errors fatal; by default they stop the simulation anyway so let's not use potentially incorrect or partial results and fix the cause first.112 raise RuntimeError("[ERROR] %d error(s) and %d warning(s) while evaluating %d genotype(s)" % (ec.error_count._value(), ec.warning_count._value(), len(genotype_list))) # make errors fatal; by default they stop the simulation anyway so let's not use potentially incorrect or partial results and fix the cause first. 115 113 116 114 results = [] … … 198 196 199 197 198 def getRandomGenotype(self, initial_genotype: str, parts_min: int, parts_max: int, neurons_min: int, neurons_max: int, iter_max: int, return_even_if_failed: bool): 199 """ 200 Some algorithms require a "random solution". To this end, this method generates a random framstick genotype. 201 202 :param initial_genotype: if not a specific genotype (which could facilitate greater variability of returned genotypes), try `getSimplest(format)`. 203 :param iter_max: how many mutations can be used to generate a random genotype that fullfills target numbers of parts and neurons. 204 :param return_even_if_failed: if the target numbers of parts and neurons was not achieved, return the closest genotype that was found? Set it to False first to experimentally adjust `iter_max` so that in most calls this function returns a genotype with target numbers of parts and neurons, and then you can set this parameter to True if target numbers of parts and neurons are not absolutely required. 205 :returns: a valid genotype or None if failed and `return_even_if_failed` is False. 206 """ 207 208 209 def estimate_diff(g: str): 210 if not self.isValidCreature([g])[0]: 211 return None, None 212 m = frams.Model.newFromString(g) 213 numparts = m.numparts._value() 214 numneurons = m.numneurons._value() 215 diff_parts = abs(target_parts - numparts) 216 diff_neurons = abs(target_neurons - numneurons) 217 in_target_range = (parts_min <= numparts <= parts_max) and (neurons_min <= numneurons <= neurons_max) # less demanding than precisely reaching target_parts and target_neurons 218 return diff_parts + diff_neurons, in_target_range 219 220 221 # try to find a genotype that matches the number of parts and neurons randomly selected from the provided min..max range 222 # (even if we fail to match this precise target, the goal will be achieved if the found genotype manages to be within min..max ranges for parts and neurons) 223 target_parts = np.random.default_rng().integers(parts_min, parts_max + 1) 224 target_neurons = np.random.default_rng().integers(neurons_min, neurons_max + 1) 225 226 if not self.isValidCreature([initial_genotype])[0]: 227 raise ValueError("Initial genotype '%s' is invalid" % initial_genotype) 228 229 g = initial_genotype 230 for i in range(iter_max // 2): # a sequence of iter_max/2 undirected mutations starting from initial_genotype 231 g_new = self.mutate([g])[0] 232 if self.isValidCreature([g_new])[0]: # valid mutation 233 g = g_new 234 235 best_diff, best_in_target_range = estimate_diff(g) 236 for i in range(iter_max // 2): # a sequence of iter_max/2 mutations, only accepting those which approach target numbers of parts and neurons 237 g_new = self.mutate([g])[0] 238 diff, in_target_range = estimate_diff(g_new) 239 if diff is not None and diff <= best_diff: # valid mutation and better or as good as current 240 g = g_new 241 best_diff = diff 242 best_in_target_range = in_target_range 243 # print(diff, best_diff) # print progress approaching target numbers of parts and neurons 244 245 if best_in_target_range or return_even_if_failed: 246 return g # best found so far (closest to target numbers of parts and neurons) 247 return None 248 249 200 250 def isValid(self, genotype_list: List[str]) -> List[bool]: 201 251 """ … … 206 256 for g in genotype_list: 207 257 valid.append(frams.Geno.newFromString(g).is_valid._int() == 1) 258 if len(genotype_list) != len(valid): 259 raise RuntimeError("Tested %d genotypes, received %d validity values" % (len(genotype_list), len(valid))) 260 return valid 261 262 263 def isValidCreature(self, genotype_list: List[str]) -> List[bool]: 264 """ 265 :returns: validity of the genotype when revived. Apart from genetic validity, this includes detecting problems that may arise when building a Creature from Genotype, such as multiple muscles of the same type in the same location in body, e.g. 'X[@][@]'. 266 """ 267 268 # Genetic validity and simulator validity are two separate properties (in particular, genetic validity check is implemented by the author of a given genetic format and operators). 269 # Thus, the subset of genotypes valid genetically and valid in simulation may be overlapping. 270 # For example, 'X[]' or 'Xr' are considered invalid by the genetic checker, but the f1->f0 converter will ignore meaningless genes and produce a valid f0 genotype. 271 # On the other hand, 'X[@][@]' or 'X[|][|]' are valid genetically, but not possible to simulate. 272 # For simplicity of usage (so that one does not need to check both properties separately using both functions), let's make one validity a subset of the other. 273 # The genetic check in the first lines of the "for" loop makes this function at least as demanding as isValid(). 274 275 assert isinstance(genotype_list, list) # because in python, str has similar capabilities as list and here it would pretend to work too, so to avoid any ambiguity 276 277 pop = frams.Populations[0] # assuming rules from population #0 (self-colision settings are population-dependent and can influence creature build success/failure) 278 279 valid = [] 280 for g in genotype_list: 281 if frams.Geno.newFromString(g).is_valid._int() != 1: 282 valid.append(False) # invalid according to genetic check 283 else: 284 can_add = pop.canAdd(g, 1, 1) # First "1" means to treat warnings during build as build failures - this allows detecting problems when building Creature from Genotype. Second "1" means mute emitted errors, warnings, messages. Returns 1 (ok, could add) or 0 (there were some problems building Creature from Genotype) 285 valid.append(can_add._int() == 1) 286 208 287 if len(genotype_list) != len(valid): 209 288 raise RuntimeError("Tested %d genotypes, received %d validity values" % (len(genotype_list), len(valid))) … … 253 332 print('\tPerformance of Offspring:', framsLib.evaluate([offspring])) 254 333 print('\tValidity (genetic) of Parent1, Parent 2, and Offspring:', framsLib.isValid([parent1, parent2, offspring])) 334 print('\tValidity (simulation) of Parent1, Parent 2, and Offspring:', framsLib.isValidCreature([parent1, parent2, offspring])) 335 print('\tRandom genotype:', framsLib.getRandomGenotype(simplest, 2, 6, 2, 4, 100, True))
Note: See TracChangeset
for help on using the changeset viewer.