RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
Public Member Functions | Public Attributes | Protected Member Functions | Static Protected Member Functions | Protected Attributes | Static Protected Attributes | List of all members
py_utils.simulation.RadClusterSimulation Class Reference
+ Collaboration diagram for py_utils.simulation.RadClusterSimulation:

Public Member Functions

 __init__ (self, I=None, V=None, solver_mode=None, physics_option=None, equations=None, cascade=None, excel_file=None, C_floor=None, he_kinetics=None, i_mobile=None, v_mobile=None, **legacy_kw)
 
 rebuild_rates (self)
 
 run_adaptive (self, solver_config=None, save_output=True, progress_callback=None, timeout_s=None, boundary_threshold=0.01, max_doublings=6, points_per_segment=10)
 
 run (self, solver_config=None, save_output=True, progress_callback=None, timeout_s=None, y0_override=None)
 

Public Attributes

 input_data
 
 reaction_rates
 
 rate_equations
 
 material_rag
 
 state_layout
 

Protected Member Functions

 _reconstruct_sia_per_size (self, sia_block)
 
 _loop_network_update (self, results, dt)
 
 _resize_domain (self, I_new, V_new)
 
 _boundary_fraction_at (self, results, idx)
 
 _boundary_fraction (self, results)
 
 _find_first_exceedance (self, results, threshold)
 
 _resume_state (self, results, idx=-1)
 
 _expand_state (self, y_old, I_new, V_new, y100_old=None)
 
 _slice_results (self, results, start, end)
 
 _merge_results (self, accumulated, new_segment)
 
 _run_cpp (self, solver_config, progress_callback=None, timeout_s=None, y0_override=None)
 
 _default_solver_config (self)
 
 _write_dict_block (cls, f, title, d)
 
 _machine_info (self)
 
 _save_output (self, results, solver_config)
 
 _save_output_impl (self, results, solver_config)
 

Static Protected Member Functions

 _sia100_column (results, idx)
 
 _concat_2d_padded (old, new)
 
 _merge_y_blocks (accumulated, new_segment, skip)
 
 _fmt_value (v)
 

Protected Attributes

 _accumulated_results
 
 _diag_text
 
 _partial_results
 
 _wall_clock_s
 
 _last_output_dir
 

Static Protected Attributes

dict _TS_KEYS
 
dict _TS_KEYS_2D = {'y_sia100', 'y_sia100_raw'}
 

Detailed Description

Orchestrates the RadCluster_2_1 simulation workflow.

Parameters
----------
I              : int, optional  — override max SIA cluster size
V              : int, optional  — override max vacancy cluster size
solver_mode    : str, optional  — 'full_system' | 'active_window'
physics_option : str, optional  — 'full_CD_fission' | 'full_CD_fusion' |
                                  'bin_moment_CD_fission' | 'bin_moment_CD_fusion'.
                                  Mutually exclusive with the new (equations,
                                  cascade) pair below.
equations      : str, optional  — 'discrete' | 'bin_moment'.  Use together with
                                  `cascade` instead of `physics_option`.
                                  Legacy 'full_CD' is silently aliased to
                                  'discrete'.
cascade        : str, optional  — 'fission' | 'fusion'.  Use together with
                                  `equations`.
excel_file     : path-like, optional
C_floor        : float, optional — concentration floor (default 1e-15).
                                   Any state variable below this is clamped
                                   before evaluating rate terms.
he_kinetics    : str, optional   — 'dynamic' (default) integrates free He as a
                                   full ODE (Eq. 157); 'quasi_steady_state'
                                   eliminates it via dc_h/dt = 0 (valid because
                                   E_m_h = 0.06 eV gives rapid equilibration).
i_mobile       : int, optional   — max mobile SIA cluster size (default 1).
                                   1 = mono-defect form; set > 1 to enable 1D
                                   glide and SIA–SIA coalescence.
