Architecture Boundaries
TIDE is maintained as one Maxwell-focused package. The package is split into four dependency layers:
public API (tide.__init__, tide.maxwell) | vcore contracts (plan, types, capability decisions) | vsolver implementations (reference Python and native adapters) | vC/CUDA kernels and ABI declarationsThe workflow and optim packages are consumers of the public solver API;
the solver must not import either package. core contains only cross-cutting
execution contracts and must not import a solver, workflow, or optimizer.
Stable versus internal code
Section titled “Stable versus internal code”The supported surface is the API exported by tide.__all__ and
tide.maxwell.__all__. Files under tide.maxwell that end in _python,
_cuda, or _autograd are implementation details. A change to them is safe
to review as an internal change only when the public behavior and numerical
parity tests remain unchanged.
Research prototypes, generated outputs, large datasets, and profiling scripts stay outside the supported package and documentation surfaces. A prototype is promoted only when it has a stable API, a reference path, capability coverage, tests, and user documentation.
Execution contract
Section titled “Execution contract”Public operators compile each request into SimulationPlan, pass it through
tide.maxwell.dispatch.ExecutionPolicy, ask the central capability matrix for
a BackendDecision, and then call one execution adapter. Forward, JVP, VJP,
and second-VJP paths in TM2D and EM3D use this transition. New options must be
represented in the plan and matrix before reaching a kernel. Unsupported
combinations either select the declared reference fallback or raise a
descriptive error.
Native execution boundary
Section titled “Native execution boundary”The native boundary is ABI v5 and is declared once in
tide/csrc/native/native_abi.h. Python binds fixed, unmangled C entrypoints for
plan creation and destruction, workspace queries, forward execution, and VJP
execution. It does not discover per-kernel symbols or maintain a parallel
ctypes signature registry.
Each adapter builds an immutable NativeExecutionPlan for one dimension,
operation, scalar type, device, stencil, shape, and time interval. Forward,
JVP, VJP, second-VJP, material preprocessing, Born-material preprocessing,
source preprocessing, and source VJP are operation values on this same plan
contract. Preprocessing therefore uses the same fixed entrypoints rather than
a separate family of exported function signatures.
Tensor addresses cross the ABI in versioned, role-indexed descriptors:
TideNativeBufferSetcontains operation inputs and outputs;TideNativeStoragecontains device-resident primal or tangent snapshots;TideNativeWorkspacecontains adapter-owned scratch allocations; andTideNativeWorkspaceRequirementslets a plan declare each required scratch slot and alignment before execution.
Every descriptor carries its ABI version and structure size. Role enums in
native_abi.h and tide.native_execution must change together; positional
tuples and unversioned pointer lists are not valid extension points.
CUDA solver configuration is immutable for an invocation. Each launch receives its dimensions, spacing, PML bounds, batching flags, and storage layout in a by-value configuration object. Native code must not place request-specific values in mutable device constant symbols. This keeps simultaneous executions with different plans isolated across CUDA streams.
Dependency rules
Section titled “Dependency rules”The following import directions are enforced by tests:
coremay depend on the standard library and PyTorch, but not on solvers, workflow, optimizers, or the native ABI loader;maxwellmay depend oncoreand shared numerical utilities, but not onworkfloworoptim;optimis self-contained and must not depend onmaxwell,workflow, orcore;workflowmay consume public solver behavior, but solver code cannot depend on workflow helpers.
Operator data flow
Section titled “Operator data flow”flowchart TD
A[Domain objects] --> B[MaxwellTM or Maxwell3D]
B --> C[SimulationPlan]
C --> D[BackendDecision]
D --> E[Reference or native adapter]
E --> F[Kernel execution]
F --> G[Named result objects]
G --> H[workflow and optim]
The public operator owns model-independent configuration. EMModel remains an
input. Linearization fixes one background model and owns the lifetime of
primal snapshots used by JVP, VJP, and second-VJP actions.
Result boundary
Section titled “Result boundary”Native and reference kernels may return different internal tuples, but public
callers receive ForwardResult, TangentResult, dimension-specific state, and
EMGradient. Adapter code is responsible for converting internal ordering into
these named values.
Workflow code must not inspect tuple positions or import a kernel adapter. This keeps shot batching, objectives, and optimizers independent of backend layout.
Numerical parity boundary
Section titled “Numerical parity boundary”A native optimization is complete only when it matches the reference path for the public quantity it implements:
- Receiver traces and final state for forward.
- Tangent receiver traces for JVP.
- Material gradients for VJP.
- Nonlinear material products for second VJP.
Tests should also cover rejected cells, fallback decisions, storage modes, and resource cleanup. Compilation success alone is not evidence of parity.
State ownership
Section titled “State ownership”Operators do not own trainable parameters. Linearized sessions may own cached primal graphs and device-resident snapshots used by JVP, VJP, and second-VJP actions. Native plans own only immutable compiled configuration and their native handles. Adapters own the tensors referenced by buffer, storage, and workspace descriptors for the full duration of an invocation.
Snapshot storage is device-only. Scratch storage is role-indexed, declared by the plan, and supplied by the adapter; it is not hidden process-global state. Workflow helpers own only indexing and composition. Optimizers own only algorithm state.
This ownership split prevents hidden model mutation, makes resource lifetime visible at the adapter boundary, and permits independent native plans to run concurrently.