1.5. Time-Dependent Problems#
Time-dependent problems add temporal evolution to the neutron transport equation and require additional parameters beyond the standard inputs.
1.5.1. Time Stepping Parameters#
Time stepping is configured via a TimeDependentData object:
from ants.datatypes import TimeDependentData, TemporalDiscretization
time_data = TimeDependentData(
steps=100, # number of time steps
dt=0.01, # time step size (seconds)
time_disc=TemporalDiscretization.BDF1, # optional; BDF1 is the default
)
1.5.2. Time Integrators#
Four time integration schemes are available via the TemporalDiscretization enum:
Enum value |
Order |
Type |
Notes |
|---|---|---|---|
|
1st |
Implicit |
Backward Euler. Default. Unconditionally stable; lowest accuracy. |
|
2nd |
Implicit |
Second-order backward difference. More accurate than BDF1 for smooth transients; still unconditionally stable. |
|
2nd |
Implicit |
Crank-Nicolson. Second-order; can exhibit oscillations on stiff problems. Requires edge-based initial flux (see below). |
|
2nd |
Implicit |
Trapezoidal Rule / BDF2 composite. Combines CN accuracy with BDF2 damping. Recommended for stiff multigroup problems. Requires edge-based initial flux (see below). |
1.5.3. Initial Flux#
The initial angular flux in SourceData is at the cell centers for BDF1 and
BDF2, and at the cell edges for CN and TR-BDF2.
BDF1 and BDF2 — cell-centered initial flux:
# 1D: shape (I, N, G)
sources = SourceData(
initial_flux=np.zeros((cells_x, angles, groups)),
...
)
# 2D: shape (I, J, N², G)
sources = SourceData(
initial_flux=np.zeros((cells_x, cells_y, angles**2, groups)),
...
)
CN and TR-BDF2 — edge-based initial flux. In 1D this is still
initial_flux, with one more row than there are cells. In 2D the x and y
faces are passed separately as initial_flux_x and initial_flux_y:
# 1D: initial_flux shape (I+1, N, G)
sources = SourceData(
initial_flux=np.zeros((cells_x + 1, angles, groups)),
...
)
# 2D: initial_flux_x shape (I+1, J, N², G), initial_flux_y shape (I, J+1, N², G)
sources = SourceData(
initial_flux_x=np.zeros((cells_x + 1, cells_y, angles**2, groups)),
initial_flux_y=np.zeros((cells_x, cells_y + 1, angles**2, groups)),
...
)
1.5.4. Time-Dependent Sources#
Sources that vary in time have a leading time axis of size T:
1D external: shape
(T, I, N, G)2D external: shape
(T, I, J, N², G)1D boundary: shape
(T, 2, N/2, G)2D boundary x: shape
(T, 2, J, N²/2, G)2D boundary y: shape
(T, 2, I, N²/2, G)
Boundary sources only hold the incoming angles of each side. Angle and group dimensions of size 1 are broadcast (see Standard Inputs).
T is 1 for a source that is constant in time. Otherwise it depends on the
time integrator, with edges_t = np.linspace(0, t_final, steps + 1):
Integrator |
External |
Boundary |
Times |
|---|---|---|---|
|
|
|
|
|
|
|
External at |
|
|
|
External at |
The time axis is always required, so a steady source of shape (I, N, G)
must be reshaped to (1, I, N, G). For a steady source repeated across all
steps, use:
from ants.external1d import time_dependence_constant
external = time_dependence_constant(external_ss) # adds T=1 leading dimension
Helper functions such as ants.boundary1d.time_dependence_decay_02() and
ants.boundary2d.time_dependence_decay_03() generate time-dependent boundary
arrays from a base boundary condition and a time axis.
A dense time-dependent source stores every angle and group of every step. A
CompactSource (see Standard Inputs) stores each spatial profile once,
with an index of shape (T, N, G) when the profiles change between steps and
an optional amplitude scale of shape (T,). The solvers expand one step at
a time, so a decaying one-group boundary source costs a few values per step
instead of N x G:
from ants.datatypes import CompactSource
# Left boundary in group 2, decaying exponentially in time
values = np.array([[1.0], [0.0]]) # (2, S) for [x(0), x(X)]
index = np.full((1, groups), -1) # (1, G), same for all angles
index[0, 2] = 0
scale = np.exp(-edges_t[1:] / 1e-6) # (T,)
boundary_x = CompactSource(values, index, scale=scale)
An existing dense array is converted with CompactSource.from_dense(array,
time=True), or CompactSource.from_steps(...) from one step at a time.
Compact boundary indices cover all angles; only the incoming angles are used.
1.5.5. Available Time-Dependent Solvers#
Module |
Description |
|---|---|
|
One-dimensional time-dependent solver |
|
Two-dimensional time-dependent solver |
|
1D time-dependent with collision-based hybrid acceleration |
|
2D time-dependent with collision-based hybrid acceleration |
|
1D time-dependent with vectorized hybrid (per-step coarse resolution) |
|
2D time-dependent with vectorized hybrid (per-step coarse resolution) |
All modules expose a single public function time_dependent().
1.5.6. Hybrid Methods for Acceleration#
The collision-based hybrid method decomposes the problem into:
Uncollided component: solved on the fine angular/energy grid
Collided component: solved on a coarser angular/energy grid
This reduces computational cost when the collided flux is smoother than the uncollided flux (common in multigroup streaming-dominated problems).
Standard hybrid (hybrid1d, hybrid2d) requires two MaterialData objects
(one for each resolution) and a HybridData object encoding the group indexing:
from ants.datatypes import HybridData
from ants.utils import hybrid as hytools
fine_idx, coarse_idx, factor = hytools.indexing(edges_g, edges_gidx_u, edges_gidx_c)
hybrid_data = HybridData(fine_idx=fine_idx, coarse_idx=coarse_idx, factor=factor)
The vectorized hybrid (vhybrid1d, vhybrid2d) instead accepts per-step integer
arrays and handles coarsening internally:
vgroups_c = np.array([groups_c] * steps, dtype=np.int32)
vangles_c = np.array([angles_c] * steps, dtype=np.int32)
flux = time_dependent(mat_data, vgroups_c, sources, geometry,
quadrature_u, vangles_c, solver, time_data, edges_g)
See Examples for complete runnable examples of each solver.