v_mobile       : int, optional   — max mobile vacancy cluster size (default 1).
                                   1 = mono-defect form; set > 1 to enable
                                   vacancy–vacancy coalescence.

Notes
-----
Two-layer RAG description.  Alongside the legacy RateEquations /
BinMomentRateEquations objects that drive the C++ solver, every
RadClusterSimulation also carries the abstract-core + EUROFER-97
instantiation as ``self.material_rag`` (the reaction admissibility
graph G_Eur, paper Section 4) and ``self.state_layout`` (its
contiguous ODE-index map).  This makes the graph-based formulation
the live structural description of every run.  The RAG build is
advisory: it is wrapped in try/except so a RAG-build issue can never
break a run, and the C++ drive path is byte-identical with or
without it.  ``material_rag`` / ``state_layout`` are ``None`` if the
build failed.

Definition at line 187 of file simulation.py.

Constructor & Destructor Documentation

◆ __init__()

py_utils.simulation.RadClusterSimulation.__init__ (   self,
  I = None,
  V = None,
  solver_mode = None,
  physics_option = None,
  equations = None,
  cascade = None,
  excel_file = None,
  C_floor = None,
  he_kinetics = None,
  i_mobile = None,
  v_mobile = None,
**  legacy_kw 
)

Definition at line 236 of file simulation.py.

Member Function Documentation

◆ _boundary_fraction()

py_utils.simulation.RadClusterSimulation._boundary_fraction (   self,
  results 
)
protected
Check tail fractions at the last time step (backward compat).

Definition at line 561 of file simulation.py.

◆ _boundary_fraction_at()

py_utils.simulation.RadClusterSimulation._boundary_fraction_at (   self,
  results,
  idx 
)
protected
Compute tail fractions at a specific time index.

Returns (frac_I, frac_V) — fraction of the total SIA/vacancy content
in the last 10% of sizes.  Values > ~0.01 indicate boundary pileup.

Definition at line 483 of file simulation.py.

◆ _concat_2d_padded()

py_utils.simulation.RadClusterSimulation._concat_2d_padded (   old,
  new 
)
staticprotected
Concatenate two [size, time] arrays along time, zero-padding the
shorter size axis so a domain doubling does not break the merge.

Definition at line 943 of file simulation.py.

◆ _default_solver_config()

py_utils.simulation.RadClusterSimulation._default_solver_config (   self)
protected

Definition at line 1414 of file simulation.py.

◆ _expand_state()

py_utils.simulation.RadClusterSimulation._expand_state (   self,
  y_old,
  I_new,
  V_new,
  y100_old = None 
)
protected
Map ODE state from the current domain to a larger (I_new, V_new) domain.

1. Reconstruct per-size distributions from old state.
2. Pad with C_floor for new sizes beyond old I, V.
3. Resize domain (rebuilds bins and rate equations).
4. Project padded distributions onto new bins.

Returns y0_new for the enlarged domain.

Parameters
----------
y100_old : ndarray or None
    The appended ⟨100⟩ block AS SOLVED (``results['y_sia100_raw']``),
    i.e. per-size in discrete modes and discrete-prefix + bin moments in
    bin-moment modes.  When given it is remapped by the SAME
    reconstruct → pad → project path as the ½⟨111⟩ block (the two share
    a layout) and appended to the returned state, so the C++ receives a
    y0 of the full width.  Omitting it returns the ⟨100⟩-free state,
    which the solver zero-fills to 1e-100 — that erases the population
    at the doubling, so pass it whenever loop conversion is on.

Definition at line 616 of file simulation.py.

◆ _find_first_exceedance()

py_utils.simulation.RadClusterSimulation._find_first_exceedance (   self,
  results,
  threshold 
)
protected
Scan all output time points and return the index of the first one
where either the SIA or vacancy tail fraction exceeds *threshold*.

Returns None if no exceedance is found.

Definition at line 568 of file simulation.py.

◆ _fmt_value()

