1 'use strict';
  2 
  3 /**
  4 *  Gridpoint is what Gridmodels are made of. Contains everything that may happen in 1 locality. 
  5 */
  6 
  7 class Gridpoint {
  8   /**
  9   *  The constructor function for a @Gridpoint object. Takes an optional template to copy primitives from. (NOTE!! Other types of objects are NOT deep copied by default)
 10   *  If you need synchronous updating with complex objects (for whatever reason), replate line 18 with line 19. This will slow things down quite a bit, so ony use this
 11   *  if you really need it. A better option is to use asynchronous updating so you won't have to worry about this at all :)
 12   *  @param {Gridpoint} template Optional template to make a new @Gridpoint from
 13   */
 14   constructor(template) {
 15     for (var prop in template)
 16       this[prop] = template[prop];                  // Shallow copy. It's fast, but be careful with syncronous updating!
 17     // this[prop] = copy(template[prop])         // Deep copy. Takes much more time, and you'll likely end up copying much more than necessary. Use only if you're sure you need it!
 18   }
 19 }
 20 
 21 /**
 22  *  Graph is a wrapper-class for a Dygraph element (see https://dygraphs.com/). It is attached to the DOM-windows, and stores all values to be plotted, colours, title, axis names, etc. 
 23  */
 24 
 25 class Graph {
 26     /**
 27   *  The constructor function for a @Canvas object. 
 28   *  @param {Array} labels array of strings containing the labels for datapoints (e.g. for the legend)
 29   *  @param {Array} values Array of floats to plot (here plotted over time)
 30   * @param {Array} colours Array of colours to use for plotting
 31   * @param {String} title Title of the plot
 32   * @param {Object} opts dictionary-style list of opts to pass onto dygraphs
 33   */
 34     constructor(labels, values, colours, title, opts) {
 35 
 36         if (typeof window == undefined) throw "Using dygraphs with cashJS only works in browser-mode"
 37         this.labels = labels;
 38         this.data = [values];
 39         this.title = title;
 40         this.num_dps = this.labels.length; // number of data points for this graphs        
 41         this.elem = document.createElement("div");
 42         this.elem.className = "graph-holder";
 43         this.colours = [];
 44 
 45         for (let v in colours) {
 46             if (v == "Time") continue
 47             else if (colours[v] == undefined) this.colours.push("#000000");
 48             else if (colours[v][0] + colours[v][1] + colours[v][2] == 765) this.colours.push("#dddddd");
 49             else this.colours.push(rgbToHex(colours[v][0], colours[v][1], colours[v][2]));
 50         }
 51 
 52         document.body.appendChild(this.elem);
 53         document.getElementById("graph_holder").appendChild(this.elem);
 54         this.g = new Dygraph(this.elem, this.data,
 55             {
 56                 title: this.title,
 57                 showRoller: false,
 58                 ylabel: this.labels.length == 2 ? this.labels[1] : "",
 59                 width: 500,
 60                 height: 200,
 61                 xlabel: this.labels[0],
 62                 drawPoints: opts && opts.drawPoints || false,
 63                 pointSize: opts ? (opts.pointSize ? opts.pointSize : 0) : 0,
 64                 strokePattern: opts ? (opts.strokePattern ? opts.strokePattern : null) : null,
 65                 dateWindow: [0, 100],
 66                 axisLabelFontSize: 10,
 67                 valueRange: [0.000,],
 68                 strokeWidth: opts ? opts.strokeWidth : 3,
 69                 colors: this.colours,
 70                 labels: this.labels
 71             });
 72     }
 73 
 74 
 75     /** Push data to your graph-element
 76     * @param {array} array of floats to be added to the dygraph object (stored in 'data')
 77     */
 78     push_data(data_array) {
 79         this.data.push(data_array);
 80     }
 81 
 82     reset_plot() {
 83         this.data = [this.data[0]];
 84         this.g.updateOptions(
 85             {
 86                 'file': [this.data]
 87             });
 88     }
 89 
 90     /** 
 91      * Update the graph axes   
 92     */
 93     update() {
 94         let max_x = 0;
 95         let min_x = 999999999999;
 96         for (let i of this.data) {
 97             if (i[0] > max_x) max_x = i[0];
 98             if (i[0] < min_x) min_x = i[0];
 99         }
100         this.g.updateOptions(
101             {
102                 'file': this.data,
103                 dateWindow: [min_x, max_x]
104             });
105 
106     }
107 }
108 
109 /* 
110 Functions below are to make sure dygraphs understands the colours used by Cacatoo (converts to hex)
111 */
112 function componentToHex(c) {
113     var hex = c.toString(16);
114     return hex.length == 1 ? "0" + hex : hex;
115 }
116 
117 function rgbToHex(r, g, b) {
118     return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
119 }
120 
121 /**
122 *  The ODE class is used to call the odex.js library and numerically solve ODEs
123 */
124 
125 class ODE {
126     /**
127     *  The constructor function for a @ODE object. 
128     *  @param {function} eq Function that describes the ODE (see examples starting with ode)
129     *  @param {Array} state_vector Initial state vector
130     *  @param {Array} pars Array of parameters for the ODEs 
131     *  @param {Array} diff_rates Array of rates at which each state diffuses to neighbouring grid point (Has to be less than 0.25!)
132     *  @param {String} ode_name Name of this ODE
133     */
134     constructor(eq, state_vector, pars, diff_rates, ode_name, acceptable_error) {
135         this.name = ode_name;
136         this.eq = eq;
137         this.state = state_vector;
138         this.diff_rates = diff_rates;
139         this.pars = pars;
140         this.solver = new Solver(state_vector.length);
141         if (acceptable_error !== undefined) this.solver.absoluteTolerance = this.solver.relativeTolerance = acceptable_error;
142     }
143 
144     /** 
145      *  Numerically solve the ODE
146      *  @param {float} delta_t Step size
147      *  @param {bool} opt_pos When enabled, negative values are set to 0 automatically
148     */
149     solveTimestep(delta_t = 0.1, pos = false) {
150         let newstate = this.solver.solve(
151             this.eq(...this.pars),      // function to solve and its pars (... unlists the array as a list of args)
152             0,                          // Initial x value
153             this.state,                  // Initial y value(s)
154             delta_t                           // Final x value            
155         ).y;
156         if (pos) for (var i = 0; i < newstate.length; i++) if (newstate[i] < 0.000001) newstate[i] = 0.0;
157         this.state = newstate;
158     }
159     /**
160     * Prints the current state to the console
161     */
162     print_state() {
163         console.log(this.state);
164     }
165 }
166 
167 /*
168   I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
169   so it's better encapsulated. Now you can have multiple random number generators
170   and they won't stomp all over eachother's state.
171   
172   If you want to use this as a substitute for Math.random(), use the random()
173   method like so:
174   
175   var m = new MersenneTwister();
176   var randomNumber = m.random();
177   
178   You can also call the other genrand_{foo}() methods on the instance.
179   If you want to use a specific seed in order to get a repeatable random
180   sequence, pass an integer into the constructor:
181   var m = new MersenneTwister(123);
182   and that will always produce the same random sequence.
183   Sean McCullough (banksean@gmail.com)
184 */
185 
186 /* 
187    A C-program for MT19937, with initialization improved 2002/1/26.
188    Coded by Takuji Nishimura and Makoto Matsumoto.
189  
190    Before using, initialize the state by using init_genrand(seed)  
191    or init_by_array(init_key, key_length).
192  
193    Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
194    All rights reserved.                          
195  
196    Redistribution and use in source and binary forms, with or without
197    modification, are permitted provided that the following conditions
198    are met:
199  
200      1. Redistributions of source code must retain the above copyright
201         notice, this list of conditions and the following disclaimer.
202  
203      2. Redistributions in binary form must reproduce the above copyright
204         notice, this list of conditions and the following disclaimer in the
205         documentation and/or other materials provided with the distribution.
206  
207      3. The names of its contributors may not be used to endorse or promote 
208         products derived from this software without specific prior written 
209         permission.
210  
211    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
212    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
213    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
214    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
215    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
216    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
217    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
218    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
219    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
220    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
221    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
222  
223  
224    Any feedback is very welcome.
225    http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
226    email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
227 */
228 
229 function MersenneTwister(seed) {
230     if (seed == undefined) {
231       seed = new Date().getTime();
232     } 
233     /* Period parameters */  
234     this.N = 624;
235     this.M = 397;
236     this.MATRIX_A = 0x9908b0df;   /* constant vector a */
237     this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
238     this.LOWER_MASK = 0x7fffffff; /* least significant r bits */
239    
240     this.mt = new Array(this.N); /* the array for the state vector */
241     this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */
242   
243     this.init_genrand(seed);
244   }  
245    
246   /* initializes mt[N] with a seed */
247   MersenneTwister.prototype.init_genrand = function(s) {
248     this.mt[0] = s >>> 0;
249     for (this.mti=1; this.mti<this.N; this.mti++) {
250         var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
251      this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
252     + this.mti;
253         /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
254         /* In the previous versions, MSBs of the seed affect   */
255         /* only MSBs of the array mt[].                        */
256         /* 2002/01/09 modified by Makoto Matsumoto             */
257         this.mt[this.mti] >>>= 0;
258         /* for >32 bit machines */
259     }
260   };
261    
262   /* initialize by an array with array-length */
263   /* init_key is the array for initializing keys */
264   /* key_length is its length */
265   /* slight change for C++, 2004/2/26 */
266   MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
267     var i, j, k;
268     this.init_genrand(19650218);
269     i=1; j=0;
270     k = (this.N>key_length ? this.N : key_length);
271     for (; k; k--) {
272       var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
273       this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
274         + init_key[j] + j; /* non linear */
275       this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
276       i++; j++;
277       if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
278       if (j>=key_length) j=0;
279     }
280     for (k=this.N-1; k; k--) {
281       var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
282       this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
283         - i; /* non linear */
284       this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
285       i++;
286       if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
287     }
288   
289     this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ 
290   };
291    
292   /* generates a random number on [0,0xffffffff]-interval */
293   MersenneTwister.prototype.genrand_int32 = function() {
294     var y;
295     var mag01 = new Array(0x0, this.MATRIX_A);
296     /* mag01[x] = x * MATRIX_A  for x=0,1 */
297   
298     if (this.mti >= this.N) { /* generate N words at one time */
299       var kk;
300   
301       if (this.mti == this.N+1)   /* if init_genrand() has not been called, */
302         this.init_genrand(5489); /* a default initial seed is used */
303   
304       for (kk=0;kk<this.N-this.M;kk++) {
305         y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
306         this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
307       }
308       for (;kk<this.N-1;kk++) {
309         y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
310         this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
311       }
312       y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
313       this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
314   
315       this.mti = 0;
316     }
317   
318     y = this.mt[this.mti++];
319   
320     /* Tempering */
321     y ^= (y >>> 11);
322     y ^= (y << 7) & 0x9d2c5680;
323     y ^= (y << 15) & 0xefc60000;
324     y ^= (y >>> 18);
325   
326     return y >>> 0;
327   };
328    
329   /* generates a random number on [0,0x7fffffff]-interval */
330   MersenneTwister.prototype.genrand_int31 = function() {
331     return (this.genrand_int32()>>>1);
332   };
333    
334   /* generates a random number on [0,1]-real-interval */
335   MersenneTwister.prototype.genrand_real1 = function() {
336     return this.genrand_int32()*(1.0/4294967295.0); 
337     /* divided by 2^32-1 */ 
338   };
339 
340   /* generates a random int between [min,max] */
341   MersenneTwister.prototype.genrand_int = function(min,max) {
342     return min+Math.floor(this.genrand_real1()*(max));
343   };
344   
345   /* generates a random number on [0,1)-real-interval */
346   MersenneTwister.prototype.random = function() {
347     return this.genrand_int32()*(1.0/4294967296.0); 
348     /* divided by 2^32 */
349   };
350    
351   /* generates a random number on (0,1)-real-interval */
352   MersenneTwister.prototype.genrand_real3 = function() {
353     return (this.genrand_int32() + 0.5)*(1.0/4294967296.0); 
354     /* divided by 2^32 */
355   };
356    
357   /* generates a random number on [0,1) with 53-bit resolution*/
358   MersenneTwister.prototype.genrand_res53 = function() { 
359     var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6; 
360     return (a*67108864.0+b)*(1.0/9007199254740992.0); 
361   };
362 
363 /**
364  *  Gridmodel is the main (currently only) type of model in Cacatoo. Most of these models
365  *  will look and feel like CAs, but GridModels can also contain ODEs with diffusion, making
366  *  them more like PDEs. 
367  */
368 
369 class Gridmodel {
370     /**
371     *  The constructor function for a @Gridmodel object. Takes the same config dictionary as used in @Simulation
372     *  @param {string} name The name of your model. This is how it will be listed in @Simulation 's properties
373     *  @param {dictionary} config A dictionary (object) with all the necessary settings to setup a Cacatoo GridModel. 
374     *  @param {MersenneTwister} rng A random number generator (MersenneTwister object)
375     */
376     constructor(name, config, rng) {
377         this.name = name;
378         this.time = 0;
379         this.grid = MakeGrid(config.ncol, config.nrow);       // Initialises an (empty) grid
380         this.nc = config.ncol || 200;
381         this.nr = config.nrow || 200;
382         this.wrap = config.wrap || [true, true];
383         this.rng = rng;
384         this.statecolours = this.setupColours(config.statecolours); // Makes sure the statecolours in the config dict are parsed (see below)
385         this.lims = {};
386         this.scale = config.scale || 1;
387         this.graph_update = config.graph_update || 20;
388         this.graph_interval = config.graph_interval || 2;
389 
390         this.margolus_phase = 0;
391         // Store a simple array to get neighbours from the N, E, S, W, NW, NE, SW, SE (analogous to Cash2.1)
392         this.moore = [[0, 0],         // SELF   _____________
393         [0, -1],        // NORTH           | 5 | 1 | 6 |
394         [1, 0],         // EAST            | 4 | 0 | 2 |
395         [0, 1],         // SOUTH           | 7 | 3 | 8 |
396         [-1, 0],        // WEST            _____________
397         [-1, -1],       // NW
398         [1, -1],        // NE
399         [-1, 1],        // SW
400         [1, 1]          // SE
401         ];
402 
403         this.graphs = {};                // Object containing all graphs belonging to this model (HTML usage only)
404         this.canvases = {};              // Object containing all Canvases belonging to this model (HTML usage only)
405     }
406 
407     /** Initiate a dictionary with colour arrays [R,G,B] used by Graph and Canvas classes
408     *   @param {statecols} object - given object can be in two forms
409     *                             | either {state:colour} tuple (e.g. 'alive':'white', see gol.html) 
410     *                             | or {state:object} where objects are {val:'colour},
411     *                             | e.g.  {'species':{0:"black", 1:"#DDDDDD", 2:"red"}}, see cheater.html 
412     */
413     setupColours(statecols) {
414         let return_dict = {};
415         if (statecols == null)           // If the user did not define statecols (yet)
416             return return_dict
417         let colours = dict_reverse(statecols) || { 'val': 1 };
418 
419 
420         for (const [statekey, statedict] of Object.entries(colours)) {
421             if (statedict == 'default') {
422                 return_dict[statekey] = default_colours;                                 // Defined below
423             }
424             else if (typeof statedict === 'string' || statedict instanceof String)       // For if 
425             {
426                 return_dict[statekey] = stringToRGB(statedict);
427             }
428             else {
429                 let c = {};
430                 for (const [key, val] of Object.entries(statedict)) {
431                     if (Array.isArray(val)) c[key] = val;
432                     else c[key] = stringToRGB(val);
433                 }
434                 return_dict[statekey] = c;
435             }
436         }
437         return return_dict
438     }
439 
440 
441     /** Initiate a gradient of colours for a property. 
442     * @param {string} property The name of the property to which the colour is assigned
443     * @param {int} n How many colours the gradient consists off
444     * For example usage, see colourViridis below
445     */
446     colourGradient(property, n) {
447         let n_arrays = arguments.length - 2;
448         if (n_arrays <= 1) throw new Error("colourGradient needs at least 2 arrays")
449 
450         let segment_len = n / (n_arrays);
451 
452         let color_dict = this.statecolours[property];
453         let total = 0;
454         if (typeof color_dict != 'undefined')
455             total = Object.keys(this.statecolours[property]).length;
456         else
457             color_dict = {};
458 
459         for (let arr = 0; arr < n_arrays - 1; arr++) {
460             let arr1 = arguments[2 + arr];
461             let arr2 = arguments[2 + arr + 1];
462             /// HIER BEN IK MEE BEZIG!!!
463             for (let i = 0; i < segment_len; i++) {
464                 let r, g, b;
465                 if (arr2[0] > arr1[0]) r = Math.floor(arr1[0] + (arr2[0] - arr1[0]) * (i / (segment_len - 1)));
466                 else r = Math.floor(arr1[0] - (arr1[0] - arr2[0]) * (i / (segment_len - 1)));
467                 if (arr2[1] > arr1[1]) g = Math.floor(arr1[1] + (arr2[1] - arr1[1]) * (i / (segment_len - 1)));
468                 else g = Math.floor(arr1[0] - (arr1[1] - arr2[1]) * (i / (segment_len - 1)));
469                 if (arr2[2] > arr1[2]) b = Math.floor(arr1[2] + (arr2[2] - arr1[2]) * (i / (segment_len - 1)));
470                 else b = Math.floor(arr1[2] - (arr1[2] - arr2[2]) * (i / (segment_len - 1)));
471 
472                 color_dict[Math.floor(i + arr * segment_len + total) + 1] = [r, g, b];
473             }
474             // total += segment_len
475         }
476         this.statecolours[property] = color_dict;
477     }
478 
479     /** Initiate a gradient of colours for a property, using the Viridis colour scheme (purpleblue-ish to green to yellow) 
480     * @param {string} property The name of the property to which the colour is assigned
481     * @param {int} n How many colours the gradient consists off
482     * @param {bool} rev Reverse the viridis colour gradient
483     */
484     colourViridis(property, n, rev = false) {
485         if (!rev) this.colourGradient(property, n, [68, 1, 84], [59, 82, 139], [33, 144, 140], [93, 201, 99], [253, 231, 37]);         // Viridis
486         else this.colourGradient(property, n, [253, 231, 37], [93, 201, 99], [33, 144, 140], [59, 82, 139], [68, 1, 84]);             // Viridis
487     }
488 
489     /** Print the entire grid to the console */
490     printgrid() {
491         console.table(this.grid);
492     }
493 
494     /** The most important function in GridModel: how to determine the next state of a gridpoint?
495      * By default, nextState is empty. It should be defined by the user (see examples)
496     * @param {int} i Position of grid point to update (column)
497     * @param {int} j Position of grid point to update (row)
498     */
499     nextState(i, j) {
500         throw 'Nextstate function of \'' + this.name + '\' undefined';
501     }
502 
503     /** Synchronously apply the nextState function (defined by user) to the entire grid
504      *  Synchronous means that all grid points will be updated simultaneously. This is ensured
505      *  by making a back-up grid, which will serve as a reference to know the state in the previous
506      *  time step. First all grid points are updated based on the back-up. Only then will the 
507      *  actual grid be changed. 
508     */
509     synchronous()                                               // Do one step (synchronous) of this grid
510     {
511         let oldstate = MakeGrid(this.nc, this.nr, this.grid);     // Old state based on current grid
512         let newstate = MakeGrid(this.nc, this.nr);               // New state == empty grid
513 
514         for (let i = 0; i < this.nc; i++) {
515             for (let j = 0; j < this.nr; j++) {
516                 this.nextState(i, j);                             // Update this.grid
517                 newstate[i][j] = this.grid[i][j];                // Set this.grid to newstate
518                 this.grid[i][j] = oldstate[i][j];                // Reset this.grid to old state
519             }
520         }
521         this.grid = newstate;
522         this.time++;
523     }
524 
525     /** Like the synchronous function above, but can not take a custom user-defined function rather
526      *  than the default next-state function. Technically one should be able to refarctor this by making
527      *  the default function of synchronous "nextstate". But this works. :)
528     */
529     apply_sync(func) {
530         let oldstate = MakeGrid(this.nc, this.nr, this.grid);     // Old state based on current grid
531         let newstate = MakeGrid(this.nc, this.nr);               // New state == empty grid
532         for (let i = 0; i < this.nc; i++) {
533             for (let j = 0; j < this.nr; j++) {
534                 func(i, j);                           // Update this.grid
535                 newstate[i][j] = this.grid[i][j];                // Set this.grid to newstate
536                 this.grid[i][j] = oldstate[i][j];                // Reset this.grid to old state
537             }
538         }
539         this.grid = newstate;
540     }
541 
542     /** Asynchronously apply the nextState function (defined by user) to the entire grid
543      *  Asynchronous means that all grid points will be updated in a random order. For this
544      *  first the update_order will be determined (this.set_update_order). Afterwards, the nextState
545      *  will be applied in that order. This means that some cells may update while all their neighours 
546      *  are still un-updated, and other cells will update while all their neighbours are already done. 
547     */
548     asynchronous() {
549         this.set_update_order();
550         for (let n = 0; n < this.nc * this.nr; n++) {
551             let m = this.upd_order[n];
552             let i = m % this.nc;
553             let j = Math.floor(m / this.nc);
554             this.nextState(i, j);
555         }
556         this.time++;
557         // Don't have to copy the grid here. Just cycle through i,j in random order and apply nextState :)
558     }
559 
560     /** Analogous to apply_sync(func), but asynchronous */
561     apply_async(func) {
562         this.set_update_order();
563         for (let n = 0; n < this.nc * this.nr; n++) {
564             let m = this.upd_order[n];
565             let i = m % this.nc;
566             let j = Math.floor(m / this.nc);
567             func(i, j);
568         }
569     }
570 
571     /** If called for the first time, make an update order (list of ints), otherwise just shuffle it. */
572     set_update_order() {
573         if (typeof this.upd_order === 'undefined')  // "Static" variable, only create this array once and reuse it
574         {
575             this.upd_order = [];
576             for (let n = 0; n < this.nc * this.nr; n++) {
577                 this.upd_order.push(n);
578             }
579         }
580         shuffle(this.upd_order, this.rng);            // Shuffle the update order
581     }
582 
583     /** The update is, like nextState, user-defined (hence, empty by default).
584      *  It should contains all functions that one wants to apply every time step
585      *  (e.g. grid manipulations and printing statistics) 
586      *  For example, and update function could look like:
587      *  this.synchronous()         // Update all cells  
588      *  this.MargolusDiffusion()   // Apply Toffoli Margolus diffusion algorithm
589      *  this.plotPopsizes('species',[1,2,3]) // Plot the population sizes
590         */
591     update() {
592         throw 'Update function of \'' + this.name + '\' undefined';
593     }
594 
595     /** Get the gridpoint at coordinates i,j 
596      *  Makes sure wrapping is applied if necessary
597      *  @param {int} i position (column) for the focal gridpoint
598      *  @param {int} j position (row) for the focal gridpoint
599      */
600      getGridpoint(i, j) {
601         let x = i;
602         if (this.wrap[0]) x = (i + this.nc) % this.nc;                         // Wraps neighbours left-to-right
603         let y = j;
604         if (this.wrap[1]) y = (j + this.nr) % this.nr;                         // Wraps neighbours top-to-bottom
605         if (x < 0 || y < 0 || x >= this.nc || y >= this.nr) return undefined                      // If sampling neighbour outside of the grid, return empty object
606         else return this.grid[x][y]
607     }
608 
609     /** Change the gridpoint at position i,j into gp
610          *  Makes sure wrapping is applied if necessary
611          *  @param {int} i position (column) for the focal gridpoint
612          *  @param {int} j position (row) for the focal gridpoint
613          *  @param {Gridpoint} @Gridpoint object to set the gp to
614     */
615     setGridpoint(i, j, gp) {
616         let x = i;
617         if (this.wrap[0]) x = (i + this.nc) % this.nc;                         // Wraps neighbours left-to-right
618         let y = j;
619         if (this.wrap[1]) y = (j + this.nr) % this.nr;                         // Wraps neighbours top-to-bottom
620            
621         if (x < 0 || y < 0 || x >= this.nc || y >= this.nr) this.grid[x][y] = undefined;    // TODO!!!!!!!! Return border-state instead!
622         else this.grid[x][y] = gp;
623     }
624 
625     /** Get the x,y coordinates of a neighbour in an array. 
626      *  Makes sure wrapping is applied if necessary
627      */
628     getNeighXY(i, j) {
629         let x = i;
630         if (this.wrap[0]) x = (i + this.nc) % this.nc;                         // Wraps neighbours left-to-right
631         let y = j;
632         if (this.wrap[1]) y = (j + this.nr) % this.nr;                         // Wraps neighbours top-to-bottom
633         if (x < 0 || y < 0 || x >= this.nc || y >= this.nr) return undefined                      // If sampling neighbour outside of the grid, return empty object
634         else return [x, y]
635     }
636 
637     /** Get array of grid points with val in property (Neu4, Neu5, Moore8, Moore9 depending on range-array)
638     *  @param {GridModel} grid The gridmodel used to check neighbours. Usually the gridmodel itself (i.e., this), 
639     *  but can be mixed to make grids interact.
640     *  @param {int} col position (column) for the focal gridpoint
641     *  @param {int} row position (row) for the focal gridpoint
642     *  @param {string} property the property that is counted
643     *  @param {int} val value 'property' should have
644     *  @param {Array} range which section of the neighbourhood must be counted? (see this.moore, e.g. 1-8 is Moore8, 0-4 is Neu5,etc)
645     *  @return {int} The number of grid points with "property" set to "val"
646     *  Below, 4 version of this functions are overloaded (Moore8, Moore9, Neumann4, etc.)
647     *  If one wants to count all the "cheater" surrounding a gridpoint in cheater.js in the Moore8 neighbourhood
648     *  one needs to look for value '3' in the property 'species':
649     *  this.getNeighbours(this,10,10,3,'species',[1-8]);  
650     *  or 
651     *  this.getMoore8(this,10,10,3,'species')
652     */
653     getNeighbours(model,col,row,property,val,range) {
654         let gps = [];
655         for (let n = range[0]; n <= range[1]; n++) {                        
656             let i = model.moore[n][0];
657             let j = model.moore[n][1];
658             let neigh = model.getGridpoint(col + i, row + j);
659             if (neigh != undefined && neigh[property] == val)
660                     gps.push(neigh);
661         }
662         return gps;
663     }
664 
665     /** getNeighbours for the Moore8 neighbourhood (range 1-8 in function getNeighbours) */     
666     getMoore8(model, col, row, property,val) { return this.getNeighbours(model,col,row,property,val,[1,8]) }
667     /** getNeighbours for the Moore8 neighbourhood (range 1-8 in function getNeighbours) */     
668     getMoore9(model, col, row, property,val) { return this.getNeighbours(model,col,row,property,val,[0,8]) }
669     /** getNeighbours for the Moore8 neighbourhood (range 1-8 in function getNeighbours) */     
670     getNeumann4(model, col, row, property,val) { return this.getNeighbours(model,col,row,property,val,[1,4]) }
671     /** getNeighbours for the Moore8 neighbourhood (range 1-8 in function getNeighbours) */     
672     getNeumann5(model, col, row, property,val) { return this.getNeighbours(model,col,row,property,val,[0,4]) }    
673 
674     /** From a list of grid points, e.g. from getNeighbours(), sample one weighted by a property. This is analogous
675      *  to spinning a "roulette wheel". Also see a hard-coded versino of this in the "cheater" example
676      *  @param {Array} gps Array of gps to sample from (e.g. living individuals in neighbourhood)
677      *  @param {string} property The property used to weigh gps (e.g. fitness)
678      *  @param {float} non Scales the probability of not returning any gp. 
679      */
680     rouletteWheel(gps, property, non = 0.0) {
681         let sum_property = non;
682         for (let i = 0; i < gps.length; i++) sum_property += gps[i][property];       // Now we have the sum of weight + a constant (non)
683         let randomnr = this.rng.genrand_real1() * sum_property;                // Sample a randomnr between 0 and sum_property        
684         let cumsum = 0.0;                                                    // This will keep track of the cumulative sum of weights
685         for (let i = 0; i < gps.length; i++) {
686             cumsum += gps[i][property];
687             if (randomnr < cumsum) return gps[i]
688         }
689         return
690     }
691 
692     /** Sum the properties of grid points in the neighbourhood (Neu4, Neu5, Moore8, Moore9 depending on range-array)
693     *  @param {GridModel} grid The gridmodel used to check neighbours. Usually the gridmodel itself (i.e., this), 
694     *  but can be mixed to make grids interact.
695     *  @param {int} col position (column) for the focal gridpoint
696     *  @param {int} row position (row) for the focal gridpoint
697     *  @param {string} property the property that is counted
698     *  @param {Array} range which section of the neighbourhood must be counted? (see this.moore, e.g. 1-8 is Moore8, 0-4 is Neu5,etc)
699     *  @return {int} The number of grid points with "property" set to "val"
700     *  Below, 4 version of this functions are overloaded (Moore8, Moore9, Neumann4, etc.)
701     *  For example, if one wants to sum all the "fitness" surrounding a gridpoint in the Neumann neighbourhood, use
702     *  this.sumNeighbours(this,10,10,'fitness',[1-4]);  
703     *  or
704     *  this.sumNeumann4(this,10,10,'fitness')
705     */
706      sumNeighbours(model, col, row, property, range) {
707         let count = 0;
708         for (let n = range[0]; n <= range[1]; n++) {
709             let i = model.moore[n][0];
710             let j = model.moore[n][1];
711             let gp = model.getGridpoint(col + i, row + j);
712             if(gp != undefined)     count += model.getGridpoint(col + i, row + j)[property];
713         }
714         return count;
715     }
716 
717     /** sumNeighbours for range 1-8 (see sumNeighbours) */     
718     sumMoore8(grid, col, row, property) { return this.sumNeighbours(grid, col, row, property, [1,8]) }
719     /** sumNeighbours for range 0-8 (see sumNeighbours) */ 
720     sumMoore9(grid, col, row, property) { return this.sumNeighbours(grid, col, row, property, [0,8]) }
721     /** sumNeighbours for range 1-4 (see sumNeighbours) */ 
722     sumNeumann4(grid, col, row, property) { return this.sumNeighbours(grid, col, row, property, [1,4]) }
723     /** sumNeighbours for range 0-4 (see sumNeighbours) */ 
724     sumNeumann5(grid, col, row, property) { return this.sumNeighbours(grid, col, row, property, [0,4]) }
725 
726 
727     /** Count the number of neighbours with 'val' in 'property' (Neu4, Neu5, Moore8, Moore9 depending on range-array)
728     *  @param {GridModel} grid The gridmodel used to check neighbours. Usually the gridmodel itself (i.e., this), 
729     *  but can be mixed to make grids interact.
730     *  @param {int} col position (column) for the focal gridpoint
731     *  @param {int} row position (row) for the focal gridpoint
732     *  @param {string} property the property that is counted
733     *  @param {int} val value property must have to be counted
734     *  @param {Array} range which section of the neighbourhood must be counted? (see this.moore, e.g. 1-8 is Moore8, 0-4 is Neu5,etc)
735     *  @return {int} The number of grid points with "property" set to "val"
736     *  Below, 4 version of this functions are overloaded (Moore8, Moore9, Neumann4, etc.)
737     *  For example, if one wants to count all the "alive" individuals in the Moore 9 neighbourhood, use
738     *  this.countNeighbours(this,10,10,1,'alive',[0-8]);  
739     *  or
740     *  this.countMoore9(this,10,10,1,'alive');  
741     */
742     countNeighbours(model, col, row, property, val, range) {
743         let count = 0;
744         for (let n = range[0]; n <= range[1]; n++) {            
745             let i = model.moore[n][0];
746             let j = model.moore[n][1];
747             if (model.getGridpoint(col + i, row + j)[property]==val) count++;
748         }
749         return count;
750     }
751 
752     /** sumNeighbours for range 1-8 (see sumNeighbours) */     
753     countMoore8(model, col, row, property, val) { return this.countNeighbours(model, col, row, property, val, [1,8]) }
754     /** sumNeighbours for range 0-8 (see sumNeighbours) */ 
755     countMoore9(model, col, row, property, val) { return this.countNeighbours(model, col, row, property, val, [0,8]) }
756     /** sumNeighbours for range 1-4 (see sumNeighbours) */ 
757     countNeumann4(model, col, row, property, val) { return this.countNeighbours(model, col, row, property, val, [1,4]) }
758     /** sumNeighbours for range 0-4 (see sumNeighbours) */ 
759     countNeumann5(model, col, row, property, val) { return this.countNeighbours(model, col, row, property, val, [0,4]) }
760 
761 
762     /** Return a random neighbour from the neighbourhood defined by range array
763      *  @param {GridModel} grid The gridmodel used to check neighbours. Usually the gridmodel itself (i.e., this), 
764      *  but can be mixed to make grids interact.
765      *  @param {int} col position (column) for the focal gridpoint
766      *  @param {int} row position (row) for the focal gridpoint
767      *  @param {Array} range from which to sample (1-8 is Moore8, 0-4 is Neu5, etc.)
768      */
769     randomMoore(grid, col, row,range) {
770         let rand = this.rng.genrand_int(range[0], range[1]);
771         let i = this.moore[rand][0];
772         let j = this.moore[rand][1];
773         let neigh = grid.getGridpoint(col + i, row + j);
774         while (neigh == undefined) neigh = this.randomMoore(grid, col, row,range);
775         return neigh
776     }
777 
778     /** sumNeighbours for range 1-8 (see sumNeighbours) */     
779     randomMoore8(model, col, row) { return this.randomMoore(model, col, row, [1,8]) }
780     /** sumNeighbours for range 0-8 (see sumNeighbours) */ 
781     randomMoore9(model, col, row) { return this.randomMoore(model, col, row, [0,8]) }
782     /** sumNeighbours for range 1-4 (see sumNeighbours) */ 
783     randomNeu4(model, col, row) { return this.randomMoore(model, col, row, [1,4]) }
784     /** sumNeighbours for range 0-4 (see sumNeighbours) */ 
785     randomNeu5(model, col, row) { return this.randomMoore(model, col, row, [0,4]) }
786     
787 
788     /** Diffuse ODE states on the grid. Because ODEs are stored by reference inside gridpoint, the 
789      *  states of the ODEs have to be first stored (copied) into a 4D array (x,y,ODE,state-vector), 
790      *  which is then used to update the grid.
791      */
792     diffuseOdeStates() {
793         let newstates_2 = CopyGridODEs(this.nc, this.nr, this.grid);    // Generates a 4D array of [i][j][o][s] (i-coord,j-coord,relevant ode,state-vector)    
794 
795         for (let i = 0; i < this.nc; i += 1) // every column
796         {
797             for (let j = 0; j < this.nr; j += 1) // every row
798             {
799                 for (let o = 0; o < this.grid[i][j].ODEs.length; o++) // every ode
800                 {
801                     for (let s = 0; s < this.grid[i][j].ODEs[o].state.length; s++) // every state
802                     {
803                         let rate = this.grid[i][j].ODEs[o].diff_rates[s];
804                         let sum_in = 0.0;
805                         for (let n = 1; n <= 4; n++)   // Every neighbour (neumann)
806                         {
807                             let moore = this.moore[n];
808                             let xy = this.getNeighXY(i + moore[0], j + moore[1]);
809                             if (typeof xy == "undefined") continue
810                             let neigh = this.grid[xy[0]][xy[1]];
811                             sum_in += neigh.ODEs[o].state[s] * rate;
812                             newstates_2[xy[0]][xy[1]][o][s] -= neigh.ODEs[o].state[s] * rate;
813                         }
814                         newstates_2[i][j][o][s] += sum_in;
815                     }
816                 }
817             }
818         }
819 
820         for (let i = 0; i < this.nc; i += 1) // every column
821             for (let j = 0; j < this.nr; j += 1) // every row
822                 for (let o = 0; o < this.grid[i][j].ODEs.length; o++)
823                     for (let s = 0; s < this.grid[i][j].ODEs[o].state.length; s++)
824                         this.grid[i][j].ODEs[o].state[s] = newstates_2[i][j][o][s];
825 
826     }
827 
828     /** Assign each gridpoint a new random position on the grid. This simulated mixing,
829      *  but does not guarantee a "well-mixed" system per se (interactions are still)
830      *  calculated based on neighbourhoods. 
831      */
832     perfectMix() {
833         let all_gridpoints = [];
834         for (let i = 0; i < this.nc; i++)
835             for (let j = 0; j < this.nr; j++)
836                 all_gridpoints.push(this.getGridpoint(i, j));
837 
838         all_gridpoints = shuffle(all_gridpoints, this.rng);
839 
840         for (let i = 0; i < all_gridpoints.length; i++)
841             this.setGridpoint(i % this.nc, Math.floor(i / this.nc), all_gridpoints[i]);
842         return "Perfectly mixed the grid"
843     }
844 
845     /** Apply diffusion algorithm for grid-based models described in Toffoli & Margolus' book "Cellular automata machines"
846      *  The idea is to subdivide the grid into 2x2 neighbourhoods, and rotate them (randomly CW or CCW). To avoid particles
847      *  simply being stuck in their own 2x2 subspace, different 2x2 subspaces are taken each iteration (CW in even iterations,
848      *  CCW in odd iterations)
849     */
850     MargolusDiffusion() {        
851         //   
852         //   A  B
853         //   D  C
854         //   a = backup of A 
855         //   rotate cw or ccw randomly
856         let even = this.margolus_phase % 2 == 0;
857         if ((this.nc % 2 + this.nr % 2) > 0) throw "Do not use margolusDiffusion with an uneven number of cols / rows!"
858 
859         for (let i = 0 + even; i < this.nc; i += 2) {
860             if(i> this.nc-2) continue
861             for (let j = 0 + even; j < this.nr; j += 2) {
862                 if(j> this.nr-2) continue
863                 // console.log(i,j)
864                 let old_A = new Gridpoint(this.grid[i][j]);
865                 let A = this.getGridpoint(i, j);
866                 let B = this.getGridpoint(i + 1, j);
867                 let C = this.getGridpoint(i + 1, j + 1);
868                 let D = this.getGridpoint(i, j + 1);
869 
870                 if (this.rng.random() < 0.5)             // CW = clockwise rotation
871                 {
872                     A = D;
873                     D = C;
874                     C = B;
875                     B = old_A;
876                 }
877                 else {
878                     A = B;                               // CCW = counter clockwise rotation      
879                     B = C;
880                     C = D;
881                     D = old_A;
882                 }
883                 this.setGridpoint(i, j, A);
884                 this.setGridpoint(i + 1, j, B);
885                 this.setGridpoint(i + 1, j + 1, C);
886                 this.setGridpoint(i, j + 1, D);
887             }
888         }
889         this.margolus_phase++;
890     }
891 
892     /** 
893      * Adds a dygraph-plot to your DOM (if the DOM is loaded)
894      *  @param {Array} graph_labels Array of strings for the graph legend
895      *  @param {Array} graph_values Array of floats to plot (here plotted over time)
896      *  @param {Array} cols Array of colours to use for plotting
897      *  @param {String} title Title of the plot
898      *  @param {Object} opts dictionary-style list of opts to pass onto dygraphs
899     */
900     plotArray(graph_labels, graph_values, cols, title, opts) {
901         if (typeof window == 'undefined') return
902         if (!(title in this.graphs)) {
903             cols = parseColours(cols);
904             graph_values.unshift(this.time);
905             graph_labels.unshift("Time");
906             this.graphs[title] = new Graph(graph_labels, graph_values, cols, title, opts);
907         }
908         else {
909             if (this.time % this.graph_interval == 0) {
910                 graph_values.unshift(this.time);
911                 graph_labels.unshift("Time");
912                 this.graphs[title].push_data(graph_values);
913             }
914             if (this.time % this.graph_update == 0) {
915                 this.graphs[title].update();
916             }
917         }
918     }
919 
920     /** 
921      * Adds a dygraph-plot to your DOM (if the DOM is loaded)
922      *  @param {Array} graph_labels Array of strings for the graph legend
923      *  @param {Array} graph_values Array of 2 floats to plot (first value for x-axis, second value for y-axis)
924      *  @param {Array} cols Array of colours to use for plotting
925      *  @param {String} title Title of the plot
926      *  @param {Object} opts dictionary-style list of opts to pass onto dygraphs
927     */
928     plotXY(graph_labels, graph_values, cols, title, opts) {
929         if (typeof window == 'undefined') return
930         if (!(title in this.graphs)) {
931             cols = parseColours(cols);
932             this.graphs[title] = new Graph(graph_labels, graph_values, cols, title, opts);
933         }
934         else {
935             if (this.time % this.graph_interval == 0) {
936                 this.graphs[title].push_data(graph_values);
937             }
938             if (this.time % this.graph_update == 0) {
939                 this.graphs[title].update();
940             }
941         }
942 
943     }
944 
945     /** 
946      * Easy function to add a pop-sizes plot (wrapper for plotArrays)
947      *  @param {String} property What property to plot (needs to exist in your model, e.g. "species" or "alive")
948      *  @param {Array} values Which values are plotted (e.g. [1,3,4,6])     
949     */
950     plotPopsizes(property, values) {
951         if (typeof window == 'undefined') return
952         if (this.time % this.graph_interval != 0 && this.graphs[`Population sizes (${this.name})`] !== undefined) return
953         // Wrapper for plotXY function, which expects labels, values, colours, and a title for the plot:
954         // Labels
955         let graph_labels = [];
956         for (let val of values) { graph_labels.push(property + '_' + val); }
957 
958         // Values
959         let popsizes = this.getPopsizes(property, values);
960         //popsizes.unshift(this.time)
961         let graph_values = popsizes;
962 
963         // Colours
964         let colours = [];
965 
966         for (let c of values) {
967             //console.log(this.statecolours[property][c])
968             if (this.statecolours[property].constructor != Object)
969                 colours.push(this.statecolours[property]);
970             else
971                 colours.push(this.statecolours[property][c]);
972         }
973         // Title
974         let title = "Population sizes (" + this.name + ")";
975 
976         this.plotArray(graph_labels, graph_values, colours, title);
977 
978 
979 
980         //this.graph = new Graph(graph_labels,graph_values,colours,"Population sizes ("+this.name+")")                            
981     }
982 
983     /** 
984      * Easy function to add a ODE states (wrapper for plot array)
985      *  @param {String} ODE name Which ODE to plot the states for
986      *  @param {Array} values Which states are plotted (if undefined, all of them are plotted)
987     */
988     plotODEstates(odename, values, colours) {
989         if (typeof window == 'undefined') return
990         if (this.time % this.graph_interval != 0 && this.graphs[`Average ODE states (${this.name})`] !== undefined) return
991         // Labels
992         let graph_labels = [];
993         for (let val of values) { graph_labels.push(odename + '_' + val); }
994         // Values
995         let ode_states = this.getODEstates(odename, values);
996         // Title
997         let title = "Average ODE states (" + this.name + ")";
998         this.plotArray(graph_labels, ode_states, colours, title);
999     }
1000 
1001     resetPlots() {
1002         this.time = 0;
1003         for (let g in this.graphs) {
1004             this.graphs[g].reset_plot();
1005         }
1006     }
1007     /** 
1008      *  Returns an array with the population sizes of different types
1009      *  @param {String} property Return popsizes for this property (needs to exist in your model, e.g. "species" or "alive")
1010      *  @param {Array} values Which values are counted and returned (e.g. [1,3,4,6])     
1011     */
1012     getPopsizes(property, values) {
1013         let sum = Array(values.length).fill(0);
1014         for (let i = 0; i < this.nc; i++) {
1015             for (let j = 0; j < this.nr; j++) {
1016                 for (let val in values)
1017                     if (this.grid[i][j][property] == values[val]) sum[val]++;
1018             }
1019         }
1020         return sum;
1021     }
1022 
1023     /** 
1024      *  Returns an array with the population sizes of different types
1025      *  @param {String} property Return popsizes for this property (needs to exist in your model, e.g. "species" or "alive")
1026      *  @param {Array} values Which values are counted and returned (e.g. [1,3,4,6])     
1027     */
1028     getODEstates(odename, values) {
1029         let sum = Array(values.length).fill(0);
1030         for (let i = 0; i < this.nc; i++)
1031             for (let j = 0; j < this.nr; j++)
1032                 for (let val in values)
1033                     sum[val] += this.grid[i][j][odename].state[val] / (this.nc * this.nr);
1034         return sum;
1035     }
1036 
1037 
1038 
1039     /** 
1040      *  Attaches an ODE to all GPs in the model. Each gridpoint has it's own ODE.
1041      *  @param {function} eq Function that describes the ODEs, see examples starting with "ode"
1042      *  @param {Object} conf dictionary style configuration of your ODEs (initial state, parameters, etc.)
1043     */
1044     attachODE(eq, conf) {
1045         for (let i = 0; i < this.nc; i++) {
1046             for (let j = 0; j < this.nr; j++) {
1047                 let ode = new ODE(eq, conf.init_states, conf.parameters, conf.diffusion_rates, conf.ode_name, conf.acceptable_error);
1048                 if (typeof this.grid[i][j].ODEs == "undefined") this.grid[i][j].ODEs = [];   // If list doesnt exist yet                
1049                 this.grid[i][j].ODEs.push(ode);
1050                 if (conf.ode_name) this.grid[i][j][conf.ode_name] = ode;
1051             }
1052         }
1053     }
1054 
1055     /** 
1056      *  Numerically solve the ODEs for each grid point
1057      *  @param {float} delta_t Step size
1058      *  @param {bool} opt_pos When enabled, negative values are set to 0 automatically
1059     */
1060     solve_all_odes(delta_t = 0.1, opt_pos = false) {
1061         for (let i = 0; i < this.nc; i++) {
1062             for (let j = 0; j < this.nr; j++) {
1063                 for (let ode of this.grid[i][j].ODEs) {
1064                     ode.solveTimestep(delta_t, opt_pos);
1065                 }
1066             }
1067         }
1068     }
1069 
1070     /** 
1071      *  Print the entire grid to the console. Not always recommended, but useful for debugging :D
1072      *  @param {float} property What property is printed
1073      *  @param {float} fract Subset to be printed (from the top-left)
1074     */
1075     printGrid(property, fract) {
1076         let ncol = this.nc;
1077         let nrow = this.nr;
1078 
1079         if (fract != undefined) ncol *= fract, nrow *= fract;
1080         let grid = new Array(nrow);             // Makes a column or <rows> long --> grid[cols]
1081         for (let i = 0; i < ncol; i++)
1082             grid[i] = new Array(ncol);          // Insert a row of <cols> long   --> grid[cols][rows]
1083         for (let j = 0; j < nrow; j++)
1084             grid[i][j] = this.grid[i][j][property];
1085 
1086         console.table(grid);
1087     }
1088 }
1089 
1090 ////////////////////////////////////////////////////////////////////////////////////////////////////
1091 //  The functions below are not methods of grid-model as they are never unique for a particular model. 
1092 ////////////////////////////////////////////////////////////////////////////////////////////////////
1093 
1094 /** 
1095  *  Make a grid, or when a template is given, a COPY of a grid. 
1096  *  @param {int} cols Width of the new grid
1097  *  @param {int} rows Height of the new grid
1098  *  @param {2DArray} template Template to be used for copying (if not set, a new empty grid is made)
1099 */
1100 function MakeGrid(cols, rows, template) {
1101     let grid = new Array(rows);             // Makes a column or <rows> long --> grid[cols]
1102     for (let i = 0; i < cols; i++) {
1103         grid[i] = new Array(cols);          // Insert a row of <cols> long   --> grid[cols][rows]
1104         for (let j = 0; j < rows; j++) {
1105             if (template) grid[i][j] = new Gridpoint(template[i][j]);  // Make a deep or shallow copy of the GP 
1106             else grid[i][j] = new Gridpoint();
1107         }
1108     }
1109 
1110     return grid;
1111 }
1112 
1113 /** 
1114  *  Make a back-up of all the ODE states (for synchronous ODE updating)
1115  *  @param {int} cols Width of the grid
1116  *  @param {int} rows Height of the grid
1117  *  @param {2DArray} template Get ODE states from here
1118 */
1119 function CopyGridODEs(cols, rows, template) {
1120     let grid = new Array(rows);             // Makes a column or <rows> long --> grid[cols]
1121     for (let i = 0; i < cols; i++) {
1122         grid[i] = new Array(cols);          // Insert a row of <cols> long   --> grid[cols][rows]
1123         for (let j = 0; j < rows; j++) {
1124             for (let o = 0; o < template[i][j].ODEs.length; o++) // every ode
1125             {
1126                 grid[i][j] = [];
1127                 let states = [];
1128                 for (let s = 0; s < template[i][j].ODEs[o].state.length; s++) // every state
1129                     states.push(template[i][j].ODEs[o].state[s]);
1130                 grid[i][j][o] = states;
1131             }
1132         }
1133     }
1134 
1135     return grid;
1136 }
1137 
1138 /** 
1139  *  Reverse dictionary 
1140  *  @param {Object} obj dictionary-style object to reverse in order 
1141 */
1142 function dict_reverse(obj) {
1143     let new_obj = {};
1144     let rev_obj = Object.keys(obj).reverse();
1145     rev_obj.forEach(function (i) {
1146         new_obj[i] = obj[i];
1147     });
1148     return new_obj;
1149 }
1150 
1151 /** 
1152  *  Randomly shuffle an array with custom RNG
1153  *  @param {Array} array array to be shuffled
1154  *  @param {MersenneTwister} rng MersenneTwister RNG
1155 */
1156 function shuffle(array, rng) {
1157     let i = array.length;
1158     while (i--) {
1159         const ri = Math.floor(rng.random() * (i + 1));
1160         [array[i], array[ri]] = [array[ri], array[i]];
1161     }
1162     return array;
1163 }
1164 
1165 /** 
1166  *  Convert colour string to RGB. Works for colour names ('red','blue' or other colours defined in cacatoo), but also for hexadecimal strings
1167  *  @param {String} string string to convert to RGB
1168 */
1169 function stringToRGB(string) {
1170     if (string[0] != '#') return nameToRGB(string)
1171     else return hexToRGB(string)
1172 }
1173 
1174 /** 
1175  *  Convert hexadecimal to RGB
1176  *  @param {String} hex string to convert to RGB
1177 */
1178 function hexToRGB(hex) {
1179     var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1180     return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
1181 }
1182 
1183 /** 
1184  *  Convert colour name to RGB
1185  *  @param {String} name string to look up in the set of known colours (see below)
1186 */
1187 function nameToRGB(string) {
1188     let colours = {
1189         'black': [0, 0, 0],
1190         'white': [255, 255, 255],
1191         'red': [255, 0, 0],
1192         'blue': [0, 0, 255],
1193         'green': [0, 255, 0],
1194         'darkgrey': [40, 40, 40],
1195         'lightgrey': [180, 180, 180],
1196         'violet': [148, 0, 211],
1197         'turquoise': [64, 224, 208],
1198         'orange': [255, 165, 0],
1199         'gold': [240, 200, 0],
1200         'grey': [125, 125, 125],
1201         'yellow': [255, 255, 0],
1202         'cyan': [0, 255, 255],
1203         'aqua': [0, 255, 255],
1204         'silver': [192, 192, 192],
1205         'nearwhite': [192, 192, 192],
1206         'purple': [128, 0, 128],
1207         'darkgreen': [0, 128, 0],
1208         'olive': [128, 128, 0],
1209         'teal': [0, 128, 128],
1210         'navy': [0, 0, 128]
1211 
1212     };
1213     let c = colours[string];
1214     if (c == undefined) throw new Error(`Cacatoo has no colour with name '${string}'`)
1215     return c
1216 }
1217 
1218 /** 
1219  *  Make sure all colours, even when of different types, are stored in the same format (RGB, as cacatoo uses internally)
1220  *  @param {Array} cols array of strings, or [R,G,B]-arrays. Only strings are converted, other returned. 
1221 */
1222 
1223 function parseColours(cols) {
1224     let return_cols = [];
1225     for (let c of cols) {
1226         if (typeof c === 'string' || c instanceof String) {
1227             return_cols.push(stringToRGB(c));
1228         }
1229         else {
1230             return_cols.push(c);
1231         }
1232     }
1233     return return_cols
1234 }
1235 
1236 /** 
1237  *  A list of default colours if nothing is given by the user. 
1238 */
1239 let default_colours = {
1240     0: [0, 0, 0],            // black
1241     1: [255, 255, 255],      // white
1242     2: [255, 0, 0],          // red
1243     3: [0, 0, 255],          // blue
1244     4: [0, 255, 0],          //green      
1245     5: [60, 60, 60],         //darkgrey    
1246     6: [180, 180, 180],      //lightgrey   
1247     7: [148, 0, 211],      //violet      
1248     8: [64, 224, 208],     //turquoise   
1249     9: [255, 165, 0],      //orange       
1250     10: [240, 200, 0],       //gold       
1251     11: [125, 125, 125],
1252     12: [255, 255, 0], // yellow
1253     13: [0, 255, 255], // cyan
1254     14: [192, 192, 192], // silver
1255     15: [0, 128, 0], //darkgreen
1256     16: [128, 128, 0], // olive
1257     17: [0, 128, 128], // teal
1258     18: [0, 0, 128]
1259 }; // navy
1260 
1261 /**
1262  *  Canvas is a wrapper-class for a HTML-canvas element. It is linked to a @Gridmodel object, and stores what from that @Gridmodel should be displayed (width, height, property, scale, etc.)
1263  */
1264 
1265 class Canvas {
1266     /**
1267     *  The constructor function for a @Canvas object. 
1268     *  @param {Gridmodel} gridmodel The gridmodel to which this canvas belongs
1269     *  @param {string} property the property that should be shown on the canvas
1270     *  @param {int} height height of the canvas (in rows)
1271     *  @param {int} width width of the canvas (in cols)
1272     *  @param {scale} scale of the canvas (width/height of each gridpoint in pixels)
1273     */
1274     constructor(gridmodel, prop, lab, height, width, scale) {
1275         this.label = lab;
1276         this.gridmodel = gridmodel;
1277         this.property = prop;
1278         this.height = height;
1279         this.width = width;
1280         this.scale = scale;        
1281         this.bgcolor = 'black';
1282 
1283         if (typeof document !== "undefined")                       // In browser, crease a new HTML canvas-element to draw on 
1284         {
1285             this.elem = document.createElement("canvas");
1286             this.titlediv = document.createElement("div");
1287             this.titlediv.innerHTML = "<font size = 2>" + this.label + "</font>";
1288             this.canvasdiv = document.createElement("div");
1289             this.canvasdiv.className = "grid-holder";
1290             this.elem.className = "grid-holder";
1291             this.elem.width = this.width * this.scale;
1292             this.elem.height = this.height * this.scale;
1293             this.canvasdiv.appendChild(this.elem);
1294             this.canvasdiv.appendChild(this.titlediv);
1295             document.getElementById("canvas_holder").appendChild(this.canvasdiv);
1296             this.ctx = this.elem.getContext("2d");
1297         }
1298 
1299     }
1300 
1301     /**
1302     *  Draw the state of the Gridmodel (for a specific property) onto the HTML element
1303     */
1304     displaygrid() {
1305         let ctx = this.ctx;
1306         let scale = this.scale;
1307         let ncol = this.width;
1308         let nrow = this.height;
1309         let prop = this.property;
1310         ctx.clearRect(0, 0, scale * ncol, scale * nrow);
1311 
1312         ctx.fillStyle = this.bgcolor;
1313         ctx.fillRect(0, 0, ncol * scale, nrow * scale);
1314         var id = ctx.getImageData(0, 0, scale * ncol, scale * nrow);
1315         var pixels = id.data;
1316         for (let i = 0; i < ncol; i++)         // i are cols
1317         {
1318             for (let j = 0; j < nrow; j++)     // j are rows
1319             {
1320                 if (!(prop in this.gridmodel.grid[i][j])) continue
1321                 let statecols = this.gridmodel.statecolours[prop];
1322                                 
1323                 
1324 
1325                 let value = this.gridmodel.grid[i][j][prop];
1326                 if(this.multiplier !== undefined) value *= this.multiplier;
1327                 if(this.maxval && value>this.maxval) value = this.maxval;
1328                 if(this.minval && value<this.minval) value = this.minval;
1329 
1330                 if (statecols[value] == undefined)                   // Don't draw the background state
1331                     continue
1332                 let idx;
1333                 if (statecols.constructor == Object) {
1334                     idx = statecols[value];
1335                 }
1336                 else idx = statecols;
1337 
1338                 for (let n = 0; n < scale; n++) {
1339                     for (let m = 0; m < scale; m++) {
1340                         let x = i * scale + n;
1341                         let y = j * scale + m;
1342                         var off = (y * id.width + x) * 4;
1343                         pixels[off] = idx[0];
1344                         pixels[off + 1] = idx[1];
1345                         pixels[off + 2] = idx[2];
1346                     }
1347                 }
1348 
1349 
1350             }
1351         }
1352         ctx.putImageData(id, 0, 0);
1353     }
1354 }
1355 
1356 /**
1357  *  Simulation is the global class of Cacatoo, containing the main configuration  
1358  *  for making a grid-based model grid and displaying it in either browser or with
1359  *  nodejs. 
1360  */
1361 class Simulation {
1362     /**
1363     *  The constructor function for a @Simulation object. Takes a config dictionary.
1364     *  and sets options accordingly.  
1365     *  @param {dictionary} config A dictionary (object) with all the necessary settings to setup a Cacatoo simulation. 
1366     */
1367     constructor(config) {
1368         this.config = config;
1369         this.rng = new MersenneTwister(config.seed || 53);
1370         this.sleep = config.sleep || 0;
1371         this.fps = config.fps * 1.4 || 60;
1372         this.limitfps = true;
1373         if (config.limitfps == false) this.limitfps = false;
1374 
1375         // Three arrays for all the grids ('CAs'), canvases ('displays'), and graphs 
1376         this.gridmodels = [];            // All gridmodels in this simulation
1377         this.canvases = [];              // Array with refs to all canvases (from all models) from this simulation
1378         this.graphs = [];                // All graphs
1379         this.time = 0;
1380     }
1381 
1382     /**
1383     *  Generate a new GridModel within this simulation.  
1384     *  @param {string} name The name of your new model, e.g. "gol" for game of life. Cannot contain whitespaces. 
1385     */
1386     makeGridmodel(name) {
1387         if (name.indexOf(' ') >= 0) throw new Error("The name of a gridmodel cannot contain whitespaces.")
1388         let model = new Gridmodel(name, this.config, this.rng); // ,this.config.show_gridname weggecomment
1389         this[name] = model;           // this = model["cheater"] = CA-obj
1390         this.gridmodels.push(model);
1391     }
1392 
1393     /**
1394     * Create a display for a gridmodel, showing a certain property on it. 
1395     * @param {string} name The name of an existing gridmodel to display
1396     * @param {string} property The name of the property to display
1397     * @param {string} customlab Overwrite the display name with something more descriptive
1398     * @param {integer} height Number of rows to display (default = ALL)
1399     * @param {integer} width Number of cols to display (default = ALL)
1400     * @param {integer} scale Scale of display (default inherited from @Simulation class)
1401     */
1402     createDisplay(name, property, customlab, height, width, scale, x, y) {
1403         let label = customlab;
1404         if (customlab == undefined) label = `${name} (${property})`; // <ID>_NAME_(PROPERTY)
1405         let gridmodel = this[name];
1406         if (gridmodel == undefined) throw new Error(`There is no GridModel with the name ${name}`)
1407         if (height == undefined) height = gridmodel.nr;
1408         if (width == undefined) width = gridmodel.nc;
1409         if (scale == undefined) scale = gridmodel.scale;
1410         
1411         let cnv = new Canvas(gridmodel, property, label, height, width, scale);
1412         gridmodel.canvases[label] = cnv;  // Add a reference to the canvas to the gridmodel
1413         this.canvases.push(cnv);  // Add a reference to the canvas to the sim
1414         const canvas = cnv.elem;
1415         canvas.addEventListener('mousedown', (e) => { this.getCursorPosition(canvas, e, scale); });
1416         cnv.displaygrid();
1417     }
1418 
1419     /**
1420     * Create a display for a gridmodel, showing a certain property on it. 
1421     * @param {string} name The name of an existing gridmodel to display
1422     * @param {string} property The name of the property to display
1423     * @param {string} customlab Overwrite the display name with something more descriptive
1424     * @param {integer} height Number of rows to display (default = ALL)
1425     * @param {integer} width Number of cols to display (default = ALL)
1426     * @param {integer} scale Scale of display (default inherited from @Simulation class)
1427     */
1428     createDisplay_c(config) {  
1429     
1430         let name = config.model;
1431         
1432         let property = config.property; 
1433         
1434         
1435         let label = config.label;
1436         if (label == undefined) label = `${name} (${property})`; // <ID>_NAME_(PROPERTY)
1437         let gridmodel = this[name];
1438         if (gridmodel == undefined) throw new Error(`There is no GridModel with the name ${name}`)
1439         
1440         let height = config.height || this[name].nr;        
1441         let width = config.width || this[name].nc;
1442         let scale = config.scale || this[name].scale;
1443        
1444         
1445         let maxval = config.maxval | 100;
1446         let minval = config.minval; 
1447         let multiplier = config.multiplier; 
1448 
1449         
1450         if(config.fill == "viridis") this[name].colourViridis(property, maxval);    
1451         else this[name].colourGradient(property, maxval, [0, 0, 0], [0, 0, 150], [100,100,150]);
1452         
1453         let cnv = new Canvas(gridmodel, property, label, height, width, scale);
1454         gridmodel.canvases[label] = cnv;  // Add a reference to the canvas to the gridmodel
1455         if (maxval != undefined) cnv.maxval = maxval;
1456         if (minval != undefined) cnv.minval = minval;
1457         if (multiplier != undefined) cnv.multiplier = multiplier;
1458 
1459         this.canvases.push(cnv);  // Add a reference to the canvas to the sim
1460         const canvas = cnv.elem;
1461         canvas.addEventListener('mousedown', (e) => { this.getCursorPosition(canvas, e, scale); });
1462         cnv.displaygrid();
1463     }
1464 
1465 
1466     /**
1467     * Create a display for a gridmodel, showing a certain property on it. 
1468     * @param {canvas} canvas A (constant) canvas object
1469     * @param {event-handler} event Event handler (mousedown)
1470     * @param {scale} scale The zoom (scale) of the grid to grab the correct grid point
1471     */
1472     getCursorPosition(canvas, event, scale) {
1473         const rect = canvas.getBoundingClientRect();
1474         const x = Math.floor(Math.max(0, event.clientX - rect.left) / scale);
1475         const y = Math.floor(Math.max(0, event.clientY - rect.top) / scale);
1476         console.log(`You have clicked the grid at position ${x},${y}`);
1477         for (let model of this.gridmodels) {
1478             console.log(`This corresponds to gridpoint...`);
1479             console.log(model.grid[x][y]);
1480             console.log(`... in model ${model.name}`);
1481         }
1482     }
1483 
1484 
1485 
1486     /**
1487     * Update all the grid models one step. Apply optional mixing
1488     */
1489     step() {
1490         for (let i = 0; i < this.gridmodels.length; i++)
1491             this.gridmodels[i].update();
1492     }
1493 
1494     /**
1495     * Apply global events to all grids in the model. 
1496     * (only perfectmix currently... :D)
1497     */
1498     events() {
1499         for (let i = 0; i < this.gridmodels.length; i++) {
1500             if (this.mix) this.gridmodels[i].perfectMix();
1501         }
1502     }
1503 
1504     /**
1505      *  Display all the canvases linked to this simulation
1506      */
1507     display() {
1508         for (let i = 0; i < this.canvases.length; i++)
1509             this.canvases[i].displaygrid();
1510     }
1511 
1512     /**
1513      *  Start the simulation. start() detects whether the user is running the code from the browser of
1514      *  in nodejs. In the browser, a GUI is provided to interact with the model. In nodejs the 
1515      *  programmer can simply wait for the result without wasting time on displaying intermediate stuff 
1516      *  (which can be slow)
1517      */
1518     start() {
1519         let model = this;    // Caching this, as function animate changes the this-scope to the scope of the animate-function
1520         if (typeof window != 'undefined') {
1521             let meter = new FPSMeter({ show: 'ms', left: "auto", top: "45px", right: "50px", graph: 1, history: 20, smoothing: 30 });
1522 
1523             document.title = `Cacatoo - ${this.config.title}`;
1524 
1525             if (document.getElementById("footer") != null) document.getElementById("footer").innerHTML = `<a target="blank" href="https://bramvandijk88.github.io/cacatoo/"><img class="logos" src="/images/elephant_cacatoo_small.png"></a>`;
1526             if (document.getElementById("footer") != null) document.getElementById("footer").innerHTML += `<a target="blank" href="https://github.com/bramvandijk88/cacatoo"><img class="logos" style="padding-top:32px;" src="/images/gh.png"></a></img>`;
1527             if (document.getElementById("header") != null) document.getElementById("header").innerHTML = `<h2>Cacatoo - ${this.config.title}</h2><font size=2>${this.config.description}</font size>`;
1528             if (document.getElementById("footer") != null) document.getElementById("footer").innerHTML += "<h2>Cacatoo is a toolbox to explore individual-based models straight from your webbrowser. It is still in development. Feedback <a href=\"https://www.bramvandijk.com/contact\">very welcome.</a></h2>";
1529             let simStartTime = performance.now();
1530 
1531             async function animate() {
1532                 if (model.config.fastmode)          // Fast-mode tracks the performance so that frames can be skipped / paused / etc. Has some overhead, so use wisely!
1533                 {
1534                     if (model.sleep > 0) await pause(model.sleep);
1535 
1536                     let t = 0;              // Will track cumulative time per step in microseconds 
1537 
1538                     while (t < 16.67 * 60 / model.fps)          //(t < 16.67) results in 60 fps if possible
1539                     {
1540                         let startTime = performance.now();
1541                         model.step();
1542                         model.events();
1543                         let endTime = performance.now();
1544                         t += (endTime - startTime);
1545                         model.time++;
1546                         if (!model.limitfps) break
1547                     }
1548                     model.display();
1549                     meter.tick();
1550                 }
1551                 else                    // A slightly more simple setup, but does not allow controls like frame-rate, skipping every nth frame, etc. 
1552                 {
1553                     meter.tickStart();
1554                     model.step();
1555                     model.events();
1556                     model.display();
1557                     meter.tick();
1558                     model.time++;
1559                 }
1560 
1561                 let frame = requestAnimationFrame(animate);
1562                 if (model.time >= model.config.maxtime) {
1563                     let simStopTime = performance.now();
1564                     console.log("Cacatoo completed after", Math.round(simStopTime - simStartTime) / 1000, "seconds");
1565                     cancelAnimationFrame(frame);
1566                 }
1567                 if (model.pause == true) { cancelAnimationFrame(frame); }
1568 
1569             }
1570 
1571             requestAnimationFrame(animate);
1572         }
1573         else {
1574             while (true) {
1575                 model.step();
1576                 model.time++;
1577             }
1578         }
1579     }
1580 
1581     /**
1582      *  initialGrid populates a grid with states. The number of arguments 
1583      *  is flexible and defined the percentage of every state. For example,
1584      *  initialGrid('grid','species',1,0.5,2,0.25) populates the grid with 
1585      *  two species (1 and 2), where species 1 occupies 50% of the grid, and
1586      *  species 2 25%. 
1587      *  @param {@GridModel} grid The gridmodel containing the grid to be modified. 
1588      *  @param {String} property The name of the state to be set 
1589      *  @param {integer} value The value of the state to be set (optional argument with position 2, 4, 6, ..., n)
1590      *  @param {float} fraction The chance the grid point is set to this state (optional argument with position 3, 5, 7, ..., n)
1591      */
1592     initialGrid(gridmodel, property) {
1593         let p = property || 'val';
1594         let bg = 0;
1595 
1596         for (let i = 0; i < gridmodel.nc; i++)                          // i are columns
1597             for (let j = 0; j < gridmodel.nr; j++)                  // j are rows
1598                 gridmodel.grid[i][j][p] = bg;
1599 
1600         for (let arg = 2; arg < arguments.length; arg += 2)         // Parse remaining 2+ arguments to fill the grid           
1601             for (let i = 0; i < gridmodel.nc; i++)                        // i are columns
1602                 for (let j = 0; j < gridmodel.nr; j++)                    // j are rows
1603                     if (this.rng.random() < arguments[arg + 1]) gridmodel.grid[i][j][p] = arguments[arg];
1604     }
1605 
1606     /**
1607      *  populateGrid populates a grid with custom individuals. 
1608      *  @param {@GridModel} grid The gridmodel containing the grid to be modified. 
1609      *  @param {Array} individuals The properties for individuals 1..n
1610      *  @param {Array} freqs The initial frequency of individuals 1..n
1611      */
1612     populateGrid(gridmodel,individuals,freqs)
1613     {
1614         let sumfreqs =0;
1615         if(individuals.length != freqs.length) throw new Error("populateGrid should have as many individuals as frequencies")
1616         for(let i=0; i<freqs.length; i++) sumfreqs += freqs[i];
1617 
1618         for (let i = 0; i < gridmodel.nc; i++)                          // i are columns
1619             for (let j = 0; j < gridmodel.nr; j++){                 // j are rows
1620                 let cumsumfreq = 0;                
1621                 for (const property in individuals[0]) {
1622                     gridmodel.grid[i][j][property] = 0;    
1623                 }
1624                 for(let n=0; n<individuals.length; n++)
1625                 {
1626                     cumsumfreq += freqs[n];
1627                     if(this.rng.random() < cumsumfreq) {
1628                         Object.assign(gridmodel.grid[i][j],individuals[n]);
1629                         break
1630                     }
1631                 }
1632             }
1633         
1634     }
1635 
1636     /**
1637     *  initialSpot populates a grid with states. Grid points close to a certain coordinate are set to state value, while
1638     *  other cells are set to the bg-state of 0. 
1639     *  @param {@GridModel} grid The gridmodel containing the grid to be modified. 
1640     *  @param {String} property The name of the state to be set 
1641     *  @param {integer} value The value of the state to be set (optional argument with position 2, 4, 6, ..., n)
1642     *  @param {float} fraction The chance the grid point is set to this state (optional argument with position 3, 5, 7, ..., n)
1643     */
1644     initialSpot(gridmodel, property, value, size, x, y) {
1645         let p = property || 'val';
1646 
1647         // Draw a circle
1648         for (let i = 0; i < gridmodel.nc; i++)                          // i are columns
1649             for (let j = 0; j < gridmodel.nr; j++)                           // j are rows
1650             {
1651                 if ((Math.pow((i - x), 2) + Math.pow((j - y), 2)) < size)
1652                     gridmodel.grid[i % gridmodel.nc][j % gridmodel.nr][p] = value;
1653                 else
1654                     gridmodel.grid[i % gridmodel.nc][j % gridmodel.nr][p] = 0;
1655             }
1656     }
1657 
1658     /**
1659      *  populateSpot populates a spot with custom individuals. 
1660      *  @param {@GridModel} grid The gridmodel containing the grid to be modified. 
1661      *  @param {Array} individuals The properties for individuals 1..n
1662      *  @param {Array} freqs The initial frequency of individuals 1..n
1663      */
1664      populateSpot(gridmodel,individuals, freqs,size, x, y)
1665      {
1666         let sumfreqs =0;
1667         if(individuals.length != freqs.length) throw new Error("populateGrid should have as many individuals as frequencies")
1668         for(let i=0; i<freqs.length; i++) sumfreqs += freqs[i];
1669          
1670         // Draw a circle
1671         for (let i = 0; i < gridmodel.nc; i++)                          // i are columns
1672         for (let j = 0; j < gridmodel.nr; j++)                           // j are rows
1673         {
1674             for (const property in individuals[0]) gridmodel.grid[i][j][property] = 0; 
1675 
1676             if ((Math.pow((i - x), 2) + Math.pow((j - y), 2)) < size)
1677             {
1678                 let cumsumfreq = 0;                
1679                 for(let n=0; n<individuals.length; n++)
1680                 {
1681                     cumsumfreq += freqs[n];
1682                     if(this.rng.random() < cumsumfreq) {
1683                         Object.assign(gridmodel.grid[i % gridmodel.nc][j % gridmodel.nr],individuals[n]);
1684                         break
1685                     }
1686                 }
1687             }
1688         }
1689          
1690      }
1691 
1692 
1693     /**
1694      *  addButton adds a HTML button which can be linked to a function by the user. 
1695      *  @param {string} text Text displayed on the button
1696      *  @param {function} func Function to be linked to the button
1697      */
1698     addButton(text, func) {
1699         if (typeof window == 'undefined') return
1700         let button = document.createElement("button");
1701         button.innerHTML = text;
1702         button.addEventListener("click", func, true);
1703         document.getElementById("form_holder").appendChild(button);
1704     }
1705 
1706     /**
1707      *  addSlider adds a HTML slider to the DOM-environment which allows the user
1708      *  to modify a model parameter at runtime. 
1709      *  @param {string} parameter The name of the (global!) parameter to link to the slider
1710      *  @param {float} [min] Minimal value of the slider
1711      *  @param {float} [max] Maximum value of the slider
1712      *  @param {float} [step] Step-size when modifying
1713      */
1714     addSlider(parameter, min = 0.0, max = 2.0, step = 0.01, label) {
1715         let lab = label || parameter;
1716         if (typeof window == "undefined") return
1717         if (window[parameter] === undefined) { console.warn(`addSlider: parameter ${parameter} not found. No slider made.`); return; }
1718         let container = document.createElement("div");
1719         container.classList.add("form-container");
1720 
1721         let slider = document.createElement("input");
1722         let numeric = document.createElement("input");
1723         container.innerHTML += "<div style='width:100%;height:20px;font-size:12px;'><b>" + lab + ":</b></div>";
1724 
1725         // Setting slider variables / handler
1726         slider.type = 'range';
1727         slider.classList.add("slider");
1728         slider.min = min;
1729         slider.max = max;
1730         slider.step = step;
1731         slider.value = window[parameter];
1732         slider.oninput = function () {
1733             let value = parseFloat(slider.value);
1734             window[parameter] = parseFloat(value);
1735             numeric.value = value;
1736         };
1737 
1738         // Setting number variables / handler
1739         numeric.type = 'number';
1740         numeric.classList.add("number");
1741         numeric.min = min;
1742         numeric.max = max;
1743         numeric.step = step;
1744         numeric.value = window[parameter];
1745         numeric.onchange = function () {
1746             let value = parseFloat(numeric.value);
1747             if (value > this.max) value = this.max;
1748             if (value < this.min) value = this.min;
1749             window[parameter] = parseFloat(value);
1750             numeric.value = value;
1751             slider.value = value;
1752         };
1753         container.appendChild(slider);
1754         container.appendChild(numeric);
1755         document.getElementById("form_holder").appendChild(container);
1756     }
1757     addHTML(div, html) {
1758         if (typeof window == "undefined") return
1759         let container = document.createElement("div");
1760         container.innerHTML += html;
1761         document.getElementById(div).appendChild(container);
1762     }
1763     log(msg, target) {
1764         if (typeof window == "undefined") console.log(msg);
1765         else if (typeof target == "undefined") console.log(msg);
1766         else document.getElementById(target).innerHTML += `${msg}<br>`;
1767     }
1768     /**
1769      *  addPatternButton adds a pattern button to the HTML environment which allows the user
1770      *  to load a PNG which then sets the state of 'proparty' for the @GridModel. 
1771      *  (currently only supports black and white image)
1772      *  @param {@GridModel} targetgrid The gridmodel containing the grid to be modified. 
1773      *  @param {String} property The name of the state to be set 
1774      */
1775     addPatternButton(targetgrid, property) {
1776         let imageLoader = document.createElement("input");
1777         imageLoader.type = "file";
1778         imageLoader.id = "imageLoader";
1779         let sim = this;
1780         imageLoader.style = "display:none";
1781         imageLoader.name = "imageLoader";
1782         document.getElementById("form_holder").appendChild(imageLoader);
1783         let label = document.createElement("label");
1784         label.setAttribute("for", "imageLoader");
1785         label.style = "background-color: rgb(217, 234, 245);border-radius: 10px;border: 2px solid rgb(177, 209, 231);padding:7px;font-size:12px;margin:10px;width:128px;";
1786         label.innerHTML = "<font size=2>Select your own initial state</font>";
1787         document.getElementById("form_holder").appendChild(label);
1788         let canvas = document.createElement('canvas');
1789         canvas.name = "imageCanvas";
1790         let ctx = canvas.getContext('2d');
1791         function handleImage(e) {
1792             let reader = new FileReader();
1793             let grid_data;
1794 
1795             let grid = e.currentTarget.grid;
1796             reader.onload = function (event) {
1797                 var img = new Image();
1798                 img.onload = function () {
1799                     canvas.width = img.width;
1800                     canvas.height = img.height;
1801                     ctx.drawImage(img, 0, 0);
1802 
1803                     grid_data = get2DFromCanvas(canvas);
1804 
1805                     for (let i = 0; i < grid.nc; i++) for (let j = 0; j < grid.nr; j++) grid.grid[i][j].alive = 0;
1806                     for (let i = 0; i < grid_data[0].length; i++)          // i are columns
1807                         for (let j = 0; j < grid_data.length; j++)             // j are rows
1808                         {
1809                             grid.grid[Math.floor(i + grid.nc / 2 - img.width / 2)][Math.floor(j + grid.nr / 2 - img.height / 2)][property] = grid_data[j][i];
1810                         }
1811                     sim.display();
1812 
1813                 };
1814                 img.src = event.target.result;
1815 
1816             };
1817             reader.readAsDataURL(e.target.files[0]);
1818             document.getElementById("imageLoader").value = "";
1819         }
1820         imageLoader.addEventListener('change', handleImage, false);
1821         imageLoader.grid = targetgrid;    // Bind a grid to imageLoader 
1822     }
1823 
1824     /**
1825      *  initialPattern takes a @GridModel and loads a pattern from a PNG file. Note that this
1826      *  will only work when Cacatoo is ran on a server due to security issues. If you want to
1827      *  use this feature locally, there are plugins for most browser to host a simple local
1828      *  webserver. 
1829      *  (currently only supports black and white image)
1830      */
1831     initialPattern(grid, property, image_path, x, y) {
1832         let sim = this;
1833         if (typeof window != undefined) {
1834             for (let i = 0; i < grid.nc; i++) for (let j = 0; j < grid.nr; j++) grid.grid[i][j][property] = 0;
1835             let tempcanv = document.createElement("canvas");
1836             let tempctx = tempcanv.getContext('2d');
1837             var tempimg = new Image();
1838             tempimg.onload = function () {
1839                 tempcanv.width = tempimg.width;
1840                 tempcanv.height = tempimg.height;
1841                 tempctx.drawImage(tempimg, 0, 0);
1842                 let grid_data = get2DFromCanvas(tempcanv);
1843                 if (x + tempimg.width >= grid.nc || y + tempimg.height >= grid.nr) throw RangeError("Cannot place pattern outside of the canvas")
1844                 for (let i = 0; i < grid_data[0].length; i++)         // i are columns
1845                     for (let j = 0; j < grid_data.length; j++)     // j are rows
1846                     {
1847                         grid.grid[x + i][y + j][property] = grid_data[j][i];
1848                     }
1849                 sim.display();
1850             };
1851 
1852             tempimg.src = image_path;
1853             tempimg.crossOrigin = "anonymous";
1854 
1855         }
1856         else {
1857             console.error("initialPattern currently only supported in browser-mode");
1858         }
1859 
1860     }
1861 
1862     /**
1863      *  Toggle the mix option
1864      */
1865     toggle_mix() {
1866         if (this.mix) this.mix = false;
1867         else this.mix = true;
1868     }
1869 
1870     /**
1871      *  Toggle the pause option. Restart the model if pause is disabled. 
1872      */
1873     toggle_play() {
1874         if (this.pause) this.pause = false;
1875         else this.pause = true;
1876         if (!this.pause) this.start();
1877     }
1878 
1879     /**
1880      *  colourRamp interpolates between two arrays to get a smooth colour scale. 
1881      *  @param {array} arr1 Array of R,G,B values to start fromtargetgrid The gridmodel containing the grid to be modified. 
1882      *  @param {array} arr2 Array of R,B,B values to transition towards
1883      *  @param {integer} n number of steps taken
1884      *  @return {dict} A dictionary (i.e. named JS object) of colours
1885      */
1886     colourRamp(arr1, arr2, n) {
1887         let return_dict = {};
1888         for (let i = 0; i < n; i++) {
1889 
1890             return_dict[i] = [Math.floor(arr1[0] + arr2[0] * (i / n)),
1891             Math.floor(arr1[1] + arr2[1] * (i / n)),
1892             Math.floor(arr1[2] + arr2[2] * (i / n))];
1893         }
1894         return return_dict
1895     }
1896 }
1897 
1898 
1899 /**
1900 * Below are a few global functions that are used by Simulation classes, but not a method of a Simulation instance per se
1901 */
1902 
1903 
1904 //Delay for a number of milliseconds
1905 const pause = (timeoutMsec) => new Promise(resolve => setTimeout(resolve, timeoutMsec));
1906 
1907 /**
1908  *  Reconstruct a 2D array based on a canvas 
1909  *  @param {canvas} canvas HTML canvas element to convert to a 2D grid for Cacatoo
1910  *  @return {2DArray} Returns a 2D array (i.e. a grid) with the states
1911  */
1912 function get2DFromCanvas(canvas) {
1913     let width = canvas.width;
1914     let height = canvas.height;
1915     let ctx = canvas.getContext('2d');
1916     let img1 = ctx.getImageData(0, 0, width, height);
1917     let binary = new Array(img1.data.length);
1918     let idx = 0;
1919     for (var i = 0; i < img1.data.length; i += 4) {
1920         let num = [img1.data[i], img1.data[i + 1], img1.data[i + 2]];
1921         let state;
1922         // console.log(num)
1923         if (JSON.stringify(num) == JSON.stringify([0, 0, 0])) state = 0;
1924         else if (JSON.stringify(num) == JSON.stringify([255, 255, 255])) state = 1;
1925         else if (JSON.stringify(num) == JSON.stringify([255, 0, 0])) state = 2;
1926         else if (JSON.stringify(num) == JSON.stringify([0, 0, 255])) state = 3;
1927         else throw RangeError("Colour in your pattern does not exist in Cacatoo")
1928         binary[idx] = state;
1929         idx++;
1930     }
1931 
1932     const arr2D = [];
1933     let rows = 0;
1934     while (rows < height) {
1935         arr2D.push(binary.splice(0, width));
1936         rows++;
1937     }
1938     return arr2D
1939 }
1940 
1941 
1942 try
1943 {
1944     module.exports = Simulation;
1945 }
1946 catch(err)
1947 {
1948     // do nothing
1949 }
1950