- Timestamp:
- 06/22/23 03:52:39 (18 months ago)
- Location:
- cpp/frams/genetics/f4
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
cpp/frams/genetics/f4/f4_conv.cpp
r1249 r1259 153 153 f4_Cell* f4_Model::getStick(f4_Cell *C) 154 154 { 155 if (C->type == CELL_STICK) return C;155 if (C->type == f4_Cell_type::CELL_STICK) return C; 156 156 if (NULL != C->dadlink) 157 157 return getStick(C->dadlink); 158 158 // we have no more dadlinks, find any stick 159 159 for (int i = 0; i < cells->cell_count; i++) 160 if (cells->C[i]->type == CELL_STICK)160 if (cells->C[i]->type == f4_Cell_type::CELL_STICK) 161 161 return cells->C[i]; 162 162 // none! … … 177 177 // make sure parent is a stick 178 178 if (C->dadlink != NULL) 179 if (C->dadlink->type != CELL_STICK)179 if (C->dadlink->type != f4_Cell_type::CELL_STICK) 180 180 { 181 181 C->dadlink = getStick(C->dadlink); … … 192 192 MultiRange range = C->genoRange; 193 193 194 if (C->type == CELL_STICK)194 if (C->type == f4_Cell_type::CELL_STICK) 195 195 { 196 196 int jj_p1_refno; // save for later … … 244 244 } 245 245 246 if (C->type == CELL_NEURON)246 if (C->type == f4_Cell_type::CELL_NEURON) 247 247 { 248 248 const char* nclass = C->neuclass->name.c_str(); … … 253 253 else 254 254 sprintf(tmpLine, "d=\"%s\"", nclass); 255 256 C->neuro_refno = addFromString(NeuronType, tmpLine, &range);257 if (C->neuro_refno < 0) return -22;258 this->checkpoint();259 255 } 260 256 else if (C->neuclass->getPreferredLocation() == 1) // attached to Part or have no required attachment - also part … … 264 260 265 261 sprintf(tmpLine, "p=%d,d=\"%s\"", partno, nclass); 266 267 C->neuro_refno = addFromString(NeuronType, tmpLine, &range);268 if (C->neuro_refno < 0) return -22;269 this->checkpoint();270 262 } 271 263 else // attached to Joint, assume there are only three possibilities of getPreferredLocation() … … 277 269 else if (strcmp(nclass, "|") == 0) 278 270 { 279 sprintf(tmpLine, "j=%d,d=\"|:p=%g,r=%g\"", jointno, C->P.muscle_power, C->dadlink->P.muscle_bend_range); //Macko 2023-05 change: we take muscle_bend_range from dadlink, not from C, because we also assign this neuron to C->dadlink->joint_refno. Without this, for example in /*4*/<<X><<<<X>N:|>X>X>X>X the muscle is attached to the junction with 3 sticks, but gets range=33% as in a four-stick junction. f1 correctly sets range=0.5 for the analogous phenotype: X(X[|],X(X,X,X))271 sprintf(tmpLine, "j=%d,d=\"|:p=%g,r=%g\"", jointno, C->P.muscle_power, C->dadlink->P.muscle_bend_range); //Macko 2023-05 change: we take muscle_bend_range from dadlink, not from C, because we also assign this neuron to C->dadlink->joint_refno. Without this, for example in /*4*/<<X><<<<X>N:|>X>X>X>X the muscle is attached to the junction with 3 sticks, but gets range=33% as in a four-stick junction. f1 correctly sets range=0.5 (see also conv_f1_f0_branch_muscle_range) for the analogous phenotype: X(X[|],X(X,X,X)) 280 272 } 281 273 else 282 274 sprintf(tmpLine, "j=%d,d=\"%s\"", jointno, nclass); 283 284 C->neuro_refno = addFromString(NeuronType, tmpLine, &range); 285 if (C->neuro_refno < 0) return -32; 286 this->checkpoint(); 287 } 275 } 276 277 C->neuro_refno = addFromString(NeuronType, tmpLine, &range); 278 if (C->neuro_refno < 0) return -22; 279 this->checkpoint(); 280 288 281 for (int j = 0; j < C->conns_count; j++) 289 282 { -
cpp/frams/genetics/f4/f4_general.cpp
r1249 r1259 37 37 { 38 38 nr = nnr; 39 type = CELL_UNDIFF;39 type = f4_Cell_type::CELL_UNDIFF; 40 40 dadlink = ndad; 41 41 org = NULL; … … 62 62 { 63 63 // make sure it is a stick (and not a stick f4_Cell!) 64 if (ndad->type == CELL_STICK)64 if (ndad->type == f4_Cell_type::CELL_STICK) 65 65 { 66 66 //firstend = ndad->lastend; … … 68 68 ndad->stickchildcount++; 69 69 } 70 if (ndad->type == CELL_NEURON)70 if (ndad->type == f4_Cell_type::CELL_NEURON) 71 71 { 72 72 inertia = ndad->inertia; … … 84 84 { 85 85 nr = nnr; 86 type = CELL_UNDIFF;86 type = f4_Cell_type::CELL_UNDIFF; 87 87 dadlink = ndad; 88 88 org = nO; … … 112 112 { 113 113 // make sure it is a stick (and not a stick f4_Cell!) 114 if (ndad->type == CELL_STICK)114 if (ndad->type == f4_Cell_type::CELL_STICK) 115 115 { 116 116 //firstend = ndad->lastend; … … 118 118 ndad->stickchildcount++; 119 119 } 120 if (ndad->type == CELL_NEURON)120 if (ndad->type == f4_Cell_type::CELL_NEURON) 121 121 { 122 122 inertia = ndad->inertia; … … 173 173 174 174 // error: sticks cannot divide 175 if (type == CELL_STICK)175 if (type == f4_Cell_type::CELL_STICK) 176 176 { 177 177 // cannot fix … … 181 181 182 182 // undiff divides 183 if (type == CELL_UNDIFF)183 if (type == f4_Cell_type::CELL_UNDIFF) 184 184 { 185 185 // commacount is set only when daughter turns into X … … 194 194 } 195 195 // a neuron divides: create a new, duplicate connections 196 if (type == CELL_NEURON)196 if (type == f4_Cell_type::CELL_NEURON) 197 197 { 198 198 // daughter cell … … 203 203 repeat.clear(); 204 204 // it is a neuron from start 205 tmp->type = CELL_NEURON;205 tmp->type = f4_Cell_type::CELL_NEURON; 206 206 // it has the same type as the parent neuron 207 207 tmp->neuclass = neuclass; … … 257 257 { 258 258 // error: still undiff 259 if (type == CELL_UNDIFF)259 if (type == f4_Cell_type::CELL_UNDIFF) 260 260 { 261 261 // fix it: insert an 'X' … … 301 301 { 302 302 // error: if neuron 303 if (type == CELL_NEURON)303 if (type == f4_Cell_type::CELL_NEURON) 304 304 { 305 305 // fix: delete it … … 331 331 { 332 332 // error: if neuron 333 if (type == CELL_NEURON) //some neurons have the same single-letter names as modifiers (for example G,S,D), but they are supposed to have is_neuroclass==true so they should indeed not be handled here333 if (type == f4_Cell_type::CELL_NEURON) //some neurons have the same single-letter names as modifiers (for example G,S,D), but they are supposed to have is_neuroclass==true so they should indeed not be handled here 334 334 {//however, what we see here is actually modifiers such as IdqEbWL (so not valid neuroclasses) that occurred within an already differentiated cell of type==CELL_NEURON. 335 335 //printf("Handled as a modifier, but type==CELL_NEURON: '%c'\n", name); … … 346 346 // turn undiff. cell into a stick 347 347 // error: already differentiated 348 if (type != CELL_UNDIFF)348 if (type != f4_Cell_type::CELL_UNDIFF) 349 349 { 350 350 // fix: delete this node … … 352 352 return; // error code set -> stop further cells development 353 353 } 354 type = CELL_STICK;354 type = f4_Cell_type::CELL_STICK; 355 355 // fix dad commacount and own anglepos 356 356 if (dadlink != NULL) … … 367 367 // connection to neuron 368 368 // error: not a neuron 369 if (type != CELL_NEURON)369 if (type != f4_Cell_type::CELL_NEURON) 370 370 { 371 371 // fix: delete it … … 384 384 for (int i = 0; i < org->cell_count; i++) 385 385 { 386 if (org->C[i]->type == CELL_NEURON) neu_counter++;386 if (org->C[i]->type == f4_Cell_type::CELL_NEURON) neu_counter++; 387 387 if (org->C[i] == this) { this_index = neu_counter - 1; break; } 388 388 } … … 398 398 for (from = 0; from < org->cell_count; from++) 399 399 { 400 if (org->C[from]->type == CELL_NEURON) neu_counter++;400 if (org->C[from]->type == f4_Cell_type::CELL_NEURON) neu_counter++; 401 401 if (from_index == (neu_counter - 1)) break; 402 402 } … … 457 457 // neuron parameter 458 458 // error: not a neuron 459 if (type != CELL_NEURON)459 if (type != f4_Cell_type::CELL_NEURON) 460 460 { 461 461 // fix: delete it … … 513 513 { 514 514 genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N: 515 if (type != CELL_UNDIFF)515 if (type != f4_Cell_type::CELL_UNDIFF) 516 516 { 517 517 // fix: delete this node … … 527 527 } 528 528 neuclass = gcur->neuclass; 529 type = CELL_NEURON;529 type = f4_Cell_type::CELL_NEURON; 530 530 // change of type also halts development, to give other 531 531 // cells a chance for adjustment. Namely, it is important … … 567 567 { 568 568 if (org->C[i]->dadlink == this) 569 if (org->C[i]->type == CELL_STICK)569 if (org->C[i]->type == f4_Cell_type::CELL_STICK) 570 570 stickchildcount++; 571 571 } … … 575 575 else 576 576 P.muscle_bend_range = 1.0 / std::max(1, dadlink->stickchildcount); //bend range in f1: 0, 1 (line XX[|]) -> 100%, 2 (Y-shape X(X[|],X)) -> 50%, 3 (cross X(X[|],X,X)) -> 33% 577 //MacKo 2023-05: but shouldn't this formula ^^ also take commacount into consideration, like in f1? 578 579 if (type == CELL_STICK)577 //MacKo 2023-05: but shouldn't this formula ^^ also take commacount into consideration, like in f1? Note: in f0, the range of a newly added muscle has the default value (1.0) until modified by mutation, and the range can be set to any value completely unrelated to the junction where the muscle is located. In f1, the range of a newly added muscle depends on the number of branches (commas) in the junction including empty branches (see also conv_f1_f0_branch_muscle_range), but can be overridden by the genotype due to mutation and any value can be directly set just as in f0 - e.g. X[|,r:0.8] - so the value based on the number of commas only serves as a default. Compared to f0 and f1, f4 enforces a specific range here and this cannot be changed in any way, because as of now (2023-05) there is no support to mutate neural properties except for the N neuron. 578 579 if (type == f4_Cell_type::CELL_STICK) 580 580 { 581 581 if (dadlink == NULL) … … 716 716 for (int i = 0; i < cell_count; i++) 717 717 { 718 if (C[i]->type == CELL_NEURON)719 { 720 while (C[i]->dadlink->type == CELL_NEURON)718 if (C[i]->type == f4_Cell_type::CELL_NEURON) 719 { 720 while (C[i]->dadlink->type == f4_Cell_type::CELL_NEURON) 721 721 { 722 722 C[i]->dadlink = C[i]->dadlink->dadlink; … … 729 729 for (int i = 0; i < cell_count; i++) 730 730 { 731 if (C[i]->type == CELL_UNDIFF)732 { 733 C[i]->type = CELL_STICK;731 if (C[i]->type == f4_Cell_type::CELL_UNDIFF) 732 { 733 C[i]->type = f4_Cell_type::CELL_STICK; 734 734 //setError(); 735 735 } … … 764 764 switch (c->type) 765 765 { 766 case CELL_UNDIFF: type = "undiff"; break;767 case CELL_STICK: type = "STICK"; break;768 case CELL_NEURON: type = string("NEURON:") + c->neuclass->name.c_str(); break;769 default: type = std::to_string( c->type);766 case f4_Cell_type::CELL_UNDIFF: type = "undiff"; break; 767 case f4_Cell_type::CELL_STICK: type = "STICK"; break; 768 case f4_Cell_type::CELL_NEURON: type = string("NEURON:") + c->neuclass->name.c_str(); break; 769 default: type = std::to_string(static_cast<int>(c->type)); 770 770 } 771 771 const char *status = c->gcur == c->old_gcur ? (c->gcur != NULL ? "no progress" : "") : (c->gcur != NULL ? "progress" : "finished"); //progress or no progress means the cell is yielding = not finished but decided to halt development and wait for other cells. New cells may be created in case of "no progress" status. … … 893 893 if (curc >= cell_count) return; 894 894 895 if (C[curc]->type != CELL_STICK) return;895 if (C[curc]->type != f4_Cell_type::CELL_STICK) return; 896 896 897 897 f4_Cell *thisti = C[curc]; … … 899 899 *tmpcel = *(thisti->dadlink); 900 900 901 // adjust length, curvedness, etc.901 // Adjust length, curvedness, etc. 902 902 tmpcel->P.propagateAlong(false); 903 904 // The sequence of "while" loops below is a fast and simple heuristic (decrease as long as the value is too big, then increase as long as the value is too small). 905 // However, since every different sequence of upper- and lower-case modifiers produces a different value of a given property, 906 // all 2^N sequences should be tested to discover the best approximation of the target value of a given property using N modifiers. 907 // This issue is TODO if anybody needed this function (and modifier sequences it produces) for any serious use. Also, add support for Qq. 908 // See also GenoOperators::simplifiedModifiers() and geneprops_test.cpp. 903 909 while (tmpcel->P.length > thisti->P.length) 904 910 { … … 938 944 for (int i = 0; i < cell_count; i++) 939 945 { 940 if (C[i]->type == CELL_NEURON)946 if (C[i]->type == f4_Cell_type::CELL_NEURON) 941 947 { 942 948 if (C[i]->dadlink == thisti) … … 971 977 for (int i = 0; i < cell_count; i++) 972 978 { 973 if (C[i]->type == CELL_STICK)979 if (C[i]->type == f4_Cell_type::CELL_STICK) 974 980 { 975 981 if (C[i]->dadlink == thisti) -
cpp/frams/genetics/f4/f4_general.h
r1249 r1259 33 33 class f4_Cells; // later 34 34 35 36 /** @name Types of f4_Cell's */ 37 //@{ 38 #define CELL_UNDIFF 40 ///<undifferentiated cell 39 #define CELL_STICK 41 ///<differentiated to stick, cannot divide 40 #define CELL_NEURON 42 ///<differentiated to neuron, can divide 41 //@} 42 35 enum class f4_Cell_type { 36 CELL_UNDIFF, ///<undifferentiated cell 37 CELL_STICK, ///<differentiated to stick, cannot divide 38 CELL_NEURON ///<differentiated to neuron, can divide 39 }; 43 40 44 41 class f4_CellConn; … … 154 151 * at least once. If one cell requires another one to develop, oneStep 155 152 * should be deployed again on this cell. 156 * 153 * 157 154 * This method, unlike genotype tree creation, checks semantics. This means that 158 155 * this function will fail (set error code) if: … … 184 181 185 182 int nr; ///<number of cell (seems to be used only in the approximate f1 converter for neuron connections) 186 int type;///<type183 f4_Cell_type type; ///<type 187 184 f4_Cell *dadlink; ///<pointer to cell parent 188 185 f4_Cells *org; ///<uplink to organism … … 462 459 * @return a pointer to the f4_Node object representing the f4 tree root 463 460 */ 464 //f4_Node* f4_processTree(const char *geno);465 466 /**467 * Scans a genotype string starting from a given position. This recursive method creates468 * a tree of f4_Node objects. This method extracts each potentially functional element469 * of a genotype string to a separate f4_Nodes. When the branching character '<' occurs,470 * f4_processRecur is deployed for the latest f4_Node element. This method does not471 * analyse the genotype semantically, it only checks if the syntax is proper. The only472 * semantic aspect is neuron class name extraction, where the GenoOperators473 * class is used to parse the potential neuron class name.474 * This is an internal function; for regular cases, use f4_process().475 * @param genot the string with the entire genotype476 * @param genot_len length of genot (precomputed for efficiency)477 * @param pos_inout the current position of processing in string (advanced by the function)478 * @param parent current parent of the analysed branch of the genotype479 * @return 0 if processing was successful, otherwise returns the position of an error in the genotype480 */461 //f4_Node* f4_processTree(const char *geno); 462 463 /** 464 * Scans a genotype string starting from a given position. This recursive method creates 465 * a tree of f4_Node objects. This method extracts each potentially functional element 466 * of a genotype string to a separate f4_Nodes. When the branching character '<' occurs, 467 * f4_processRecur is deployed for the latest f4_Node element. This method does not 468 * analyse the genotype semantically, it only checks if the syntax is proper. The only 469 * semantic aspect is neuron class name extraction, where the GenoOperators 470 * class is used to parse the potential neuron class name. 471 * This is an internal function; for regular cases, use f4_process(). 472 * @param genot the string with the entire genotype 473 * @param genot_len length of genot (precomputed for efficiency) 474 * @param pos_inout the current position of processing in string (advanced by the function) 475 * @param parent current parent of the analysed branch of the genotype 476 * @return 0 if processing was successful, otherwise returns the position of an error in the genotype 477 */ 481 478 int f4_processRecur(const char *genot, const int genot_len, int &pos_inout, f4_Node *parent); 482 479 -
cpp/frams/genetics/f4/f4_oper.cpp
r1247 r1259 15 15 // 16 16 // TODO the behavior of neuron input indexes during mutation seems badly implemented (see also TREAT_BAD_CONNECTIONS_AS_INVALID_GENO). Are they kept properly maintained when nodes are added and removed? This could be done well because during mutation we operate on the tree structure with cross-references between nodes (so they should not be affected by local changes in the tree), and then convert the tree back to string. Yet, the f4_Node.conn_from is an integer and these fields in nodes do not seem to be maintained on tree node adding/removal... change these integer offsets to references to node objects? But actually, do the offsets that constitute relative connection references concern the f4_Node tree structure (and all these sophisticated calculations of offsets during mutation are useful) or rather they concern the f4_Cells development? verify all situations in f4_Cell::oneStep(), case '['. 17 // TODO in mutation, adding the '#' gene does not seem to be effective. The gene is added and genotypes are valid, but hardly ever #n is effective, i.e., it hardly ever multiplicates body or brain parts... investigate! And maybe, during repair or mutations, simplify/remove ineffective #N genes, if there is no chance/hardly any chance that they will be turned effective after future mutation (or crossover)17 // TODO in mutation, adding the '#' gene does not seem to be effective. The gene is added and genotypes are valid, but hardly ever #n is effective, i.e., it hardly ever multiplicates body or brain parts... investigate! Maybe most places it is added at are ineffective. And maybe, during repair or mutations, simplify/remove ineffective #N genes, if there is no chance/hardly any chance that they will be turned effective after future mutation (or crossover) 18 18 // TODO add support for properties of (any class of) neurons - not just sigmoid/force/intertia (':' syntax) for N 19 19 // TODO add mapping genotype character ranges for neural [connections] 20 // TODO The f0 genotypes for /*4*/<<RX>X>X> and RX(X,X) are identical, but if you replace R with Q or C, there are small differences (they were present both before and after the Q,C change in f1 in 2023-05) - check why and perhaps unify?20 // TODO The f0 genotypes for /*4*/<<RX>X>X> and RX(X,X) are identical, but if you replace R with C or Q, there are small differences (they were present both before and after the change in C,Q effects in the f1 converter in 2023-06, see conv_f1_f0_cq_influence) - check why (modifiers affecting cells=sticks are applied differently or skip some initial sticks?) and perhaps unify with f1? 21 21 // TODO F4_SIMPLIFY_MODIFIERS in f4_general.cpp: currently it works while parsing (which is a bit "cheating": we get a phenotype that is a processed version of the genotype, thus some changes in modifiers in the genotype have no effect on its phenotype). Another (likely better) option, instead of simplifying while parsing, would be during mutations (like it is done in f1): when mutations add/modify/remove a modifier node, they could "clean" the tree by simplifying modifiers on the same subpath just as GenoOperators::simplifiedModifiers() does. This way, simplifying would be only performed when we actually modify a part of a genotype, not each time we interpret it, and there would be no hidden mechanism: all visible genes would have an expected effect on the phenotype. 22 // TODO improve the way modifiers are handled in the f4->f1 approximate converter (used extremely rarely just for illustration) 22 23 23 24 … … 43 44 { 44 45 { "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT + F4_MODNEU_COUNT + 2, }, 45 { "f4_mut_add", 0, 0, "Add node", "f 0 100 50", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", },46 { "f4_mut_add_div", 0, 0, "- add division", "f 0 100 20", FIELD(probadd[F4_ADD_DIV]), "Add node mutation: probability of adding a division", },47 { "f4_mut_add_conn", 0, 0, "- add connection", "f 0 100 1 5", FIELD(probadd[F4_ADD_CONN]), "Add node mutation: probability of adding a neural connection", },48 { "f4_mut_add_neupar", 0, 0, "- add neuron property", "f 0 100 5", FIELD(probadd[F4_ADD_NEUPAR]), "Add node mutation: probability of adding a neuron property/modifier", },49 { "f4_mut_add_rep", 0, 0, "- add repetition '#'", "f 0 100 1 0", FIELD(probadd[F4_ADD_REP]), "Add node mutation: probability of adding the '#' repetition gene", },50 { "f4_mut_add_simp", 0, 0, "- add simple node", "f 0 100 50", FIELD(probadd[F4_ADD_SIMP]), "Add node mutation: probability of adding a random, simple gene", },51 52 { "f4_mut_del", 0, 0, "Delete node", "f 0 100 20", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", },53 54 { "f4_mut_mod", 0, 0, "Modify node", "f 0 100 30", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", },55 { "f4_mut_modneu_conn", 0, 0, "- neuron input: modify source", "f 0 100 60", FIELD(probmodneu[F4_MODNEU_CONN]), "Neuron input mutation: probability of changing its source neuron", },56 { "f4_mut_modneu_weight", 0, 0, "- neuron input: modify weight", "f 0 100 40", FIELD(probmodneu[F4_MODNEU_WEIGHT]), "Neuron input mutation: probability of changing its weight", },46 { "f4_mut_add", 0, 0, "Add node", "f 0 100 4", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", }, 47 { "f4_mut_add_div", 0, 0, "- add division", "f 0 100 4", FIELD(probadd[F4_ADD_DIV]), "Add node mutation: probability of adding a division", }, 48 { "f4_mut_add_conn", 0, 0, "- add connection", "f 0 100 1", FIELD(probadd[F4_ADD_CONN]), "Add node mutation: probability of adding a neural connection", }, 49 { "f4_mut_add_neupar", 0, 0, "- add neuron property", "f 0 100 1", FIELD(probadd[F4_ADD_NEUPAR]), "Add node mutation: probability of adding a neuron property/modifier", }, 50 { "f4_mut_add_rep", 0, 0, "- add repetition '#'", "f 0 100 1", FIELD(probadd[F4_ADD_REP]), "Add node mutation: probability of adding the '#' repetition gene", }, 51 { "f4_mut_add_simp", 0, 0, "- add simple node", "f 0 100 4", FIELD(probadd[F4_ADD_SIMP]), "Add node mutation: probability of adding a random, simple gene", }, 52 53 { "f4_mut_del", 0, 0, "Delete node", "f 0 100 1", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", }, 54 55 { "f4_mut_mod", 0, 0, "Modify node", "f 0 100 1", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", }, 56 { "f4_mut_modneu_conn", 0, 0, "- neuron input: modify source", "f 0 100 3", FIELD(probmodneu[F4_MODNEU_CONN]), "Neuron input mutation: probability of changing its source neuron", }, 57 { "f4_mut_modneu_weight", 0, 0, "- neuron input: modify weight", "f 0 100 3", FIELD(probmodneu[F4_MODNEU_WEIGHT]), "Neuron input mutation: probability of changing its weight", }, 57 58 58 59 { "f4_mut_max_rep", 1, 0, "Maximum number for '#' repetitions", "d 2 20 6", FIELD(mut_max_rep), "Maximum allowed number of repetitions for the '#' repetition gene", },
Note: See TracChangeset
for help on using the changeset viewer.