py_utils.simulation.RadClusterSimulation._fmt_value (   v)
staticprotected
Format a scalar for the provenance file.

Definition at line 1466 of file simulation.py.

◆ _loop_network_update()

py_utils.simulation.RadClusterSimulation._loop_network_update (   self,
  results,
  dt 
)
protected
Operator-split advance of the dynamic network density ρ_net between
integration segments (loop_network_loss.tex §Implementation).

ρ_net is held piecewise-constant *within* a segment (so the Jacobian /
Woodbury structure is untouched) and advanced *between* segments by an
explicit Euler step of Eq. (rho_net_balance):

    ρ_net ← ρ_net + dt · [ (π/Ω) Σ d_n Λ_n c_n  −  K_rec ρ_net^{3/2} ]

The segment-end SIA/vacancy monomers are frozen into ``ci1_seg`` /
``cv1_seg`` (they set the network climb velocity v_net for the next
segment), and ``rebuild_rates`` refreshes ReactionRates so the P4 sink
and Λ_n^net pick up the new ρ_net.  No-op unless the channel is on.

The per-size loop distributions that drive the growth term come from the
post-processed segment and so work in BOTH discrete and bin-moment modes:
  * ½⟨111⟩ — the SIA block of ``results['y']`` (per-size in discrete
    mode; reconstructed from the SIA bin moments via the active shape
    function in bin-moment mode);
  * ⟨100⟩  — ``results['y_sia100']``, which cpp_bridge has already
    reconstructed to per-size [I, n_t] in both modes.  (Note ``results['y']``
    no longer carries the appended ⟨100⟩ block — cpp_bridge splits it
    off — so it must be read from this dedicated key, not the y tail.)

Definition at line 414 of file simulation.py.

◆ _machine_info()

py_utils.simulation.RadClusterSimulation._machine_info (   self)
protected

Definition at line 1486 of file simulation.py.

◆ _merge_results()

py_utils.simulation.RadClusterSimulation._merge_results (   self,
  accumulated,
  new_segment 
)
protected
Merge two results dicts by concatenating time-series arrays.

The first time point of *new_segment* is skipped if it duplicates
the last time point of *accumulated* (continuation overlap).
The raw state ``y`` is decomposed into SIA / VAC / He blocks,
each zero-padded independently to the larger layout, then
reassembled and concatenated so that plots cover the full dose
range even after domain-doubling.

Definition at line 892 of file simulation.py.

◆ _merge_y_blocks()

py_utils.simulation.RadClusterSimulation._merge_y_blocks (   accumulated,
  new_segment,
  skip 
)
staticprotected
Remap old y to the new layout and concatenate.

Uses ``_y_i_VAC`` and ``_y_i_He`` stored in each results dict to
decompose y into SIA / VAC / He blocks, zero-pad each to the
larger size, then reassemble into a single array with the new
layout.

Definition at line 954 of file simulation.py.

◆ _reconstruct_sia_per_size()

py_utils.simulation.RadClusterSimulation._reconstruct_sia_per_size (   self,
  sia_block 
)
protected
Reconstruct the per-size ½⟨111⟩ distribution c_n (n = 1..I) from a
SIA state block.

In discrete mode the block is already per-size and is returned (clipped)
as-is.  In bin-moment mode the block is ``i_discrete`` discrete sizes
followed by ``I_bin`` bins of ``P`` moments each; the binned part is
expanded through the active intra-bin shape function, exactly as
cpp_bridge reconstructs the appended ⟨100⟩ block.

Definition at line 379 of file simulation.py.

◆ _resize_domain()

py_utils.simulation.RadClusterSimulation._resize_domain (   self,
  I_new,
  V_new 
)
protected
Resize the cluster domain and rebuild all rate objects.

Definition at line 476 of file simulation.py.

◆ _resume_state()

py_utils.simulation.RadClusterSimulation._resume_state (   self,
  results,
  idx = -1 
)
protected
Full C++ state vector at time index *idx*, ready to resume from.

