Settings¶
Algan exposes one process-global settings root named algan.SETTINGS. Its sections are grouped by lifecycle and responsibility:
SETTINGS.videoDefault resolution, frame rate, anti-aliasing, and related output settings.
SETTINGS.styleDefault colors, frame/background behavior, shaders, and scene-end style.
SETTINGS.pathsOutput and content-cache paths.
SETTINGS.computingRuntime-adjustable memory budgets and authoring controls.
SETTINGS.raytracingWhat the renderer produces: sampling, bounces, shadows, lighting and tonemapping. Internal performance switches live on
SETTINGS.raytracing.experimental.
Mutating live settings¶
Section objects keep stable identity. Modify them in place with set rather
than assigning a replacement object:
SETTINGS.video.set(frames_per_second=60)
SETTINGS.paths.set(output_directory="renders")
SETTINGS.raytracing.set(samples_per_pixel=4)
Assigning a single field is the same operation, so use whichever reads better – both validate the value and reject a bad one on the spot:
SETTINGS.video.frames_per_second = 60 # identical to the above
This is valid:
SETTINGS.video.set(HD)
It copies every field from the HD preset into the live video settings
section. Keyword arguments may override copied fields:
SETTINGS.video.set(HD, frames_per_second=60)
This is intentionally invalid because replacing a section would break code that retains a reference to it:
SETTINGS.video = HD # raises AlganConfigurationError
Short spellings¶
Two video settings have long names for something written this often, so each
takes a short alias as well – fps or FPS for frames_per_second, and
ssaa or SSAA for supersampling:
SETTINGS.video.set(fps=60, ssaa=1)
SETTINGS.video.FPS = 60
draft = HD.set(fps=24, SSAA=1)
An alias is a second spelling, not a second setting: it reads back the same
value, and a snapshot or to_dict() always answers with the declared name, so
state saved through one spelling restores through the other. Naming the same
setting twice in one call (set(fps=60, frames_per_second=30)) is rejected
rather than resolved to whichever came last.
Supported settings versus experimental switches¶
SETTINGS.raytracing exposes the settings that change what a render looks
like:
SETTINGS.raytracing.set(samples_per_pixel=4) # path-traced quality
SETTINGS.raytracing.set(max_bounces=6)
SETTINGS.raytracing.set(shadows=True)
SETTINGS.raytracing.set(tonemap_exposure=1.2)
SETTINGS.raytracing.set(denoise=False) # raw path-traced output
The renderer also carries a large number of performance and capability
switches — kernel fusion, rasterization gates, memory ratios, and so on. Those
are real, but their names and behaviour follow the current kernels and can
change between releases, so they live behind experimental:
SETTINGS.raytracing.experimental.set(hybrid_raster=False)
Setting one of them on SETTINGS.raytracing directly raises an error telling
you where it lives. dir(SETTINGS.raytracing) lists only the supported
settings, and dir(SETTINGS.raytracing.experimental) lists the rest.
Immutable presets¶
Built-in video constants such as PREVIEW and HD are immutable presets.
Calling set on a preset returns a modified preset copy:
portrait_hd = HD.set(resolution=(1080, 1920))
assert portrait_hd.resolution == (1080, 1920)
assert HD.resolution == (1920, 1080)
The same distinction applies to captured ray-tracing presets. Mutable sections change in place; presets return new values.
Temporary overrides¶
Use a section override for one section:
with SETTINGS.video.override(frames_per_second=12):
Scene.save_video("draft.mp4")
Use the root override for multiple sections:
with SETTINGS.override(
video={"resolution": (640, 360), "frames_per_second": 12},
raytracing={"samples_per_pixel": 1},
):
Scene.save_video("fast_preview.mp4")
Both forms restore the previous settings even when the body raises an exception.
For longer-lived save/restore flows, use snapshot = SETTINGS.snapshot() and
SETTINGS.restore(snapshot).
Per-render settings¶
A Scene captures its current video settings when it is constructed. You can also provide settings explicitly:
with Scene(video_settings=PREVIEW) as scene:
Square().spawn()
scene.save_video("preview.mp4", HD)
The override applies to that render only; afterwards the Scene returns to its
own settings. save_frame takes the same argument and likewise restores
every derived render value after the still has been written.
This per-render form is usually what you want, because it has no ordering
constraints. SETTINGS.video is read when a Scene is constructed, and
Algan creates its default Scene as soon as you build your first Mob, so a
SETTINGS.video.set(...) placed after that point will not affect the Scene
that is already running. Either set it at the top of your script, or pass the
settings to save_video.
Style defaults¶
SETTINGS.style holds the defaults a Scene picks up when you do not say
otherwise. Unlike the other sections it is mostly about authoring, so its
fields are the ones you set once at the top of a script:
backgroundThe color behind everything, as an Algan
Color. Defaults toBLACK. This is the process-wide default;Scene.set_background(...)changes one Scene and thebackground=argument tosave_video()changes one render, each overriding the one before it. It also accepts an image path orTRANSPARENT– see Backgrounds and Post-Processing and Transparent Backgrounds.frameColor of the letterbox area outside the rendered frame, when the output aspect ratio does not fill the canvas. Defaults to
BLACK.text_colorbufferDefault gap, in world units, left by the layout methods (
move_next_to,arrange_in_line,arrange_in_grid). Defaults to0.6. Must be finite and non-negative.fade_out_on_scene_endWhether a render fades everything out at the end. Defaults to
False.save_video(animate_fade_out=...)overrides it per render.default_materialThe material a 3-D Mob is shaded with when it sets none of its own. Defaults to
DiffuseMaterial(Lambert diffuse), installed when Algan is imported;use_manim_defaults()replaces it withManimMaterial, so imported 3-D geometry shades the way Manim shades it. Must be aMaterialinstance – a value without a.shaderattribute is rejected – and flat 2-D content never consults it. See Shaders and Materials.shape_style_profileWhose per-shape styling defaults the built-in shapes adopt:
"algan"(the default) keeps Algan’s own – a red filledSquarewith a wide white border, say – while"manim"asks them to adopt Manim Community’s constructor defaults instead (an unfilled whiteSquareoutline of stroke width 4). Enabling it reads each shape’s defaults out of the installedmanimpackage once, so the values follow whatever version you have; a shape Manim does not define simply keeps Algan’s default. An explicit keyword always wins over the profile:Square(color=BLUE)is blue either way.
SETTINGS.style.set(shape_style_profile="manim") # Manim-looking shapes
SETTINGS.style.set(shape_style_profile="algan") # back to Algan's
border_placementWhere a filled shape lays its border relative to its outline.
"inward"(the default) puts the whole stroke inside, so raisingstroke_widtheats into the shape rather than growing it and neighbouring glyphs never fuse."centered"straddles the outline the way Manim and SVG’s defaultstrokedo, so half the width spills outside and the silhouette grows with the stroke.use_manim_defaults()selects"centered", since an inward stroke puts an imported Manim shape’s silhouette half a stroke width in from where Manim draws it. Unfilled shapes – aLine, an openCircle– are centred under both: an open path has no interior to lay a stroke inside of.
SETTINGS.style.set(border_placement="centered") # Manim/SVG stroke
manim_stroke_width_ratioManim stroke-width units per Algan unit, used by every conversion in the Manim compatibility layer – import, export and the shape adapters alike, so a round trip returns the width it started with. Defaults to
2.0, Algan’s stated convention that “Manim’s number is twice Algan’s”.use_manim_defaults()swaps in the exact figure,2.0202(manim_stroke_width_ratio()), which is the ratio that actually draws the same number of pixels Manim draws; the two would agree ifPREVIEWwere 400 px tall rather than 396, so the round convention is 1.01% off rather than wrong in kind.
SETTINGS.style.set(background=Color([0.05, 0.05, 0.15]), buffer=0.3)
Choosing the render device¶
SETTINGS.computing.render_device is where a render’s primitives are built
and the ray tracer runs. It accepts a device string, a torch.device, or
'auto' (CUDA, then MPS, then CPU), and it starts at whatever
ALGAN_RENDER_DEVICE said – also auto by default.
from algan import *
SETTINGS.computing.set(render_device="cpu") # or "cuda", "cuda:1", "auto"
Square(color=RED).spawn()
Scene.save_video("on_the_cpu")
Set it at the top of the script, before creating any Mob. It can be changed between renders, but the change is not free: Taichi’s compute backend is chosen from it, so crossing the CPU/GPU line discards every kernel compiled so far and the next render pays a fresh kernel-preparation pass.
Two changes are refused rather than silently mishandled:
While a render is running. Batch preparation launches kernels on a worker thread, so a change mid-render could pull the backend out from under it.
Once a textured Mob exists. A texture is wide enough that its per-frame window is allocated on the render device when the Mob is created, and nothing re-asks afterwards. Choose the device before creating one.
Fusing the pipeline’s arithmetic with torch.compile¶
Between the ray-tracing kernels, a render is a long chain of small PyTorch
operations: the timeline materialized at every frame, vertices projected and
shaded, the analytic-coverage fragments compacted into sheets, frames
post-processed. Eager PyTorch pays a dispatch and a full pass over memory for
each one. SETTINGS.computing.torch_compile runs those chains through
torch.compile instead, which fuses each into a single kernel:
from algan import *
SETTINGS.computing.set(torch_compile=False) # or True, or "auto"
"auto", the default, is on wherever torch.compile is supported and off
where it is not – Windows, and any Python version PyTorch’s compiler does not
yet support. True tries regardless; False is off everywhere. The
environment variable ALGAN_TORCH_COMPILE overrides the field, and
algan check reports what it resolved to.
Three things to know:
The first render of a process is slower, every later one faster. Each compiled function is built on its first call – seconds apiece on a CPU, cached on disk by PyTorch so a later process starts warmer – and that cost lands on the first frames of the first render. A script that renders once and exits may not recoup it; a session that renders repeatedly, the interactive viewer, and any longer video do.
It can never fail a render. A function whose compile fails on your machine – no C++ compiler on the path, an operation the backend cannot lower – warns once, naming the function and the reason, and runs eagerly from then on. On an Apple GPU this is the common case for now: PyTorch 2.7’s Metal backend is a prototype, and a function it cannot build simply keeps running eagerly on the GPU.
Output is unchanged to within the rounding the render suites already tolerate. Fused arithmetic keeps the operation order; only transcendental functions may differ by a unit in the last place.
Initialization-only configuration¶
Some configuration affects Torch/Taichi process initialization and therefore must be set through environment variables before importing Algan. It has no runtime Python settings object.
Supported initialization variables include:
ALGAN_ANIMATION_DEVICETorch device used for animation materialization. The default is
cpu. Unlike the render device this one cannot change: every Mob’s authoring state is allocated on it from the first one onward.ALGAN_HOMEandALGAN_CACHE_DIRBase and content-cache locations.
TI_OFFLINE_CACHE_FILE_PATHOffline kernel-cache location for the compiler. Unset, each compiler gets its own directory under
ALGAN_CACHE_DIR(cache/quadrants,cache/taichi), so switching between them does not invalidate either.ALGAN_TAICHI_BACKENDWhich compiler builds Algan’s kernels:
quadrants(the default, and whatpip install alganbrings) ortaichi(pip install algan[taichi]). Both compile the same kernel sources and render the same frames. Every kernel in a process is built by whichever one is chosen here, so it cannot be re-selected afterwards, and the render daemon refuses a client whose value differs rather than serving it from the wrong compiler.ALGAN_SOFT_SHADOW_SAMPLESLength of the deterministic soft-shadow fan, baked into the shade kernels when they compile.
Example:
import os
os.environ["ALGAN_ANIMATION_DEVICE"] = "cpu"
from algan import *
Changing these variables after import algan does not reinitialize the
process. Two variables that look like they belong here do not:
ALGAN_RENDER_DEVICE only supplies the starting value of
SETTINGS.computing.render_device, and ALGAN_HDR_BUFFER_F16 the starting
value of SETTINGS.raytracing.experimental.hdr_buffer_f16 (the frame buffer
dtype is chosen when the buffer is allocated, so nothing bakes it in); both
settings own their value from then on and are settable between renders.
What the ALGAN_ prefix is, and is not¶
Algan declares around two hundred ALGAN_ names, and that number is not the
size of its supported interface. They fall into three groups, all listed in
algan/environment.py:
The initialization-only variables above. These are the supported way to configure process startup, and they are documented here because there is no settings object that can own them.
The live variables – the daemon controls (
ALGAN_USE_DAEMON,ALGAN_DAEMON_TIMEOUT, …),ALGAN_LOG_LEVEL,ALGAN_PROGRESS,ALGAN_VIDEO_ENCODERand the profiling switches. Each is read at the point of use, so setting one between two renders in the same process takes effect on the next read. The ones meant for you are documented in these tutorials; the rest are development instrumentation.Roughly a hundred and fifty import-time kernel and performance gates (
ALGAN_WAVEFRONT_*,ALGAN_SHEET_*,ALGAN_BVH_*, …). These are the environment defaults behindSETTINGS.raytracing.experimentaland are not a supported interface: they exist so a benchmark can flip one arm of an A/B, they are read once when their module is imported, and any of them may be renamed or removed in a patch release without notice. Reach for theexperimentalsection instead, which at least has stable identity and a snapshot/restore round trip.
An ALGAN_ variable this version does not know is ignored, not rejected.
Algan does not police the whole prefix – a wrapper script or CI job is free to
keep its own variables under it – so only a name close enough to a declared one
to look like a typo of it produces a warning, naming the variable it resembles.
See Also¶
Performance and Quality – which of these settings to reach for, and what each one costs.
Saving Videos and Images – the per-render form, which is usually what you want.
Backgrounds and Post-Processing – the anti-aliasing and tonemapping settings in context.
Lighting and Shadows –
SETTINGS.raytracing.shadows.The Render Daemon – why a warm daemon refuses a script that wants different initialization-only or import-time values.
Multi-Scene Projects –
video_settingson a whole project, instead of a global mutation at the top of the file.