Crate julia_set

source ·
Expand description

Julia set boundary computation and rendering.

§Theory

Informally, the Julia set for a complex-valued function f (in Rust terms, fn(Complex32) -> Complex32) is a set of complex points for which an infinitely small perturbation can lead to drastic changes in the sequence of iterated function applications (that is, f(z), f(f(z)), f(f(f(z))) and so on).

For many functions f, the iterated sequence may tend to infinity. Hence, the commonly used computational way to render the Julia set boundary is as follows:

  1. For each complex value z within a rectangular area, perform steps 2-3.
  2. Compute the minimum iteration 0 < i <= MAX_I such that |f(f(f(...(z)))| > R. Here, f is applied i times; R is a positive real-valued constant (the infinity distance); MAX_I is a positive integer constant (maximum iteration count).
  3. Associate z with a color depending on i. For example, i == 1 may be rendered as black, i == MAX_I as white, and values between it may get the corresponding shades of gray.
  4. Render the rectangular area as a (two-dimensional) image, with each pixel corresponding to a separate value of z.

This is exactly the way Julia set rendering is implemented in this crate.

§Backends

The crate supports several computational Backends.

BackendCrate featureHardwareCrate dep(s)
OpenClopencl_backendGPU, CPUocl
Vulkanvulkan_backendGPUvulkano, shaderc
Cpucpu_backendCPUrayon
Cpudyn_cpu_backendCPUrayon

None of the backends are on by default. A backend can be enabled by switching on the corresponding crate feature. dyn_cpu_backend requires cpu_backend internally.

All backends except for cpu_backend require parsing the complex-valued Function from a string presentation, e.g., "z * z - 0.4i". The arithmetic-parser crate is used for this purpose. For cpu_backend, the function is defined directly in Rust.

For efficiency and modularity, a Backend creates a program for each function. (In case of OpenCL, a program is a kernel, and in Vulkan a program is a compute shader.) The program can then be Rendered with various Params.

Backends targeting GPUs (i.e., OpenCl and Vulkan) should be much faster than CPU-based backends. Indeed, the rendering task is embarrassingly parallel (could be performed independently for each point).

§Examples

Using Rust function definition with cpu_backend:

use julia_set::{Backend, Cpu, Params, Render};
use num_complex::Complex32;

let program = Cpu.create_program(|z: Complex32| z * z + Complex32::new(-0.4, 0.5))?;
let render_params = Params::new([50, 50], 4.0).with_infinity_distance(5.0);
let image = program.render(&render_params)?;
// Do something with the image...

Using interpreted function definition with dyn_cpu_backend:

use julia_set::{Backend, Cpu, Function, Params, Render};
use num_complex::Complex32;

let function: Function = "z * z - 0.4 + 0.5i".parse()?;
let program = Cpu.create_program(&function)?;
let render_params = Params::new([50, 50], 4.0).with_infinity_distance(5.0);
let image = program.render(&render_params)?;
// Do something with the image...

Modules§

  • Post-processing transforms on Julia set images.

Structs§

Traits§

  • Backend capable of converting an input (the type parameter) into a program. The program then can be used to Render the Julia set with various rendering Params.
  • Complex-valued function of a single variable.
  • Program for a specific Backend (e.g., OpenCL) corresponding to a specific Julia set. A single program can be rendered with different parameters (e.g., different output sizes), but the core settings (e.g., the complex-valued function describing the set) are fixed.

Type Aliases§