3 This file is part of Golly, a Game of Life Simulator.
4 Copyright (C) 2008 Andrew Trevorrow and Tomas Rokicki.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 Web site: http://sourceforge.net/projects/golly
21 Authors: rokicki@gmail.com andrew@trevorrow.com
38 Makes a rule-table for your transition function.
41 g++ -O5 -o make-ruletable make-ruletable.cpp
42 or in Microsoft Visual Studio, add to an empty CLR project.
45 1) fill slowcalc with your own transition function.
46 2) set the parameters in main() at the bottom.
47 3) execute the program from the command line (no options taken)
49 For a 32-state 5-neighbourhood rule it took 16mins on a 2.2GHz machine.
50 For a 4-state 9-neighbourhood rule it took 4s.
52 The merging is very simple - if a transition is compatible with the first rule,
53 then merge the transition into the rule. If not, try the next rule. If there are
54 no more rules, add a new rule containing just the transition.
56 === Worked example: ===
62 Start. First transition taken as first rule:
64 Next transition - is it compatible with rule 1?
65 i.e. is 1,[0,2],0->3 valid for all permutations? Yes.
66 rule 1 now: 1,[0,2],0->3
67 Next transition - is it compatible with rule 1?
68 i.e. is [1,2],[0,2],0->3 valid for all permutations?
69 no - because 2,2,0 ->1, not ->3. so add the transition as a new rule:
70 rule 1 still: 1,[0,2],0 -> 3
72 Next transition - is it compatible with rule 1? no - output is different.
73 Is it compatible with rule 2? no - output is different.
78 Written with variables:
85 In the function produce_rule_table, the state space is exhaustively traversed.
86 If your transition function consists of transition rules already then you can
87 optimise by running through the transitions instead. You might also want to
88 turn off the optimisation in rule::can_merge, to see if it gives you better
91 Also note: I feel sure there are better ways to compress rule tables than this...
93 Contact: Tim Hutton <tim.hutton@gmail.com>
97 // some typedefs and compile-time constants
98 typedef unsigned short state;
99 enum TSymm { none, rotate4, rotate8, reflect, rotate4reflect, rotate8reflect };
100 static const string symmetry_strings[] = {"none","rotate4","rotate8","reflect","rotate4reflect","rotate8reflect"};
102 // fill in this function with your desired transition rules
103 // (for von Neumann neighbourhoods, just ignore the nw,se,sw,ne inputs)
104 state slowcalc(state nw,state n,state ne,state w,state c,state e,state sw,state s,state se)
113 if ((((1+(nw==1)+(n==1)+(ne==1)+(w==1)+(e==1)+(sw==1)+
114 (s==1)+(se==1))) | 1) == 3)
119 return 0 ; // should throw an error here
123 vector<state> rotate_inputs(const vector<state>& inputs,int rot)
125 vector<state> rotinp(inputs);
126 rotate_copy(inputs.begin()+1,inputs.begin()+1+rot,inputs.end(),rotinp.begin()+1);
130 vector<state> reflect_inputs(const vector<state>& inputs,int neighbourhood_size)
132 vector<state> refinp(inputs);
133 if(neighbourhood_size==5) // CNESW
135 refinp[2]=inputs[4]; // swap E and W
138 else // neighbourhood_size==9 (C,N,NE,E,SE,S,SW,W,NW)
145 refinp[6]=inputs[4]; // swap all E and W
150 // simple rule structure, e.g. 1,2,[4,5],8,2 -> 0
154 set<state> inputs[9]; // c,n,ne,e,se,s,sw,w,nw or c,n,e,s,w
155 state ns; // new state
157 int n_inputs; // 5: von Neumann; 9: Moore
162 rule(const rule& r) : ns(r.ns),n_inputs(r.n_inputs),symm(r.symm)
164 for(int i=0;i<n_inputs;i++)
165 inputs[i]=r.inputs[i];
167 rule& operator=(const rule& r)
172 for(int i=0;i<n_inputs;i++)
173 inputs[i]=r.inputs[i];
176 rule(const vector<state>& inputs,int n_inputs,state ns1,TSymm symm1)
177 : ns(ns1),n_inputs(n_inputs),symm(symm1)
182 // if we merge the rule and the supplied transition, will the rule remain true for all cases?
183 bool can_merge(const vector<state>& test_inputs,state ns1) const
185 if(ns1!=ns) return false; // can't merge if the outputs are different
187 // If you are running through your own transitions, or for small state spaces,
188 // you might want to turn off this optimisation, to get better compression.
189 // On JvN29 it doesn't make any difference but on Nobili32 it does.
190 const bool forbid_multiple_input_differences = true;
192 if(forbid_multiple_input_differences)
194 // optimisation: we skip this rule if more than 2 entries are different, we
195 // assume we will have considered the relevant one-change rules before this.
197 for(int i=0;i<n_inputs;i++)
198 if(inputs[i].find(test_inputs[i])==inputs[i].end())
201 // just check the new permutations
202 for(int i=0;i<n_inputs;i++)
204 if(inputs[i].find(test_inputs[i])==inputs[i].end())
207 r1.inputs[i].clear(); // (since we don't need to re-test all the other permutations)
208 r1.inputs[i].insert(test_inputs[i]);
209 if(!r1.all_true()) return false;
215 // need to check all combinations - this can be very slow for large state spaces
216 for(int i=0;i<n_inputs;i++)
218 if(inputs[i].find(test_inputs[i])==inputs[i].end())
221 r1.merge(test_inputs); // this is what makes it slow, we may introduce many new permutations
222 r1.inputs[i].clear(); // (since we don't need to re-test all the old permutations)
223 r1.inputs[i].insert(test_inputs[i]);
224 if(!r1.all_true()) return false;
230 // merge the inputs with this rule
231 void merge(const vector<state>& new_inputs)
233 for(int i=0;i<n_inputs;i++)
234 inputs[i].insert(new_inputs[i]); // may already exist, set ignores if so
237 // is this set of inputs a match for the rule, for the given symmetry?
238 bool matches(const vector<state>& test_inputs) const
240 int n_rotations,rotation_skip;
245 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
249 n_rotations=4; rotation_skip=1; do_reflect=false;
253 n_rotations=4; rotation_skip=2; do_reflect=false;
256 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
257 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
261 n_rotations=4; rotation_skip=1; do_reflect=true;
265 n_rotations=4; rotation_skip=2; do_reflect=true;
268 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
270 for(int iRot=0;iRot<n_rotations;iRot++)
272 if(nosymm_matches(rotate_inputs(test_inputs,iRot*rotation_skip)))
274 if(do_reflect && nosymm_matches(reflect_inputs(rotate_inputs(test_inputs,iRot*rotation_skip),n_inputs)))
277 return false; // no match found
282 // ignoring symmetry, does this set of inputs match the rule?
283 bool nosymm_matches(const vector<state>& test_inputs) const
285 for(int i=0;i<n_inputs;i++)
286 if(inputs[i].find(test_inputs[i])==inputs[i].end())
291 // is the rule true in all permutations?
292 bool all_true() const
294 set<state>::const_iterator c_it,n_it,ne_it,e_it,se_it,s_it,sw_it,w_it,nw_it;
297 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
298 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
299 for(ne_it = inputs[2].begin();ne_it!=inputs[2].end();ne_it++)
300 for(e_it = inputs[3].begin();e_it!=inputs[3].end();e_it++)
301 for(se_it = inputs[4].begin();se_it!=inputs[4].end();se_it++)
302 for(s_it = inputs[5].begin();s_it!=inputs[5].end();s_it++)
303 for(sw_it = inputs[6].begin();sw_it!=inputs[6].end();sw_it++)
304 for(w_it = inputs[7].begin();w_it!=inputs[7].end();w_it++)
305 for(nw_it = inputs[8].begin();nw_it!=inputs[8].end();nw_it++)
306 if(slowcalc(*nw_it,*n_it,*ne_it,*w_it,*c_it,*e_it,*sw_it,*s_it,*se_it)!=ns)
311 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
312 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
313 for(e_it = inputs[2].begin();e_it!=inputs[2].end();e_it++)
314 for(s_it = inputs[3].begin();s_it!=inputs[3].end();s_it++)
315 for(w_it = inputs[4].begin();w_it!=inputs[4].end();w_it++)
316 if(slowcalc(0,*n_it,0,*w_it,*c_it,*e_it,0,*s_it,0)!=ns)
323 // makes a unique variable name for a given value
324 string get_variable_name(unsigned int iVar)
326 const char VARS[52]={'a','b','c','d','e','f','g','h','i','j',
327 'k','l','m','n','o','p','q','r','s','t','u','v','w','x',
328 'y','z','A','B','C','D','E','F','G','H','I','J','K','L',
329 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
334 oss << VARS[(iVar-(iVar%52))/52 - 1] << VARS[iVar%52];
336 oss << "!"; // we have a 52*52 limit ("should be enough for anyone")
340 void print_rules(const vector<rule>& rules,ostream& out)
342 // first collect all variables (makes reading easier)
343 map< string , set<state> > vars;
344 ostringstream rules_out;
345 for(vector<rule>::const_iterator r_it=rules.begin();r_it!=rules.end();r_it++)
347 vector<string> variables_used;
348 for(int i=0;i<r_it->n_inputs;i++)
350 // if more than one state for this input, we need a variable
351 if(r_it->inputs[i].size()>1)
354 // is there a variable that matches these inputs, and that we haven't used?
355 bool found_unused_var=false;
356 for(map<string, set<state> >::const_iterator v_it=vars.begin();v_it!=vars.end();v_it++)
358 if(v_it->second==r_it->inputs[i] && find(variables_used.begin(),variables_used.end(),v_it->first)==variables_used.end())
360 found_unused_var = true;
365 if(!found_unused_var)
367 // we need to make a new one for this set of inputs
368 var = get_variable_name(vars.size());
369 // add it to the list of made variables
370 vars[var] = r_it->inputs[i];
372 out << "var " << var << "={";
373 set<state>::const_iterator it=r_it->inputs[i].begin();
378 if(it!=r_it->inputs[i].end()) out << ",";
383 // add the variable to the list of used ones
384 variables_used.push_back(var);
385 rules_out << var << ",";
389 // just a state, output it
390 rules_out << (int)*r_it->inputs[i].begin() << ",";
393 rules_out << (int)r_it->ns << endl;
395 out << rules_out.str();
398 void produce_rule_table(vector<rule>& rules,int N,int nhood_size,TSymm symm,bool remove_stasis)
400 int n_rotations,rotation_skip;
405 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
409 n_rotations=4; rotation_skip=1; do_reflect=false;
413 n_rotations=4; rotation_skip=2; do_reflect=false;
416 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
417 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
421 n_rotations=4; rotation_skip=1; do_reflect=true;
425 n_rotations=4; rotation_skip=2; do_reflect=true;
428 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
431 state c,n,ne,nw,sw,s,se,e,w,ns;
432 vector<rule>::iterator it;
436 cout << "\nProcessing for c=" << (int)c << ", " << rules.size() << " rules so far." << endl;
440 vector<state> inputs(9);
467 ns = slowcalc(nw,n,ne,w,c,e,sw,s,se);
468 if(remove_stasis && ns == c)
469 continue; // we can ignore stasis transitions
470 // can we merge this transition with any existing rule?
473 for(it=rules.begin();!merged && it!=rules.end();it++)
476 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
478 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
480 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
483 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
485 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
492 // need to make a new rule starting with this transition
493 rule r(inputs,nhood_size,ns,symm);
505 else // nhood_size==5
507 vector<state> inputs(5);
522 ns = slowcalc(0,n,0,w,c,e,0,s,0);
523 if(remove_stasis && ns == c)
524 continue; // we can ignore stasis transitions
526 // can we merge this transition with any existing rule?
529 for(it=rules.begin();!merged && it!=rules.end();it++)
532 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
534 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
536 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
539 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
541 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
548 // need to make a new rule starting with this transition
549 rule r(inputs,nhood_size,ns,symm);
560 // here we use the computed rule table as a replacement slowcalc, for checking correctness
561 state new_slowcalc(const vector<rule>& rules,const vector<state>& inputs)
563 for(vector<rule>::const_iterator it=rules.begin();it!=rules.end();it++)
564 if(it->matches(inputs))
566 return inputs[0]; // default: no change
569 bool is_correct(const vector<rule>&rules,int N,int neighbourhood_size)
572 state c,n,ne,nw,sw,s,se,e,w;
573 if(neighbourhood_size==9)
575 vector<state> inputs(9);
603 if(new_slowcalc(rules,inputs)
604 != slowcalc(nw,n,ne,w,c,e,sw,s,se))
618 vector<state> inputs(5);
634 if(new_slowcalc(rules,inputs)
635 != slowcalc(0,n,0,w,c,e,0,s,0))
648 // parameters for use:
649 const int N_STATES = 4;
650 const TSymm symmetry = rotate8;
651 const int nhood_size = 9;
652 const string output_filename = "wireworld.table";
653 const bool remove_stasis_transitions = true;
658 produce_rule_table(rules,N_STATES,nhood_size,symmetry,remove_stasis_transitions);
660 int n_secs = (int)difftime(t2,t1);
661 cout << "\nProcessing took: " << n_secs << " seconds." << endl;
664 ofstream out(output_filename.c_str());
665 out << "# rules: " << rules.size() << "\n#";
666 out << "\n# Golly rule-table format.\n# Each rule: C,";
670 out << "N,NE,E,SE,S,SW,W,NW";
672 out << "\n# N.B. Where the same variable appears multiple times in a transition,\n# it takes the same value each time.\n#";
673 if(remove_stasis_transitions)
674 out << "\n# Default for transitions not listed: no change\n#";
676 out << "\n# All transitions should be included below, including no-change ones.\n#";
677 out << "\nn_states:" << N_STATES;
678 out << "\nneighborhood:" << ((nhood_size==5)?("vonNeumann"):("Moore"));
679 out << "\nsymmetries:" << symmetry_strings[symmetry] << "\n";
680 print_rules(rules,out);
682 cout << rules.size() << " rules written." << endl;
684 // optional: run through the entire state space, checking that new_slowcalc matches slowcalc
685 cout << "Verifying is correct (can abort if you're confident)...";
687 if(is_correct(rules,N_STATES,nhood_size))
688 cout << "yes." << endl;
690 cout << "no! Either there's a bug in the code, or the transition function does not have the symmetry you selected." << endl;