Skip to content

CalcGen UserGuide

Bradley Brown edited this page Aug 13, 2026 · 1 revision

User Equation & the Fracturing Fog DSL — Guide + Cookbook

This is the complete authoring reference for your own fractal formulas in Fracturing Fog. It documents the equation language (the DSL) in full — every operator, function, and constant, the mathematical grammar, how an equation is built and iterated, which features are available on which rendering path — and closes with an exhaustive cookbook of ready-to-paste recipes.

It is written for authors, not implementers. If you want the generator internals (AST, simplifier, Taylor/BLA expansion, distance-estimate derivation), read the technical companions:

Companion pages: User Index · Fractal Equation Design Guide (technical) · CalculatorGen Authoring (technical) · User Bulb 3D Guide · ColorGen User Guide


Table of contents

  1. What "your own equation" means
  2. Two engines, one language
  3. The editor at a glance
  4. Mathematical grammar — how an equation is built
  5. Language reference — operators
  6. Language reference — the function catalogue
  7. Constants, variables, and state
  8. Structure: let, statement blocks, and conditionals
  9. Holomorphic vs non-holomorphic — why it matters
  10. Execution paths and what gates them
  11. Migrating C# equations to the DSL
  12. The Cookbook
  13. Editor workflow
  14. Command-line generation
  15. Troubleshooting
  16. Reference card
  17. See also

1. What "your own equation" means

A fractal equation in Fracturing Fog is the single line of math the app runs once per pixel, per iteration. The classic Mandelbrot recipe is:

$$ z_{n+1} = z_n^{2} + c $$

z starts at zero, c is the pixel's coordinate on the complex plane. Repeat that line hundreds or thousands of times for every pixel, ask whether |z| ever runs away past a bailout radius, and colour each pixel by how fast it escaped. That is the whole idea behind every escape-time fractal.

The User Equation type lets you replace that one line with anything the DSL can express. In the DSL, the Mandelbrot recipe is simply:

z*z + c

You are always writing the right-hand side of z_{n+1} = …. You never write z_{n+1} = yourself — the app supplies the loop, the escape test, the precision management, the colouring, and the deep-zoom machinery. You supply the math.

Note

The DSL is deliberately small and pure: it has numbers, the two fractal variables, arithmetic, a fixed catalogue of mathematical functions, and simple structure (let, conditionals). It has no file access, no loops, no reflection, nothing from the .NET runtime. That is what makes an equation safe to save, share, and open from someone else's region file.


2. Two engines, one language

The same equation can be run by two different engines, and it is worth understanding the split because it decides how deep you can zoom and how fast the render is.

Live interpreter CalcGen compiler
Where The editor's live preview + the User Equation fractal type The DSL tab → Compile & Load / Generate via CalcGen
How it runs Interprets the equation directly, one pixel at a time Generates typed C#, compiles it, and hot-loads a real calculator
Speed Good for authoring; scalar only Fast: scalar + AVX2 SIMD + GPU
Deep zoom Shallow (roughly to the limit of double) Deep — perturbation, BLA, Series Approximation, DD/QD high precision
Surface normals / distance estimate No Yes, where the math allows
Language The DSL (this document) The DSL (this document)

Both engines speak the same DSL, with two small dialect differences noted throughout (the live interpreter additionally understands let … in, ?:, && || !, and multi-statement blocks; CalcGen understands if … then … else and integer-only ^). Everything in the function catalogue works in both.

The practical workflow: author in the live editor (instant feedback), then, when you want speed or a deep dive, Compile & Load through CalcGen.

Note

Older versions accepted raw C# that was compiled with full .NET access. That path has been retired for safety. The User Equation tab still accepts C#-style text (return z*z + c;) and automatically translates it to the DSL for you (see §11), but the DSL is now the real language underneath. New equations should be written directly in DSL.


3. The editor at a glance

Open it with toolbar Type → User Equation, then Params. The editor has two input tabs and a live analysis panel.

