A small MarkovJunior-style rewrite engine in vanilla (almost) Wolfram Language.
Wolfram Language is generally built around pattern matching; therefore, MarkovJunior can be implemented naturally using native
ReplaceAll,Rule, andPatternsymbols. Most of the code is used for building a friendly API and performing error checks.
This project is inspired by MarkovJunior, the probabilistic programming language by Maxim Gumin. The language, core idea, and many example patterns come from that original project.
This repository is only a small, partial Wolfram Language implementation of some MarkovJunior-style array rewrite behavior. It is not a full port of the original interpreter or language.
- Wolfram Language 13.0 or newer
- A 2D array as input
3D arrays are not supported yet.
Implemented:
- 2D array rewrite states
- single random-match rule groups with
1 - whole-array replacement groups with
All - automatic rotations and mirrored rotations for rules
Not implemented:
- the original XML importer
- observations, constraints, fields, search, and inference
- 3D grids and voxel output
From this repository directory:
PacletDirectoryLoad[Directory[]];
Needs["CoffeeLiqueur`Workshop`MarkovJunior`" -> "mj`"];Or run directly any suitable example from Examples/ folder
mj`MarkovState[array, rules]
mj`Propagate[state]
mj`GetArray[state]
mj`FinishedQ[state]Rules are grouped as:
{
1 | All, (* apply one random match, or all matches *)
_Integer | Infinity, (* maximum number of propagation steps for this group *)
(* symmetry operations to be applied *)
Automatic | All | None | "Rotation" | "Mirror" | "MirrorX" | "MirrorY",
{rules...}
}A 1 group chooses one transformed result at random from the available matches.
An All group applies replacements across the whole array.
Rules are tried with rotations and mirrored rotations, so simple 0D or 1D directional rules can apply in every orientation.
If symmetry operations is set to Automatic, only 1D and 0D rules will be symmetrized, the rest will be applied as it is.
Single-cell replacement:
state = mj`MarkovState[Table[Black, {20}, {20}], {
{1, Infinity, Automatic, {Black -> Red}}
}];Growth model:
state = mj`MarkovState[Table[Black, {20}, {20}], {
{1, 1, Automatic, {
Black -> Red
}},
{1, Infinity, All, {
{a___, Black, Red, b___} :> {a, Red, Red, b}
}}
}];Self-avoiding walk:
state = mj`MarkovState[Table[Black, {20}, {20}], {
{1, 1, Automatic, {
Black -> Red
}},
{1, Infinity, All, {
{a___, Red,Black,Black, b___} :> {a, White,Gray,Red, b}
}}
}];Then to run this example call Propagate method:
state = mj`Propagate[state];
state // mj`GetArray // ArrayPlotTo export it as GIF animation, one can use Image as a wrapper:
imgs = Table[ImageResize[
Image[(state=mj`Propagate[state])//mj`GetArray], {350,350}, Method->"NearestNeighbor"], {100}];
Export["ex.gif", AnimatedImage[imgs, FrameRate->25]]The bundled notebook in [Examples/Example.*]
builds a small procedural river scene
The same rules can be written as plain Wolfram Language by naming the colors first:
empty = GrayLevel[0];
yellow = RGBColor[1, 1, 0];
red = RGBColor[1, 0, 0];
river = RGBColor[1/3, 1/3, 1];
grass = RGBColor[0, 1, 0];
darkGrass = RGBColor[0, 2/3, 0];
riverRules = {
(* Create two random seed points. *)
{1, 1, Automatic, {empty -> yellow}},
{1, 1, Automatic, {empty -> red}},
(* Flood both seed regions randomly. *)
{
1,
Infinity,
Automatic,
{
{a___, red, empty, b___} :> {a, red, red, b},
{a___, yellow, empty, b___} :> {a, yellow, yellow, b}
}
},
(* Mark the meeting edge as river. *)
{
All,
Infinity,
Automatic,
{
{a___, red, yellow, b___} :> {a, river, river, b}
}
},
(* Remove the temporary flood colors. *)
{
All,
Infinity,
Automatic,
{
{a___, yellow, b___} :> {a, empty, b},
{a___, red, b___} :> {a, empty, b}
}
},
(* Expand the river once *)
{
All,
1,
Automatic,
{
{a___, river, empty, b___} :> {a, river, river, b}
}
},
(* Place grass around the river, then fill the rest. *)
{
All,
Infinity,
Automatic,
{
{a___, river, empty, b___} :> {a, grass, grass, b}
}
},
{
1,
13,
Automatic,
{
{a___, empty, b___} :> {a, darkGrass, b}
}
},
{
1,
Infinity,
Automatic,
{
{a___, darkGrass, empty, b___} :> {a, darkGrass, darkGrass, b},
{a___, grass, empty, b___} :> {a, grass, grass, b}
}
}
};
initialState[size_: 20] := mj`MarkovState[
ConstantArray[empty, {size, size}],
riverRules
];See
Examples/Example.wls
The repository includes a standalone script version at
Examples/Example.wls. Clone repository and run:
wolframscript -file Examples/Example.wlsThe script exports the rendered result to Examples/markov-river.png.
See
Examples/Example.nb
In a Mathematica notebook, use the standard Wolfram Language Refresh option
syntax inside Dynamic. If the notebook is in this repository root:
PacletDirectoryLoad[NotebookDirectory[]];
Needs["CoffeeLiqueur`Workshop`MarkovJunior`" -> "mj`"];Then evaluate the river-generator definitions above, followed by:
DynamicModule[{state = initialState[40]},
Dynamic[
Refresh[
If[! mj`FinishedQ[state], state = mj`Propagate[state]];
ArrayPlot[mj`GetArray[state], PixelConstrained -> 10],
UpdateInterval -> 1/30,
TrackedSymbols :> {}
]
]
]See
Examples/Example.wln
For WLJS Notebook in
Examples/, load the package from the parent directory:
PacletDirectoryLoad[NotebookDirectory[] // ParentDirectory];
Needs["CoffeeLiqueur`Workshop`MarkovJunior`" -> "mj`"];After evaluating the river-generator definitions above:
state = initialState[20];
Refresh[
state = mj`Propagate[state];
ArrayPlot[mj`GetArray[state]],
1/30.0
]The two-argument Refresh[expr, seconds] form is specific to WLJS. In
Mathematica notebooks, use the Dynamic[Refresh[..., UpdateInterval -> ...]]
form shown above.
TrackedSymbols :> {} is useful here because the display should refresh on a
timer, not only when a notebook symbol changes.





