Step 6 — Maxwell Walkthrough
Capability
Read a real example through the same operator and execution language used in the earlier steps.
Problem Statement
The tutorial sequence should end at a real shipped example, not a docs-only toy abstraction. That is where the teaching story proves it is actually the same language as the codebase.
Mathematical Idea
Treat the Maxwell example as a composed system, not as a magical monolith. Identify the mesh, cochains, operators, execution object, and listeners.
Flux Concepts
- Example families are clients of the library, not a separate framework with different nouns.
- The runnable surface lives under
examples/, not insidesrc/. - The same
SystemplusEvolutionsplit still applies.
Minimal Runnable Snippet
Excerpt from the Maxwell example module:
const std = @import("std");
const flux = @import("flux");
const common = @import("examples_common");
const system_mod = @import("system.zig");
pub const system_api = system_mod;
const SnapshotCadence = union(enum) {
disabled,
interval: u32,
frames: u32,
};
pub fn CavityConfig(comptime dim: u8) type {
return struct {
steps: u32 = 1000,
counts: [dim]u32 = defaultCounts(dim),
extents: [dim]f64 = @splat(1.0),
courant: f64 = 0.1,
time_step_override: ?f64 = null,
output_dir: ?[]const u8 = null,
snapshot_cadence: SnapshotCadence = defaultSnapshotCadence(dim),
reference: bool = false,
boundary: system_mod.BoundaryCondition = .pec,
pub fn timeStep(self: @This()) f64 {
if (self.time_step_override) |value| return value;
return self.courant * minSpacing(dim, self.counts, self.extents);
}
pub fn snapshotInterval(self: @This()) ?u32 {
if (self.output_dir == null) return null;
return switch (self.snapshot_cadence) {
.disabled => null,
.interval => |value| value,
.frames => |value| @max(@as(u32, 1), common.framesToInterval(self.steps, value)),
};
}
pub fn cavityOptions(self: @This()) system_mod.CavityOptions(dim) {
return .{
.extents = self.extents,
.time_step = self.timeStep(),
.boundary = self.boundary,
};
}
pub fn measurementProvider(self: @This()) system_mod.CavityMeasurementProvider(dim) {
return .{
.extents = self.extents,
.time_step = self.timeStep(),
};
}
pub fn snapshotBaseName(self: @This()) []const u8 {
return if (self.reference) "cavity_reference" else "cavity";
}
};
}
pub const DipoleConfig2D = struct {
steps: u32 = 1000,
counts: [2]u32 = .{ 32, 32 },
extents: [2]f64 = .{ 1.0, 1.0 },
courant: f64 = 0.1,
time_step_override: ?f64 = null,
frequency_hz: f64 = 0.0,
amplitude: f64 = 1.0,
output_dir: ?[]const u8 = null,
snapshot_cadence: SnapshotCadence = .{ .frames = 100 },
boundary: system_mod.BoundaryCondition = .pec,
pub fn timeStep(self: @This()) f64 {
if (self.time_step_override) |value| return value;
return self.courant * minSpacing(2, self.counts, self.extents);
}
pub fn sourceFrequency(self: @This()) f64 {
if (self.frequency_hz != 0.0) return self.frequency_hz;
return 1.0 / (2.0 * self.extents[0]);
}
pub fn snapshotInterval(self: @This()) ?u32 {
if (self.output_dir == null) return null;
return switch (self.snapshot_cadence) {
.disabled => null,
.interval => |value| value,
.frames => |value| @max(@as(u32, 1), common.framesToInterval(self.steps, value)),
};
}
pub fn dipoleOptions(self: @This()) system_mod.DipoleOptions(2) {
return .{
.center = .{ 0.5 * self.extents[0], 0.5 * self.extents[1] },
.frequency_hz = self.sourceFrequency(),
.amplitude = self.amplitude,
.boundary = self.boundary,
};
}
};
pub fn RunResult(comptime Summary: type) type {
return struct {
elapsed_s: f64,
snapshot_count: u32,
summary: Summary,
};
}
pub const CavitySummary = struct {
energy_final: f64,
electric_l2_final: ?f64 = null,
magnetic_l2_final: ?f64 = null,
};
pub fn runDipole(
allocator: std.mem.Allocator,
config: DipoleConfig2D,
writer: *std.Io.Writer,
) !RunResult(CavitySummary) {
Excerpt from the umbrella CLI surface:
const std = @import("std");
const diffusion = @import("diffusion.zig");
const euler = @import("euler.zig");
const maxwell = @import("maxwell.zig");
pub const Subcommand = struct {
name: []const u8,
summary: []const u8,
run: *const fn (allocator: std.mem.Allocator, args: []const [:0]const u8) anyerror!void,
};
pub const subcommands = [_]Subcommand{
.{ .name = diffusion.name, .summary = diffusion.summary, .run = diffusion.run },
.{ .name = euler.name, .summary = euler.summary, .run = euler.run },
.{ .name = maxwell.name, .summary = maxwell.summary, .run = maxwell.run },
};
test {
std.testing.refAllDeclsRecursive(@This());
}
Expected Result
By this point the code should read as a larger version of the earlier steps: typed spaces, assembled operators, and an execution layer with listeners and output.
Possible Extensions
- Repeat the same read-through for diffusion and Euler.
- Add a new teaching page for one concrete conserved quantity in Maxwell.
- Once 3D Maxwell is stable, add a second “full example” tutorial rather than mutating this one into a catch-all.
API Reference Jump
Start from flux.evolution and then jump back into the example sources.