Control What it does
User Equation tab Accepts DSL or C#-style (return z*z + c;). Runs live on the interpreter and auto-renders as you type. C# is translated to DSL automatically.
DSL tab Bare DSL (z*z + c). Feeds the CalcGen buttons.
Compile & Load CalcGen-compiles the DSL and swaps it onto the live render (unlocks SIMD / GPU / deep zoom / normals).
Compile + Save The same, and stores the equation in your library.
Generate via CalcGen Writes Calculators/Generated/{Name}Calculator.cs for a permanent, build-time calculator.
Validate for CalcGen Parses without rendering — tells you whether the DSL compiles and what paths it will support.
Equation Guide Opens this document.
Rotation° Rotates the sampling plane for display only; the equation is unchanged.

The live analysis panel mirrors what CalcGen deduces from your equation:

Readout Meaning
AST The parsed equation, normalised and pretty-printed.
dz/dz, dz/dc The symbolic derivatives used for normals and distance estimate.
SA Series-Approximation degree (0 = unavailable).
Perturbation Whether deep-zoom perturbation is available.
DE / normals Whether the distance estimate / surface normals are available.
Flags The feature flags (conj, div, trans, cond, …) the equation tripped.

Use the panel as a live gating check: if DE / normals reads off and you wanted lit relief, you know a construct in your equation disabled it — see §9 and §10.


4. Mathematical grammar — how an equation is built

4.1 The complex plane

Every value in the DSL is a complex number a + b·i, where a is the real part and b the imaginary part. A plain number like 2 or 0.5 is the complex value (2, 0) — a real value, which the engine tracks specially so it can skip the dead imaginary half of the arithmetic. The pixel coordinate c and the iterate z are full complex numbers.

i is the imaginary unit (0, 1). So 3 + 4*i is the complex number with real part 3 and imaginary part 4, and i*z rotates z by 90°.

4.2 The iteration

An equation defines one step. The host wraps it in the loop:

z ← 0            (or a seed)
repeat:
    z ← <your equation>          # c is the pixel; z is the running value
    if |z| > bailout: escaped

Because the equation is re-evaluated every iteration, the same short formula produces unlimited detail — the feedback is where the fractal comes from. The Design Guide §1–2 explains the dynamics in depth.

4.3 Real vs complex values

Some functions return a real value lifted back into the complex plane as (value, 0):

  • re(z), im(z), abs(z), arg(z) — extract a real scalar.
  • min, max, mod, clamp, atan2 — operate on real parts.
  • floor, ceil, round, trunc, fract, sign — act per component.

Everything else is fully complex. Mixing is fine: z*z + abs(z) + c adds the complex z*z, the real-lifted abs(z), and the complex c.

4.4 Precedence and associativity

From loosest to tightest binding:

Level Operators Associativity
1 + - left
2 * / left
3 ^ (power) CalcGen: applies to the preceding factor, integer exponent. Live: right-associative, any exponent
4 unary - prefix
5 atoms: numbers, z, c, (…), f(…)

So 2*z^2 is 2*(z^2), and -z*z is -(z*z) is (-z)*z — all the same by sign rules, but be explicit with parentheses when in doubt. a - b - c is (a - b) - c.

Note

Power differs between the two engines. CalcGen's ^ takes an integer exponent 0–64 and applies to the immediately preceding factor (z^3, c^2); for anything else — negative, fractional, or complex exponents — use the pow(base, exp) function. The live interpreter's ^ is right-associative and accepts any exponent (z^2.5, z^-3). For portability across both engines, prefer pow() whenever the exponent is not a small non-negative integer.

4.5 Building an equation, step by step

Start from the Mandelbrot baseline and layer ideas:

z*z + c                       # 1. Mandelbrot
z*z*z + c                     # 2. raise the power → Multibrot
z*z*z - z + c                 # 3. add a linear term → a new critical structure
z*z*z - z + 0.5*conj(z) + c   # 4. mix in a conjugate → break the symmetry

Each change is a hypothesis; Compile & Load (or just watch the live render) is the experiment. Keep the ones that surprise you and Save them with a descriptive name.


5. Language reference — operators

Operator Example Meaning
+ z + c Complex addition
- z - c, -z Complex subtraction; unary negation
* z*z, 2*c Complex multiplication
/ z / (z + 1) Complex division (see gating in §10)
^ z^2, z^3 Power. CalcGen: integer 0–64. Live: any exponent, right-assoc. Prefer pow() for non-integer.
( ) (z + c)*(z - c) Grouping

