





The Regular Triangular Lattice



First of all, we are trying to simulate snowflake, which is a kind of hexagonal crystal. So it should be best to construct our CA on a regular hexagonal grid, i.e. regular triangular lattice.



We all know CellularAutomaton inherently works on rectangle lattices ("4-lattice" for short), so how can we deduce a triangular lattice ("3-lattice" for short) on it? Well, the differences between rect-lattice and triangular one is just a geometric transformation.



To demonstrate that, have a look at the following 4-lattice, with a blue square highlighting the range-1







Clearly there is always a hexagon (the green area) in this kind of neighborhood.



So forming a regular 3-lattice is as straightforward as doing a simple affine transformation (basically a shearing and a scaling):







So to take advantage of all the power of CellularAutomaton, all we have to do, is to use a following special 6-neighborhood stencil on rectangle lattices, meanwhile our model can be discussed and constructed on regular triangular lattice convieniently:







And after the calculation, we can perform the affine transformation with following functions to get a nice hexagonal grid picture.



Clear[vertexFunc]



vertexFunc =

Compile[{{para, _Real, 1}},

Module[{center, ratio}, center = para[[1 ;; 2]];

ratio = para[[3]];

{Re[#], Im[#]} + {{1, -(1/2)}, {0,

Sqrt[3]/2}}.Reverse[{-1, 1} center + {3, 0}] & /@ (ratio 1/

Sqrt[3] E^(I ?/6) E^(I Range[6] ?/3))],

RuntimeAttributes -> {Listable}, Parallelization -> True,

RuntimeOptions -> "Speed"

(*,CompilationTarget?"C"*)];



Clear[displayfunc]

displayfunc[array_, ratio_] :=

