fix: add c parameter again for lambda fractal in julia mode

This commit is contained in:
2026-09-25 10:06:01 +02:00
parent 0d86e7b0e0
commit fbf0ef64af
3 changed files with 40 additions and 59 deletions
+1 -1
View File
@@ -2142,7 +2142,7 @@ impl FractalApp {
return; return;
} }
if self.mode == FractalMode::Julia && self.kind != FractalKind::Lambda { if self.mode == FractalMode::Julia {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("c ="); ui.label("c =");
ui.add( ui.add(
+2 -2
View File
@@ -26,7 +26,7 @@ pub enum FractalKind {
Buffalo = 6, Buffalo = 6,
/// `z -> z^2 + c + p·z_{n-1}` (two-term recurrence; `p` is `phoenix_p`). /// `z -> z^2 + c + p·z_{n-1}` (two-term recurrence; `p` is `phoenix_p`).
Phoenix = 7, Phoenix = 7,
/// `z -> lambda·z(1 - z)` (logistic map). /// `z -> lambda·z(1 - z) + c` (logistic map).
Lambda = 8, Lambda = 8,
/// `z -> z^power + c`, where `power` is a complex constant (the /// `z -> z^power + c`, where `power` is a complex constant (the
/// `complex_power` argument), via the principal branch `z^p = exp(p·ln z)`. /// `complex_power` argument), via the principal branch `z^p = exp(p·ln z)`.
@@ -104,7 +104,7 @@ impl FractalKind {
FractalKind::Perpendicular => "z = (x² − y²) − 2x|y|i + c".to_string(), FractalKind::Perpendicular => "z = (x² − y²) − 2x|y|i + c".to_string(),
FractalKind::Buffalo => "z = |Re(z²)| − i|Im(z²)| + c".to_string(), FractalKind::Buffalo => "z = |Re(z²)| − i|Im(z²)| + c".to_string(),
FractalKind::Phoenix => "z = z² + c + p·z_prev".to_string(), FractalKind::Phoenix => "z = z² + c + p·z_prev".to_string(),
FractalKind::Lambda => "z = λ·z(1 − z)".to_string(), FractalKind::Lambda => "z = λ·z(1 − z) + c".to_string(),
FractalKind::ComplexMultibrot => { FractalKind::ComplexMultibrot => {
format!("z = z^({:.3}{:+.3}i) + c", complex_power.0, complex_power.1) format!("z = z^({:.3}{:+.3}i) + c", complex_power.0, complex_power.1)
} }
+36 -55
View File
@@ -59,41 +59,6 @@ pub fn compute_reference(
lambda_l: (f64, f64), lambda_l: (f64, f64),
complex_power: (f64, f64), complex_power: (f64, f64),
morph: Option<(FractalKind, f64)>, morph: Option<(FractalKind, f64)>,
) -> Vec<[f32; 2]> {
compute_reference_inner(
z0_re,
z0_im,
c_re,
c_im,
max_iter,
precision,
kind,
power,
phoenix_p,
lambda_l,
complex_power,
morph,
false,
)
}
/// [`compute_reference`] plus `set_plane` (see [`StepConsts::set_plane`]):
/// picks the `f64` fast path or the `FBig` path by precision.
#[allow(clippy::too_many_arguments)]
fn compute_reference_inner(
z0_re: &Big,
z0_im: &Big,
c_re: &Big,
c_im: &Big,
max_iter: u32,
precision: usize,
kind: FractalKind,
power: u32,
phoenix_p: (f64, f64),
lambda_l: (f64, f64),
complex_power: (f64, f64),
morph: Option<(FractalKind, f64)>,
set_plane: bool,
) -> Vec<[f32; 2]> { ) -> Vec<[f32; 2]> {
// A zero-weight morph is just the plain kind; skip the second formula. // A zero-weight morph is just the plain kind; skip the second formula.
let morph = morph.filter(|&(_, w)| w != 0.0); let morph = morph.filter(|&(_, w)| w != 0.0);
@@ -104,7 +69,6 @@ fn compute_reference_inner(
l: lambda_l, l: lambda_l,
cpow: complex_power, cpow: complex_power,
power, power,
set_plane,
}; };
return compute_reference_f64( return compute_reference_f64(
(z0_re.to_f64().value(), z0_im.to_f64().value()), (z0_re.to_f64().value(), z0_im.to_f64().value()),
@@ -125,7 +89,6 @@ fn compute_reference_inner(
cpow_im: big_from_f64(complex_power.1, precision), cpow_im: big_from_f64(complex_power.1, precision),
power, power,
precision, precision,
set_plane,
}; };
compute_reference_big(z0_re, z0_im, max_iter, kind, &k, morph) compute_reference_big(z0_re, z0_im, max_iter, kind, &k, morph)
} }
@@ -137,7 +100,6 @@ struct StepConstsF64 {
l: (f64, f64), l: (f64, f64),
cpow: (f64, f64), cpow: (f64, f64),
power: u32, power: u32,
set_plane: bool,
} }
/// [`compute_reference`]'s fast path for shallow views (see /// [`compute_reference`]'s fast path for shallow views (see
@@ -204,16 +166,11 @@ fn step_f64(
) )
} }
FractalKind::Lambda => { FractalKind::Lambda => {
// λ·z(1 - z) (+ c on the parameter plane). // λ·z(1 - z) + c.
let (lr, li) = k.l; let (lr, li) = k.l;
let (re2, im2) = (1.0 - zr, -zi); let (re2, im2) = (1.0 - zr, -zi);
let (lzr, lzi) = (lr * zr - li * zi, lr * zi + li * zr); let (lzr, lzi) = (lr * zr - li * zi, lr * zi + li * zr);
let (re, im) = (lzr * re2 - lzi * im2, re2 * lzi + lzr * im2); (lzr * re2 - lzi * im2 + cr, re2 * lzi + lzr * im2 + ci)
if k.set_plane {
(re + cr, im + ci)
} else {
(re, im)
}
} }
FractalKind::ComplexMultibrot => { FractalKind::ComplexMultibrot => {
let (pr, pi) = complex_pow_complex_f64(zr, zi, k.cpow.0, k.cpow.1); let (pr, pi) = complex_pow_complex_f64(zr, zi, k.cpow.0, k.cpow.1);
@@ -250,9 +207,6 @@ struct StepConsts {
cpow_im: Big, cpow_im: Big,
power: u32, power: u32,
precision: usize, precision: usize,
/// Parameter plane: the GPU adds `dc` every step for every kind, so the
/// Lambda map (which has no `c` of its own) is `λ·z(1 - z) + c` there.
set_plane: bool,
} }
/// [`compute_reference`] at arbitrary precision (`FBig`), for deep views. /// [`compute_reference`] at arbitrary precision (`FBig`), for deep views.
@@ -369,18 +323,14 @@ fn step(
(re2 + cr + pzr, im2 + ci + pzi) (re2 + cr + pzr, im2 + ci + pzi)
} }
FractalKind::Lambda => { FractalKind::Lambda => {
// λ·z(1 - z): logistic map (+ c on the parameter plane). // λ·z(1 - z) + c: logistic map plus the usual additive `c`.
let re2 = 1 - zr; let re2 = 1 - zr;
let im2 = -zi; let im2 = -zi;
let lzr = &k.lr * zr - &k.li * zi; let lzr = &k.lr * zr - &k.li * zi;
let lzi = &k.lr * zi + &k.li * zr; let lzi = &k.lr * zi + &k.li * zr;
let re = &lzr * &re2 - &lzi * &im2; let re = &lzr * &re2 - &lzi * &im2;
let im = re2 * lzi + lzr * im2; let im = re2 * lzi + lzr * im2;
if k.set_plane {
(re + cr, im + ci) (re + cr, im + ci)
} else {
(re, im)
}
} }
FractalKind::ComplexMultibrot => { FractalKind::ComplexMultibrot => {
let (pr, pi) = complex_pow_complex(zr, zi, &k.cpow_re, &k.cpow_im, k.precision); let (pr, pi) = complex_pow_complex(zr, zi, &k.cpow_re, &k.cpow_im, k.precision);
@@ -455,7 +405,7 @@ pub fn compute_set_reference(
morph: Option<(FractalKind, f64)>, morph: Option<(FractalKind, f64)>,
) -> Vec<[f32; 2]> { ) -> Vec<[f32; 2]> {
let zero = big_zero(precision); let zero = big_zero(precision);
compute_reference_inner( compute_reference(
&zero, &zero,
&zero, &zero,
center_re, center_re,
@@ -468,7 +418,6 @@ pub fn compute_set_reference(
lambda_l, lambda_l,
complex_power, complex_power,
morph, morph,
true,
) )
} }
@@ -680,6 +629,38 @@ mod tests {
} }
} }
/// Lambda Julia orbit adds the Julia `c`: `z -> λ·z(1 - z) + c`.
#[test]
fn lambda_julia_reference_matches_naive_f64() {
let (lr, li) = (-0.5_f64, 0.2_f64);
let (cr, ci) = (0.1_f64, -0.3_f64);
let points = compute_reference(
&Big::try_from(0.2_f64).unwrap(),
&Big::try_from(0.1_f64).unwrap(),
&Big::try_from(cr).unwrap(),
&Big::try_from(ci).unwrap(),
60,
200,
FractalKind::Lambda,
2,
(0.0, 0.0),
(lr, li),
(0.0, 0.0),
None,
);
let (mut zr, mut zi) = (0.2_f64, 0.1_f64);
for point in &points {
let tol = 1e-4 * (1.0 + zr.abs().max(zi.abs()));
assert!((point[0] as f64 - zr).abs() < tol, "{point:?} vs {zr}");
assert!((point[1] as f64 - zi).abs() < tol, "{point:?} vs {zi}");
let (lzr, lzi) = (lr * zr - li * zi, lr * zi + li * zr);
let (ar, ai) = (1.0 - zr, -zi);
zr = lzr * ar - lzi * ai + cr;
zi = lzr * ai + lzi * ar + ci;
}
}
/// Celtic reference matches a naive f64 iteration: real = |x^2 - y^2| + cr. /// Celtic reference matches a naive f64 iteration: real = |x^2 - y^2| + cr.
#[test] #[test]
fn celtic_reference_matches_naive_f64() { fn celtic_reference_matches_naive_f64() {