Comparisons — only inside a condition (if … in CalcGen, ?:/&&/|| live):

Operator Meaning
< <= > >= ordered comparison of real scalars
== != equality / inequality

Boolean operators (live interpreter only): && (and), || (or), ! (not).

Ternary (live interpreter only): cond ? a : b.

Warning

A bare = is not an operator in an expression — use == to compare. In the live interpreter, = only appears in a statement block or a let binding (let k = … in …). In CalcGen, use if cond then … else ….


6. Language reference — the function catalogue

Every function below works in both engines unless noted. Argument counts are fixed; names are case-insensitive.

6.1 Powers and roots

Function Signature Meaning & notes
sqr(x) 1-arg x*x. A convenience; identical to writing x*x (and still deep-zoomable).
pow(x, y) 2-arg General power x^y. If both operands are real, real Math.Pow (so pow(-2, 3) = -8); otherwise the principal complex power, zero-guarded so pow(0, 0) = 1 and pow(0, k) = 0 (no NaN at the z = 0 seed). Use for negative or fractional exponents.
sqrt(x) 1-arg Principal square root. In CalcGen it desugars to exp(0.5*log(x)), matching Complex.Sqrt's branch.
sqr(z) + c                    # = z*z + c
pow(z, 3) + c                 # cubic Multibrot, general-power form
pow(z, -2) + c                # negative power — a "Donut"/inverse map (finite at z=0)
z*pow(z, -3) + c*pow(c, -2)   # "Movie Reel" — mixed inverse powers
pow(z, 2.5) + c               # fractional power (live: z^2.5 also works)
sqrt(z*z - 1) + c             # square-root shell

Note

Why pow(z, -3) instead of 1/z^3? At the Mandelbrot seed z = 0, the form 1/z^3 is 1/0 = NaN and blanks the image; pow(0, -3) is defined as 0 by the zero-guard, so negative-power maps render correctly. Always express negative powers with pow().

6.2 Exponential, logarithm, trigonometry

Function Meaning
exp(x) e^x (complex exponential)
log(x) Natural log, principal branch (pole at 0)
sin(x) cos(x) tan(x) Circular trig (tan = sin/cos)
sinh(x) cosh(x) tanh(x) Hyperbolic (built from exp)
exp(z) + c                    # exponential map
sin(z) + c                    # sinusoidal
log(z*z) + c                  # logarithmic shell
sin(z)*cos(c) + c             # mixed trig "petals"
tanh(z*z) + c                 # hyperbolic
sin(pi*z) + c                 # constants in action

6.3 Inverse trigonometry and hyperbolics

Function Meaning
asin(x) acos(x) atan(x) Inverse circular functions (principal branch)
asinh(x) acosh(x) atanh(x) Inverse hyperbolic functions

