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)
106 int neighbours = nw+n+ne+e+se+s+sw+w;
111 if (neighbours == 3) {
117 if (neighbours == 2 || neighbours == 3) {
123 return 0 ; // should throw an error here
127 vector<state> rotate_inputs(const vector<state>& inputs,int rot)
129 vector<state> rotinp(inputs);
130 rotate_copy(inputs.begin()+1,inputs.begin()+1+rot,inputs.end(),rotinp.begin()+1);
134 vector<state> reflect_inputs(const vector<state>& inputs,int neighbourhood_size)
136 vector<state> refinp(inputs);
137 if(neighbourhood_size==5) // CNESW
139 refinp[2]=inputs[4]; // swap E and W
142 else // neighbourhood_size==9 (C,N,NE,E,SE,S,SW,W,NW)
149 refinp[6]=inputs[4]; // swap all E and W
154 // simple rule structure, e.g. 1,2,[4,5],8,2 -> 0
158 set<state> inputs[9]; // c,n,ne,e,se,s,sw,w,nw or c,n,e,s,w
159 state ns; // new state
161 int n_inputs; // 5: von Neumann; 9: Moore
166 rule(const rule& r) : ns(r.ns),n_inputs(r.n_inputs),symm(r.symm)
168 for(int i=0;i<n_inputs;i++)
169 inputs[i]=r.inputs[i];
171 rule& operator=(const rule& r)
176 for(int i=0;i<n_inputs;i++)
177 inputs[i]=r.inputs[i];
180 rule(const vector<state>& inputs,int n_inputs,state ns1,TSymm symm1)
181 : ns(ns1),n_inputs(n_inputs),symm(symm1)
186 // if we merge the rule and the supplied transition, will the rule remain true for all cases?
187 bool can_merge(const vector<state>& test_inputs,state ns1) const
189 if(ns1!=ns) return false; // can't merge if the outputs are different
191 // If you are running through your own transitions, or for small state spaces,
192 // you might want to turn off this optimisation, to get better compression.
193 // On JvN29 it doesn't make any difference but on Nobili32 it does.
194 const bool forbid_multiple_input_differences = true;
196 if(forbid_multiple_input_differences)
198 // optimisation: we skip this rule if more than 2 entries are different, we
199 // assume we will have considered the relevant one-change rules before this.
201 for(int i=0;i<n_inputs;i++)
202 if(inputs[i].find(test_inputs[i])==inputs[i].end())
205 // just check the new permutations
206 for(int i=0;i<n_inputs;i++)
208 if(inputs[i].find(test_inputs[i])==inputs[i].end())
211 r1.inputs[i].clear(); // (since we don't need to re-test all the other permutations)
212 r1.inputs[i].insert(test_inputs[i]);
213 if(!r1.all_true()) return false;
219 // need to check all combinations - this can be very slow for large state spaces
220 for(int i=0;i<n_inputs;i++)
222 if(inputs[i].find(test_inputs[i])==inputs[i].end())
225 r1.merge(test_inputs); // this is what makes it slow, we may introduce many new permutations
226 r1.inputs[i].clear(); // (since we don't need to re-test all the old permutations)
227 r1.inputs[i].insert(test_inputs[i]);
228 if(!r1.all_true()) return false;
234 // merge the inputs with this rule
235 void merge(const vector<state>& new_inputs)
237 for(int i=0;i<n_inputs;i++)
238 inputs[i].insert(new_inputs[i]); // may already exist, set ignores if so
241 // is this set of inputs a match for the rule, for the given symmetry?
242 bool matches(const vector<state>& test_inputs) const
244 int n_rotations,rotation_skip;
249 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
253 n_rotations=4; rotation_skip=1; do_reflect=false;
257 n_rotations=4; rotation_skip=2; do_reflect=false;
260 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
261 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
265 n_rotations=4; rotation_skip=1; do_reflect=true;
269 n_rotations=4; rotation_skip=2; do_reflect=true;
272 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
274 for(int iRot=0;iRot<n_rotations;iRot++)
276 if(nosymm_matches(rotate_inputs(test_inputs,iRot*rotation_skip)))
278 if(do_reflect && nosymm_matches(reflect_inputs(rotate_inputs(test_inputs,iRot*rotation_skip),n_inputs)))
281 return false; // no match found
286 // ignoring symmetry, does this set of inputs match the rule?
287 bool nosymm_matches(const vector<state>& test_inputs) const
289 for(int i=0;i<n_inputs;i++)
290 if(inputs[i].find(test_inputs[i])==inputs[i].end())
295 // is the rule true in all permutations?
296 bool all_true() const
298 set<state>::const_iterator c_it,n_it,ne_it,e_it,se_it,s_it,sw_it,w_it,nw_it;
301 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
302 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
303 for(ne_it = inputs[2].begin();ne_it!=inputs[2].end();ne_it++)
304 for(e_it = inputs[3].begin();e_it!=inputs[3].end();e_it++)
305 for(se_it = inputs[4].begin();se_it!=inputs[4].end();se_it++)
306 for(s_it = inputs[5].begin();s_it!=inputs[5].end();s_it++)
307 for(sw_it = inputs[6].begin();sw_it!=inputs[6].end();sw_it++)
308 for(w_it = inputs[7].begin();w_it!=inputs[7].end();w_it++)
309 for(nw_it = inputs[8].begin();nw_it!=inputs[8].end();nw_it++)
310 if(slowcalc(*nw_it,*n_it,*ne_it,*w_it,*c_it,*e_it,*sw_it,*s_it,*se_it)!=ns)
315 for(c_it = inputs[0].begin();c_it!=inputs[0].end();c_it++)
316 for(n_it = inputs[1].begin();n_it!=inputs[1].end();n_it++)
317 for(e_it = inputs[2].begin();e_it!=inputs[2].end();e_it++)
318 for(s_it = inputs[3].begin();s_it!=inputs[3].end();s_it++)
319 for(w_it = inputs[4].begin();w_it!=inputs[4].end();w_it++)
320 if(slowcalc(0,*n_it,0,*w_it,*c_it,*e_it,0,*s_it,0)!=ns)
327 // makes a unique variable name for a given value
328 string get_variable_name(unsigned int iVar)
330 const char VARS[52]={'a','b','c','d','e','f','g','h','i','j',
331 'k','l','m','n','o','p','q','r','s','t','u','v','w','x',
332 'y','z','A','B','C','D','E','F','G','H','I','J','K','L',
333 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
338 oss << VARS[(iVar-(iVar%52))/52 - 1] << VARS[iVar%52];
340 oss << "!"; // we have a 52*52 limit ("should be enough for anyone")
344 void print_rules(const vector<rule>& rules,ostream& out)
346 // first collect all variables (makes reading easier)
347 map< string , set<state> > vars;
348 ostringstream rules_out;
349 for(vector<rule>::const_iterator r_it=rules.begin();r_it!=rules.end();r_it++)
351 vector<string> variables_used;
352 for(int i=0;i<r_it->n_inputs;i++)
354 // if more than one state for this input, we need a variable
355 if(r_it->inputs[i].size()>1)
358 // is there a variable that matches these inputs, and that we haven't used?
359 bool found_unused_var=false;
360 for(map<string, set<state> >::const_iterator v_it=vars.begin();v_it!=vars.end();v_it++)
362 if(v_it->second==r_it->inputs[i] && find(variables_used.begin(),variables_used.end(),v_it->first)==variables_used.end())
364 found_unused_var = true;
369 if(!found_unused_var)
371 // we need to make a new one for this set of inputs
372 var = get_variable_name(vars.size());
373 // add it to the list of made variables
374 vars[var] = r_it->inputs[i];
376 out << "var " << var << "={";
377 set<state>::const_iterator it=r_it->inputs[i].begin();
382 if(it!=r_it->inputs[i].end()) out << ",";
387 // add the variable to the list of used ones
388 variables_used.push_back(var);
389 rules_out << var << ",";
393 // just a state, output it
394 rules_out << (int)*r_it->inputs[i].begin() << ",";
397 rules_out << (int)r_it->ns << endl;
399 out << rules_out.str();
402 void produce_rule_table(vector<rule>& rules,int N,int nhood_size,TSymm symm,bool remove_stasis)
404 int n_rotations,rotation_skip;
409 case none: n_rotations=1; rotation_skip=1; do_reflect=false; break;
413 n_rotations=4; rotation_skip=1; do_reflect=false;
417 n_rotations=4; rotation_skip=2; do_reflect=false;
420 case rotate8: n_rotations=8; rotation_skip=1; do_reflect=false; break;
421 case reflect: n_rotations=1; rotation_skip=1; do_reflect=true; break;
425 n_rotations=4; rotation_skip=1; do_reflect=true;
429 n_rotations=4; rotation_skip=2; do_reflect=true;
432 case rotate8reflect: n_rotations=8; rotation_skip=1; do_reflect=true; break;
435 state c,n,ne,nw,sw,s,se,e,w,ns;
436 vector<rule>::iterator it;
440 cout << "\nProcessing for c=" << (int)c << ", " << rules.size() << " rules so far." << endl;
444 vector<state> inputs(9);
471 ns = slowcalc(nw,n,ne,w,c,e,sw,s,se);
472 if(remove_stasis && ns == c)
473 continue; // we can ignore stasis transitions
474 // can we merge this transition with any existing rule?
477 for(it=rules.begin();!merged && it!=rules.end();it++)
480 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
482 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
484 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
487 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
489 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
496 // need to make a new rule starting with this transition
497 rule r(inputs,nhood_size,ns,symm);
509 else // nhood_size==5
511 vector<state> inputs(5);
526 ns = slowcalc(0,n,0,w,c,e,0,s,0);
527 if(remove_stasis && ns == c)
528 continue; // we can ignore stasis transitions
530 // can we merge this transition with any existing rule?
533 for(it=rules.begin();!merged && it!=rules.end();it++)
536 for(int iRot=0;!merged && iRot<n_rotations;iRot++)
538 if(r.can_merge(rotate_inputs(inputs,iRot*rotation_skip),ns))
540 r.merge(rotate_inputs(inputs,iRot*rotation_skip));
543 else if(do_reflect && r.can_merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size),ns))
545 r.merge(reflect_inputs(rotate_inputs(inputs,iRot*rotation_skip),nhood_size));
552 // need to make a new rule starting with this transition
553 rule r(inputs,nhood_size,ns,symm);
564 // here we use the computed rule table as a replacement slowcalc, for checking correctness
565 state new_slowcalc(const vector<rule>& rules,const vector<state>& inputs)
567 for(vector<rule>::const_iterator it=rules.begin();it!=rules.end();it++)
568 if(it->matches(inputs))
570 return inputs[0]; // default: no change
573 bool is_correct(const vector<rule>&rules,int N,int neighbourhood_size)
576 state c,n,ne,nw,sw,s,se,e,w;
577 if(neighbourhood_size==9)
579 vector<state> inputs(9);
607 if(new_slowcalc(rules,inputs)
608 != slowcalc(nw,n,ne,w,c,e,sw,s,se))
622 vector<state> inputs(5);
638 if(new_slowcalc(rules,inputs)
639 != slowcalc(0,n,0,w,c,e,0,s,0))
652 // parameters for use:
653 const int N_STATES = 2;
654 const TSymm symmetry = none;
655 const int nhood_size = 9;
656 const string output_filename = "life.table";
657 const bool remove_stasis_transitions = false;
662 produce_rule_table(rules,N_STATES,nhood_size,symmetry,remove_stasis_transitions);
664 int n_secs = (int)difftime(t2,t1);
665 cout << "\nProcessing took: " << n_secs << " seconds." << endl;
668 ofstream out(output_filename.c_str());
669 out << "# rules: " << rules.size() << "\n#";
670 out << "\n# Golly rule-table format.\n# Each rule: C,";
674 out << "N,NE,E,SE,S,SW,W,NW";
676 out << "\n# N.B. Where the same variable appears multiple times in a transition,\n# it takes the same value each time.\n#";
677 if(remove_stasis_transitions)
678 out << "\n# Default for transitions not listed: no change\n#";
680 out << "\n# All transitions should be included below, including no-change ones.\n#";
681 out << "\nn_states:" << N_STATES;
682 out << "\nneighborhood:" << ((nhood_size==5)?("vonNeumann"):("Moore"));
683 out << "\nsymmetries:" << symmetry_strings[symmetry] << "\n";
684 print_rules(rules,out);
686 cout << rules.size() << " rules written." << endl;
688 // optional: run through the entire state space, checking that new_slowcalc matches slowcalc
689 cout << "Verifying is correct (can abort if you're confident)...";
691 if(is_correct(rules,N_STATES,nhood_size))
692 cout << "yes." << endl;
694 cout << "no! Either there's a bug in the code, or the transition function does not have the symmetry you selected." << endl;