Graphics[{FaceForm[{ColorData["DeepSeaColors"][3]}],

EdgeForm[{ColorData["DeepSeaColors"][4]}],

Polygon[vertexFunc[Append[#, ratio]] & /@ Position[array, 1]]},

Background -> ColorData["DeepSeaColors"][0]]







The Model



To construct the crystallization model, let's consider one of the 6-neighborhood stencil, where each cell represents a minimal crystal unit:







A simple model will need only 2 states: 0 for "It's empty", 1 for "There is a crystal unit". So by considering all (except the 000000 one, because we are generating ONE snowflake thus don't want a crystall randomly arises from void) 6-bit non-negative numbers, we can have a finite set of possible arrangements of the neighborhood:



stateSet = Tuples[{0, 1}, 6] // Rest

However, from the viewpoint of physics, any two arrangements which can be transformed into each other with only rotation and reflection should be considered as the same arrangement in the sense of their physical effects on the central cell (i.e. cell 2,2 ) are the same:







So we should gather stateSet with above equivalence class:



gatherTestFunc = Function[lst, Union[Join[



RotateLeft[lst, # - 1] & /@ Flatten[Position[lst, 1]],

RotateLeft[Reverse[lst], # - 1] & /@

Flatten[Position[Reverse[lst], 1]]

]]];



stateClsSet = Sort /@ Gather[stateSet, gatherTestFunc[#1] == gatherTestFunc[#2] &];



stateClsSetHomogeneous = ArrayPad[#, {{0, 12 - Length@#}, {0, 0}}] & /@ stateClsSet;



Which turned out to be 12 classes in total:







Now from the viewpoint of cellular automaton, we need to establish a set of rules on how should any 6-neighborhood arrangement, i.e. those 12 kinds of equivalence classes, determine the state of the central cell.



There are 4 kinds of possible transformations on cell 2,2 : 0 --> 1 is called frozen, 0 --> 0 is remaining empty, 1 --> 1 is remaining frozen, and 1 --> 0 is called melten. To make things more interesting and to explore more possibilities, we can introduce probability here, so certain arrangement will give certain probabilities corresponding to the 4 kinds of transformations. But notice that because of the unitarity of probability, we have Prob(frozen) + Prob(0->0) = 1 and Prob(melten) + Prob(1->1) = 1, so only 2 of the 4 probabilities are independent. In the following, we'll choose Prob(frozen) and Prob(melten), and denote them as pFrozen and pMelten.







Back to physics / thermodynamics, those 24 probabilities, pFrozen and pMelten, can of corse be determined by serious physical models, or they can be chosen randomly just for fun. For example, an intuitive (and naive) idea would be to believe an empty cell nearby a sharp pointed end or with abundant moisture source will have a high pFrozen. (People who are interested in the serious physical models should not miss



Now we have the grid, the stencil, the neighborhood arrangement set and the transfer probabilities, we're offically ready to construct our cellular automaton rules.

Following the above discussion, the construction is straightforward. There are only two points which need to pay attention to. One is to keep in mind that the rule function is applied on the 3x3 stencil, so even cell 1,1 and cell 3,3 has nothing to do with our model, don't forget handling them. The second is to use a SeedRandom function to make sure same arrangement gives same result in same time step, otherwise the 6-fold rotational symmetry and 3 axes of reflection symmetry will both break!



Clear[ruleFunc]



ruleFunc = With[{

stateClsSetHomogeneous = stateClsSetHomogeneous,

seedStore = RandomInteger[{0, 1000}, 1000],

pFreeze = {1, 0, 0.6, 0, 0.3, 0.15, 0, 0.2, 0, 0.2, 0, 0.8},

pMelt = {0, 0.7, 0.5, 0.7, 0.7, 0.5, 0.3, 0.5, 0.3, 0.2, 0.1, 0 }

},

Compile[{{neighborarry, _Integer, 2}, {step, _Integer}},

Module[{cv, neighborlst, cls, rand},

cv = neighborarry[[2, 2]];

neighborlst = {#[[1, 2]], #[[1, 3]], #[[2, 3]], #[[3, 2]], #[[3,

1]], #[[2, 1]]} &[neighborarry];

If[Total[neighborlst] == 0, cv,

cls = Position[stateClsSetHomogeneous, neighborlst][[1, 1]];

SeedRandom[seedStore[[step + 1]]];

rand = RandomReal[];

Boole@If[cv == 0, rand < pFreeze[[cls]], rand > pMelt[[cls]]]

]],

(*CompilationTarget -> "C",*)

RuntimeAttributes -> {Listable}, Parallelization -> True,

RuntimeOptions -> "Speed"

]

];



(Note: re-compile the rule function ruleFunc will give a different set of seedStore thus a different growth path.)



Now everything is ready, let's grow a snowflake from the beginning!



dataSet = Module[{



rule,

initM = {{

{0, 0, 0},

{0, 1, 0},

{0, 0, 0}

}, 0},

rspec = {1, 1},

tmin = 0, tmax = 100, dt = 1},

rule = {ruleFunc, {}, rspec};

CellularAutomaton[rule, initM, {{tmin, tmax, dt}}]

]; // AbsoluteTiming



Animate[

Rotate[displayfunc[dataSet[[k]], .8], 90 °],

{k, 1, Length[dataSet], 1},

AnimationDirection -> ForwardBackward,

AnimationRunning -> False, DisplayAllSteps -> True

]













Possible Improvements



We used a SeedRandom function in our CA rule function to force the 6-fold rotational symmetry and 3 axes of reflection symmetry, and performed the CA calculation on all cells. However, this so called 11/12 of the calculation. Also, the randomness of the growth path comes from seedStore, so to generate a new growth path, we have to re-compile the rule function. But with a improved model as described above, this constraint will no longer exist.









Open question



Can we construct a well-organized structure (like the crystals) from a cellular automaton defined on an irregular grid? While I believe the answer is yes, the next question would be how? Attachments: Random Snowflake...nb Some time ago one of my friends asked me whether it is possible to design a cellular automaton which can generate realistic snowflakes. I recall my crystallography and thermodynamics knowledge and came up a very simple yet impressive model.First of all, we are trying to simulate snowflake, which is a kind of hexagonal crystal. So it should be best to construct our CA on a regular hexagonal grid, i.e. regular triangular lattice.We all knowinherently works on rectangle lattices ("4-lattice" for short), so how can we deduce a triangular lattice ("3-lattice" for short) on it? Well, the differences between rect-lattice and triangular one is just a geometric transformation.To demonstrate that, have a look at the following 4-lattice, with a blue square highlighting the range-1 Moore neighborhood Clearly there is always a hexagon (the green area) in this kind of neighborhood.So forming a regular 3-lattice is as straightforward as doing a simple affine transformation (basically a shearing and a scaling):So to take advantage of all the power of, all we have to do, is to use a following special 6-neighborhood stencil on rectangle lattices, meanwhile our model can be discussed and constructed on regular triangular lattice convieniently:And after the calculation, we can perform the affine transformation with following functions to get a nice hexagonal grid picture.To construct the crystallization model, let's consider one of the 6-neighborhood stencil, where each cell represents a minimal crystal unit:A simple model will need only 2 states:for "It's empty",for "There is a crystal unit". So by considering all (except theone, because we are generating ONE snowflake thus don't want a crystall randomly arises from void)non-negative numbers, we can have a finite set of possible arrangements of the neighborhood:However, from the viewpoint of physics, any two arrangements which can be transformed into each other with only rotation and reflection should be considered as the same arrangement in the sense of their physical effects on the central cell (i.e. cell) are the same:So we should gatherwith above equivalence class:Which turned out to beclasses in total:Now from the viewpoint of cellular automaton, we need to establish a set of rules on how should any 6-neighborhood arrangement, i.e. those 12 kinds of equivalence classes, determine the state of the central cell.There are 4 kinds of possible transformations on cellis calledisis, andis called. To make things more interesting and to explore more possibilities, we can introduce probability here, so certain arrangement will give certain probabilities corresponding to the 4 kinds of transformations. But notice that because of the unitarity of probability, we have Prob(frozen) + Prob(0->0) = 1 and Prob(melten) + Prob(1->1) = 1, so only 2 of the 4 probabilities are independent. In the following, we'll choose Prob(frozen) and Prob(melten), and denote them asandBack to physics / thermodynamics, those 24 probabilities,and, can of corse be determined by serious physical models, or they can be chosen randomly just for fun. For example, an intuitive (and naive) idea would be to believe an empty cell nearby a sharp pointed end or with abundant moisture source will have a high. (People who are interested in the serious physical models should not miss the Gravner-Griffeath Snowfakes model .)Now we have the grid, the stencil, the neighborhood arrangement set and the transfer probabilities, we're offically ready to construct our cellular automaton rules.Following the above discussion, the construction is straightforward. There are only two points which need to pay attention to. One is to keep in mind that the rule function is applied on the 3x3 stencil, so even celland cellhas nothing to do with our model, don't forget handling them. The second is to use afunction to make sure same arrangement gives same result in same time step, otherwise the 6-fold rotational symmetry and 3 axes of reflection symmetry will both break!(Note: re-compile the rule functionwill give a different set ofthus a different growth path.)Now everything is ready, let's grow a snowflake from the beginning!We used afunction in our CA rule function to force the 6-fold rotational symmetry and 3 axes of reflection symmetry, and performed the CA calculation on all cells. However, this so called D 6 symmetry can (and should) be integrated into our model, which will savingof the calculation. Also, the randomness of the growth path comes from, so to generate a new growth path, we have to re-compile the rule function. But with a improved model as described above, this constraint will no longer exist.Can we construct a well-organized structure (like the crystals) from a cellular automaton defined on angrid? While I believe the answer is yes, the next question would be how?