These are holomorphic. Surface normals and the distance estimate work for them — their analytic derivative rules are built in (e.g. d/dz asin(u) = u'/√(1 − u²)). They still run on the shallow direct paths (no deep-zoom perturbation), which is where their parity holds.

atan(z) + c                   # bounded inverse-tangent map
asinh(z*z) + c                # chain rule exercised in the normals
z*z + asin(c) + c             # inverse-trig term with full distance estimate

Note

asin, acos, atanh are bounded maps — orbits stay small and may never exceed the bailout, so a solid "all inside the set" image can be the correct result, not a blank. Drive them with an escaping term (e.g. a z*z) if you want classic escape banding.

6.4 Component and rounding functions (per-component)

Applied to the real and imaginary parts independently: f(a + b·i) = f(a) + f(b)·i.

Function Meaning
floor(x) Round toward −∞, each component
ceil(x) Round toward +∞
round(x) Round half-to-even
trunc(x) Round toward zero
fract(x) Fractional part x − floor(x)
sign(x) −1 / 0 / +1 per component
fold(x) `(
fold(z)*fold(z) + c           # Burning Ship
z*z + fract(z) + c            # domain-warped / tiled Mandelbrot
z*z + 0.1*floor(z*4) + c      # quantised feedback ("pixelated" bands)

6.5 Real-valued extractors and reducers

These return a real scalar lifted to (value, 0).

Function Meaning
re(x) Real part
im(x) Imaginary part
abs(x) Magnitude `
arg(x) Principal argument (angle) in (−π, π]
conj(x) Complex conjugate (Re, −Im) — this one stays complex
min(a, b) max(a, b) Min / max of the real parts
mod(x, p) Real modulo, centered, per component
clamp(x, lo, hi) Clamp the real part to [lo, hi]
atan2(y, x) Two-argument arctangent of the real parts
conj(z)*conj(z) + c           # Tricorn
z*z + 0.1*arg(z) + c          # spiral biased by orbit angle
min(z*z, max(z, -1.0)) + c    # clamp-like feedback
z*z + mod(z, 1.0) + c         # periodic wrap
clamp(z, -2.0, 2.0) + c       # bounded feedback

Warning

abs has two meanings depending on where it appears. As a value in an expression, abs(x) is the magnitude |x|. Inside an if condition, the shorthand abs(x) means the squared magnitude |x|² — it saves a square root and matches the bailout-threshold form (if abs(z) > 4 … is really |z|² > 4). If you need the true magnitude inside a condition, compare against the squared threshold, or use re/im explicitly.


7. Constants, variables, and state

Name Meaning Availability
z Current iterate (complex) both engines
c Pixel coordinate (complex) both engines
n / iter Current iteration index (real scalar) both engines
prev The previous iterate z_{n-1} (Phoenix coupling) CalcGen only
pi π both
e Euler's number both
i Imaginary unit (0, 1) both
z*z + c + 0.5*prev            # Phoenix (CalcGen)
z*z + c + 0.001*n             # slow iteration drift
i*z + c                       # 90° rotation via the imaginary unit
sin(pi*z) + e*c               # π and e as literals

Note

prev is only available through the CalcGen path (it needs an extra state slot the live interpreter does not carry). If you author a Phoenix equation, use Compile & Load to see it.


8. Structure: let, statement blocks, and conditionals

8.1 Conditionals

CalcGen uses an if … then … else expression — both branches must produce a value:

if abs(z) < 1 then z*z + c else z*z - c
if re(z) > 0 then z*z + c else conj(z)*conj(z) + c
if im(z) > 0 then z^3 + c else z^2 + c

Each side of the comparison is a single condition term: re(…), im(…), abs(…) (remember: |x|² here), arg(…), or a numeric literal. You cannot combine terms inside a CalcGen condition (re(z)*im(z) > 0 is not valid there, and there is no &&/||). For compound conditions, use the live interpreter's ternary (next section).

The live interpreter additionally supports C-style ternary and boolean operators:

abs(z) < 1 ? z*z + c : z*z - c
(re(z) > 0 && im(z) > 0) ? z^3 + c : z*z + c

8.2 let bindings (live interpreter)

Name a sub-expression and reuse it:

let w = z*z in w*w + c        # z^4 + c, computed once
let r = abs(z) in z*z + r*c   # magnitude-weighted forcing

8.3 Statement blocks (live interpreter)

A saved C#-style equation can be a short sequence of statements — declarations, reassignments, an early guard, and a final return. Each desugars to a let; reassignment shadows the previous binding. There are still no loops, no braces, and no side effects.

var w = z*z;
var d = w - 1;
return w*w + d + c;
if (n == 0) z = c;            // seed on the first iteration
return z*z + c;

Blocks are how legacy multi-line C# equations keep working; when authoring fresh, a single DSL expression (optionally with let) is usually clearer.


9. Holomorphic vs non-holomorphic — why it matters

A function is holomorphic (complex-differentiable) if it bends the plane smoothly without folding or reflecting it. Holomorphicity is not academic here — it decides which rendering features your equation can use:

  • Holomorphic (polynomials, exp, log, sin/cos/tan, sinh/cosh/tanh, sqrt, inverse trig, division): the app can track dz/dc analytically, so surface normals and the distance estimate work, and (for polynomials) the deep-zoom accelerators are available.
  • Non-holomorphic (conj, fold, re, im, abs, arg, atan2, min, max, mod, clamp, the per-component rounding functions): these fold or reflect the plane. They render beautifully, but the derivative chain becomes meaningless, so the distance estimate / normals turn off for equations that use them.

The one deliberate exception the app makes: pow and the per-component functions are transcendental/kinked, so they disable the deep-zoom accelerators and the distance estimate, while the inverse-trig functions keep the distance estimate (their derivatives are known) even though they too disable deep-zoom perturbation.

The live analysis panel's DE / normals and Perturbation readouts tell you exactly what your current equation supports.


10. Execution paths and what gates them

CalcGen emits every path and the runtime picks the best for the current view. Some constructs switch specific paths off:

Construct in your equation Scalar AVX2 Perturbation BLA / SA DE (normals) DD/QD deep GPU
Polynomial in z (+ c)
Division /
conj / fold
exp log sin cos tan sinh cosh tanh sqrt ✓ (degraded)
asin acos atan asinh acosh atanh ✓ (degraded)
pow ✓ (degraded)
re im abs arg atan2 min max mod clamp
floor round ceil trunc fract sign
if … then … else ✓ per branch
prev (Phoenix)
n / iter

Rules of thumb:

  • Stay polynomial in z (+ c) to keep every accelerator — that is the only shape that gets perturbation + BLA + Series Approximation, so it is the one you can dive into past 10¹⁵.
  • Transcendental holomorphic functions keep the distance estimate (so you can light them as relief) but drop deep-zoom perturbation.
  • Inverse trig is the sweet spot for lit non-polynomial art: normals on, perturbation off.
  • The status bar shows the active path (SP / AVX2 / PT / DD-HP / QD-PT).

11. Migrating C# equations to the DSL

The User Equation tab still accepts C#-style text and translates it automatically; the table below is the exact mapping so you can convert by hand or understand what the translator did. Write new equations in DSL directly.

C# form DSL form
return z*z + c; z*z + c
Complex.Conjugate(z) conj(z)
Complex.Pow(z, 3) z^3 (or pow(z, 3))
Complex.Pow(z, -3) pow(z, -3)
Complex.Pow(z, expr) pow(z, expr)
Complex.Sqrt(z) sqrt(z)
Complex.Divide(a, b) (a)/(b)
Complex.Sin/Cos/Tan/Exp/Log(z) sin/cos/tan/exp/log(z)
Complex.ImaginaryOne i
new Complex(a, b) (a + (b)*i)
new Complex(a, 0) a
z.Real re(z)
z.Imaginary im(z)
z.Magnitude abs(z) (or sqrt(z*conj(z)))
z.Phase arg(z)
Math.Abs(x) abs(x)
Math.PI / Math.E pi / e
Complex.Zero / Complex.One 0 / 1

Worked conversions:

// Tricorn — C#:  var zb = Complex.Conjugate(z); return zb*zb + c;
conj(z)*conj(z) + c

// Hybrid — C#:  return z*z + c + 0.25*Complex.Conjugate(z);
z*z + c + 0.25*conj(z)

// Newton-ish — C#:  return z - Complex.Divide(z*z*z - 1, 3*z*z);
z - (z*z*z - 1)/(3*z*z)

Note

Saved equations from older versions are migrated to DSL automatically on first launch, with a timestamped backup of your library kept alongside. Your edits are preserved; only exact unmodified legacy sources are rewritten.


12. The Cookbook

Each recipe is a complete equation you can paste into the DSL tab. Grouped by family; every one is DSL (no C#).

12.1 The Mandelbrot / Multibrot family (deep-zoomable)

z*z + c                       # classic Mandelbrot
z^3 + c                       # cubic Multibrot
z^4 + c
z^5 + c
z^6 + c
z^3 - z + c                   # cubic with a linear term
z*z + 0.5*z + c               # quadratic with linear coupling
(z^4 + z^2)/2 + c             # mixed-degree (division → shallow only)
i*z*z + c                     # complex leading coefficient
0.5*z*z + 0.3*i*z + c         # mixed real/imaginary coefficients

12.2 Anti-holomorphic — Burning Ship, Tricorn, hybrids

fold(z)*fold(z) + c           # Burning Ship
conj(z)*conj(z) + c           # Tricorn (Mandelbar)
conj(fold(z))^2 + c           # Burning-Tricorn hybrid
fold(z)^3 + c                 # cubic Burning Ship
z*z + c + 0.25*conj(z)        # Mandelbrot / Tricorn blend

12.3 Rational maps (division; shallow-but-deep via DD/QD)

(z*z - 1)/(z + 1) + c         # Mandelbrot-on-a-shell
z - (z*z*z - 1)/(3*z*z)       # Newton-shaped iteration
(z*z + c)/(1 + 0.1*z)         # damped feedback with a c-independent pole
1/(z*z) + c                   # inverse-square (prefer pow(z,-2)+c)

12.4 Powers via pow (negative / fractional)

pow(z, -2) + c                # "Donut" inverse map (finite at z=0)
z*pow(z, -3) + c*pow(c, -2)   # "Movie Reel"
pow(z, 2.5) + c               # fractional Multibrot
pow(z, 3) + pow(z, -1) + c    # mixed positive/negative powers

12.5 Transcendental — exp / log / trig (lit relief works)

exp(z) + c
sin(z) + c
cos(z)*z + c
log(z*z) + c
sin(z)*cos(c) + c
0.5*z + sin(z) + c            # damped oscillator
tan(z) + c
sinh(z) + c
cosh(z) - 0.5*c
tanh(z*z) + c
sqrt(z) + c
sqrt(z*z - 1) + c
sin(pi*z) + c
e*z*z + c

12.6 Inverse trig / hyperbolic (normals on)

atan(z) + c                   # bounded; drive harder for banding
z*z + asin(c) + c             # escaping term + inverse-trig, with normals
asinh(z*z) + c
z*z + 0.3*atan(z) + c         # gentle inverse-tangent forcing
acosh(z) + c

12.7 Phoenix family (prev; Compile & Load)

z*z + c + 0.5*prev            # classic Phoenix
z*z - 0.5*prev + c            # negative feedback
z^3 + 0.4*prev + c            # cubic Phoenix
z*z + 0.3*prev - 0.1*prev*prev + c   # two-tap Phoenix

12.8 Component / rounding (domain warps, quantisation)

z*z + fract(z) + c            # tiled / Kali-style warp
z*z + 0.1*floor(z*4) + c      # quantised bands
z*z + 0.2*sign(re(z)) + c     # sign-driven asymmetry
z*z + round(z) - z + c        # snap-to-lattice feedback

12.9 Argument / angle driven

z*z + 0.1*arg(z) + c          # spiral bias by orbit angle
z*z + 0.05*atan2(z, c) + c    # branch by relative angle
if arg(z) > 0 then z*z + c else z*z - c   # phase-split map

12.10 Real reducers — min / max / mod / clamp

min(z*z, max(z, -1.0)) + c    # clamped feedback
z*z + mod(z, 1.0) + c         # periodic wrap
max(z*z, sqr(z)) + c          # hybrid step
clamp(z, -2.0, 2.0) + c       # bounded orbit
z*z + clamp(re(z), -1.0, 1.0)*i + c   # clamp only the real drive

12.11 Conditional / piecewise

if abs(z) < 1 then z*z + c else z*z - c
if re(z) > 0 then z*z + c else conj(z)*conj(z) + c
if im(z) > 0 then z^3 + c else z^2 + c

Live-interpreter ternary equivalents — and compound conditions the CalcGen if cannot express (a product of terms, &&/||):

abs(z) < 1 ? z*z + c : z*z - c
(re(z) > 0 && im(z) > 0) ? z^3 + c : z*z + c
re(z)*im(z) > 0 ? z^3 + c : z^2 + c

12.12 Iteration-aware (n / iter)

z*z + c + 0.001*n             # slow drift (breaks scale invariance — by design)
sin(z + 0.01*n) + c           # phase that winds with depth
(1 - n*0.001)*(z*z) + n*0.001*(z^3) + c   # crossfade quadratic→cubic
if (n mod 4) < 2 then z*z + c else z*z - c   # alternate every few iters

12.13 let and blocks (live interpreter)

let w = z*z in w*w + c        # z^4, computed once
let r = abs(z) in z*z + 0.2*r*c
var w = z*z;
var d = w*w - w;
return d + c;

13. Editor workflow

Authoring loop

  1. Type → User Equation, then Params.
  2. Type in the User Equation tab (DSL or C#-style) — it renders live.
  3. When you like it, switch to the DSL tab, click Validate for CalcGen to confirm it compiles and see which paths it supports, then Compile & Load for the fast/deep engine.
  4. Compile + Save (or Save…) to store it in your library (%APPDATA%\FracturingFog\userequations.json).
  5. Promote to fractal list to surface it in the Type dropdown next launch.

Compile & Load vs Generate via CalcGen

Path When to use
Compile & Load Iterative authoring; preview an idea with the full engine in one click.
Generate via CalcGen Promote a keeper into a permanent C# calculator that builds into the app.

Generated files land at Calculators/Generated/{Name}Calculator.cs (plus a self-test); the next dotnet build compiles them in and the calculator appears under Type → "{Name} (Generated)".

Compile feedback

The status line shows ✓ Compiled (a green/neutral confirmation) or the parse error with line, col. Live re-compile debounces about half a second after you stop typing. Advisory messages render in yellow (#FFCC00), never red.


14. Command-line generation

dotnet build CalculatorGen\CalculatorGen.csproj -c Release
dotnet run --project CalculatorGen -c Release -- `
    --equation "z*z + c" `
    --name MandelbrotZ2 `
    --out Calculators\Generated `
    --selftest

Flags:

  • --equation "..." — the RHS only (no z_{n+1} =).
  • --name <Name> — base class name (Calculator suffix added automatically).
  • --out <dir> — output directory (default: current directory).
  • --selftest — also emit {Name}CalculatorSelfTest.cs.
  • --bailout <R> — bailout radius (default 512; the generator squares it, so --bailout 512 means "escape when |z|² ≥ 262144"). Raise it for smoother transcendental gradients; drop it to 2 for the classic Mandelbrot contract.

15. Troubleshooting

Symptom Likely cause / fix
Unknown identifier 'X' Typo. The allowed names are the operators, the functions in §6, the constants pi e i, and the variables z c n/iter prev. The lexer suggests the closest match.
Unexpected '=' Use == to compare. = only appears in a let/statement-block binding.
Exponent … must be a non-negative integer ≤ 64 CalcGen's ^ caps at 64 and takes integer exponents. Use pow(x, y) for anything else.
Expected ',' … after a function name Multi-arg functions (pow, atan2, min, max, mod, clamp) need their arguments comma-separated.
Equation is empty The editor is blank after stripping return/;.
DE / normals reads off when you wanted relief A non-holomorphic construct (conj, fold, re/im/abs/arg, min/max/mod/clamp, per-component, pow, prev, n) disabled it — see §9.
Deep zoom drops to scalar / DD A non-polynomial construct disabled perturbation; only z^d + c-shaped maps deep-zoom with acceleration.
A bounded map (asin, atanh) renders all-inside That is correct — those orbits never escape. Add an escaping term (z*z).
Negative-power map is blank Use pow(z, -k) (zero-guarded), not 1/z^k (NaN at z = 0).

16. Reference card

Variables / state   z   c   n (or iter)   prev (CalcGen)
Constants           pi   e   i                     (i = imaginary unit (0,1))
Operators           +  -  *  /  ^          (^ = int 0..64 in CalcGen; any exp live)
Compare (in cond)   <  <=  >  >=  ==  !=
Boolean (live)      &&  ||  !
Conditional         CalcGen: if <cmp> then <expr> else <expr>
                    Live:    <cmp> ? <expr> : <expr>
Binding (live)      let <name> = <expr> in <expr>   |   statement blocks

Powers / roots      sqr(x)  pow(x,y)  sqrt(x)
Exp / log / trig    exp log sin cos tan sinh cosh tanh
Inverse trig        asin acos atan asinh acosh atanh          (normals ON)
Per-component       floor round ceil trunc fract sign  fold
Real extractors     re(x) im(x) abs(x) arg(x)  conj(x)
Real reducers       min(a,b) max(a,b) mod(x,p) clamp(x,lo,hi) atan2(y,x)

Reminders:

  • abs = magnitude |x| in an expression, but |x|² inside an if condition.
  • Prefer pow() over ^ whenever the exponent is not a small non-negative integer, so the equation behaves the same on both engines.
  • Stay polynomial in z (+ c) to keep deep zoom + normals.

17. See also

Clone this wiki locally