``results['y']`` has the appended ⟨100⟩ block split off by cpp_bridge,
so resuming from it alone hands the solver a SHORT y0.  The C++ reads
missing ``y0_k`` keys as 1e-100 (parameters.h), so this did not fail —
it silently erased the entire ⟨100⟩ population at every segment
boundary, which the population then had to regrow from nothing.  That
produced the once-per-segment sawtooth in the ⟨100⟩ number density and
a violent restart transient for CVODE.  Re-attach the block here.

Definition at line 601 of file simulation.py.

◆ _run_cpp()

py_utils.simulation.RadClusterSimulation._run_cpp (   self,
  solver_config,
  progress_callback = None,
  timeout_s = None,
  y0_override = None 
)
protected
Invoke the C++ solver via cpp_bridge.

Definition at line 1400 of file simulation.py.

◆ _save_output()

py_utils.simulation.RadClusterSimulation._save_output (   self,
  results,
  solver_config 
)
protected
Write timestamped output directory.

Resilient to partial/interrupted runs: each artifact (provenance,
summary, diagnostics, .npy, plots) is written under its own try/except
so a failure in one does not abort the others.

The whole body runs under an `_InterruptDeferred` shield so that a
Ctrl+C arriving mid-save does not abandon partially-written files —
the current write completes, the rest of the artifacts are flushed,
then the interrupt is re-raised for the caller.  A second Ctrl+C in
the same block restores the default handler so the user can still
force-abort if a save step hangs.

Definition at line 1506 of file simulation.py.

◆ _save_output_impl()

py_utils.simulation.RadClusterSimulation._save_output_impl (   self,
  results,
  solver_config 
)
protected

Definition at line 1523 of file simulation.py.

◆ _sia100_column()

py_utils.simulation.RadClusterSimulation._sia100_column (   results,
  idx 
)
staticprotected
The ⟨100⟩ block AS SOLVED at time index *idx*, or None if absent.

Reads ``y_sia100_raw`` (the raw appended block) and never ``y_sia100``,
which is a per-size closure reconstruction with the C_floor IC already
subtracted — fine for plots, wrong as an initial condition.

Definition at line 586 of file simulation.py.

◆ _slice_results()

py_utils.simulation.RadClusterSimulation._slice_results (   self,
  results,
  start,
  end 
)
protected
Slice a results dict to time indices [start:end).

Definition at line 878 of file simulation.py.

◆ _write_dict_block()

py_utils.simulation.RadClusterSimulation._write_dict_block (   cls,
  f,
  title,
  d 
)
protected

Definition at line 1477 of file simulation.py.

◆ rebuild_rates()

py_utils.simulation.RadClusterSimulation.rebuild_rates (   self)
Rebuild reaction rates and rate equations from current input_data.

Call this after modifying input_data parameter dicts and calling
input_data._calculate_derived() to ensure the solver uses the
updated rate constants.

Definition at line 361 of file simulation.py.

◆ run()

py_utils.simulation.RadClusterSimulation.run (   self,
  solver_config = None,
  save_output = True,
  progress_callback = None,
  timeout_s = None,
  y0_override = None 
)
Run the simulation using the configured solver mode and physics option.

Parameters
----------
solver_config     : dict, optional
    Keys: t_span, n_points, rtol, atol, log_time,
          solver_method (dict with linsol, window_*, etc.)
save_output       : bool
    Write timestamped output/ directory.
progress_callback : callable or None
    Called once per output time step during the C++ solver run with a
    dict of concentrations and rate-breakdown values (atom fraction).
    Enables verbose mode in the C++ solver automatically.
timeout_s         : float or None
    Maximum wall-clock seconds for the C++ solver.
y0_override       : ndarray or None
    Custom initial conditions (adaptive continuation).

Returns
-------
results : dict  (see post_process.calculate_derived_quantities)

Definition at line 1294 of file simulation.py.

◆ run_adaptive()

py_utils.simulation.RadClusterSimulation.run_adaptive (   self,
  solver_config = None,
  save_output = True,
  progress_callback = None,
  timeout_s = None,
  boundary_threshold = 0.01,
  max_doublings = 6,
  points_per_segment = 10 
)
Truly adaptive domain doubling via short time-segments.

The total time span is divided into short segments of
*points_per_segment* output points each.  After every segment
the tail fraction is checked.  If it exceeds *boundary_threshold*:

1. The first exceedance point within the segment is located.
2. Results up to that point are kept.
3. I and/or V are doubled; the state vector is mapped to the
   larger domain via ``_expand_state``.
4. Integration resumes from the exceedance time.

This allows starting with a small domain (e.g. I = V = 1000) and
growing it on demand as the distribution spreads, without ever
restarting from t = 0.

Parameters
----------
boundary_threshold : float
    Adapt if tail fraction > this (default 0.01 = 1 %).
max_doublings : int
    Maximum number of doublings (default 6; 1000 → 64 000).
points_per_segment : int
    Output points per solver call (default 10).  Smaller values
    detect exceedance earlier but add subprocess overhead.

Returns
-------
results : dict

Definition at line 1009 of file simulation.py.

Member Data Documentation

◆ _accumulated_results

py_utils.simulation.RadClusterSimulation._accumulated_results
protected

Definition at line 354 of file simulation.py.

◆ _diag_text

py_utils.simulation.RadClusterSimulation._diag_text
protected

Definition at line 1144 of file simulation.py.

◆ _last_output_dir

py_utils.simulation.RadClusterSimulation._last_output_dir
protected

Definition at line 1730 of file simulation.py.

◆ _partial_results

py_utils.simulation.RadClusterSimulation._partial_results
protected

Definition at line 1334 of file simulation.py.

◆ _TS_KEYS

dict py_utils.simulation.RadClusterSimulation._TS_KEYS
staticprotected
Initial value:
= {'t', 'dose', 'rho_net', 'C_SIA_tot', 'C_VAC_tot', 'C_He_tot',
'C_He_free', 'mean_n_i', 'mean_n_v', 'N_loops', 'N_voids',
'swelling', 'C_i1', 'C_v1', 'delta_FP', 'delta_He',
'J_SIA_fixed', 'J_SIA_mutual', 'J_VAC_fixed', 'J_VAC_mutual',
'J_He_sink',
# loop-conversion series (⟨100⟩ block); omitting these left
# them holding only the LAST segment, so their length no
# longer matched len(t) and every loop-character plot broke.
'N_loops_111', 'N_loops_100', 'mean_n_111', 'mean_n_100',
'f_111_loop',
# the separated conservation arms — same defect
'delta_FP_sia', 'delta_FP_vac'}

Definition at line 861 of file simulation.py.

◆ _TS_KEYS_2D

dict py_utils.simulation.RadClusterSimulation._TS_KEYS_2D = {'y_sia100', 'y_sia100_raw'}
staticprotected

Definition at line 876 of file simulation.py.

◆ _wall_clock_s

py_utils.simulation.RadClusterSimulation._wall_clock_s
protected

Definition at line 1366 of file simulation.py.

◆ input_data

py_utils.simulation.RadClusterSimulation.input_data

Definition at line 293 of file simulation.py.

◆ material_rag

py_utils.simulation.RadClusterSimulation.material_rag

Definition at line 335 of file simulation.py.

◆ rate_equations

py_utils.simulation.RadClusterSimulation.rate_equations

Definition at line 321 of file simulation.py.

◆ reaction_rates

py_utils.simulation.RadClusterSimulation.reaction_rates

Definition at line 317 of file simulation.py.

◆ state_layout

py_utils.simulation.RadClusterSimulation.state_layout

Definition at line 336 of file simulation.py.


The documentation for this class was generated from the following file: