systems.base.numerical_integration.normalize_method_name

systems.base.numerical_integration.normalize_method_name(
    method,
    backend='numpy',
)

Normalize method names across backends to canonical form.

Provides user-friendly aliases while maintaining backend compatibility. Handles both deterministic and stochastic (SDE) methods. This enables portable code: users can write ‘euler_maruyama’ and it automatically becomes ‘EM’ (NumPy), ‘euler’ (PyTorch), or ‘Euler’ (JAX).

Parameters

Name Type Description Default
method str User-provided method name (canonical or backend-specific) required
backend Backend Target backend: ‘numpy’, ‘torch’, or ‘jax’ 'numpy'

Returns

Name Type Description
str Normalized method name appropriate for backend

Normalization Logic

  1. Auto-switching methods → Pass through unchanged
    • Julia methods with parentheses (e.g., ‘AutoTsit5(Rosenbrock23())’)
  2. Already valid for backend? → Return (with special upgrades)
    • Check if method is in BACKEND_METHODS[backend]
    • Special case: lowercase euler/heun/midpoint on numpy → upgrade to Julia
    • Avoids breaking SDE methods (e.g., ‘Euler’ on jax stays ‘Euler’)
  3. In normalization map? → Map to backend-specific name
    • Check both exact case and lowercase versions
    • Handles canonical names like ‘euler_maruyama’, ‘rk45’
    • Handles explicit manual requests like ‘manual_euler’
  4. Unknown method? → Pass through unchanged
    • Will fail at integrator level with appropriate error
    • Allows custom methods to pass through

Backend Conventions

  • NumPy/Julia (DiffEqPy): Capitalized (e.g., ‘EM’, ‘Tsit5’, ‘SRIW1’)
  • PyTorch (TorchSDE/TorchDiffEq): lowercase (e.g., ‘euler’, ‘dopri5’)
  • JAX (Diffrax): PascalCase (e.g., ‘Euler’, ‘ItoMilstein’, ‘Tsit5’)

Julia Preference for ODE Methods

On NumPy backend, lowercase ODE methods automatically prefer Julia: - ‘euler’ → ‘Euler’ (Julia’s implementation) - ‘heun’ → ‘Heun’ (Julia’s implementation) - ‘midpoint’ → ‘Midpoint’ (Julia’s implementation)

To explicitly use manual implementations on NumPy: - Use ‘manual_euler’, ‘manual_heun’, ‘manual_midpoint’

Idempotency

Normalization is idempotent: >>> normalize_method_name(normalize_method_name(‘euler_maruyama’, ‘jax’), ‘jax’) ‘Euler’

Once normalized, subsequent calls return the same result.

Examples

Canonical SDE names → backend-specific:

>>> normalize_method_name('euler_maruyama', 'numpy')
'EM'
>>> normalize_method_name('euler_maruyama', 'torch')
'euler'
>>> normalize_method_name('euler_maruyama', 'jax')
'Euler'

Canonical ODE names → backend-specific:

>>> normalize_method_name('rk45', 'numpy')
'RK45'
>>> normalize_method_name('rk45', 'torch')
'dopri5'
>>> normalize_method_name('rk45', 'jax')
'tsit5'

Julia preference for ODE methods on NumPy:

>>> normalize_method_name('euler', 'numpy')
'Euler'  # Upgraded to Julia
>>> normalize_method_name('heun', 'numpy')
'Heun'   # Upgraded to Julia
>>> normalize_method_name('midpoint', 'numpy')
'Midpoint'  # Upgraded to Julia

Manual implementations on NumPy:

>>> normalize_method_name('manual_euler', 'numpy')
'euler'  # Explicit manual request
>>> normalize_method_name('manual_heun', 'numpy')
'heun'   # Explicit manual request

SDE methods preserved (critical for correctness):

>>> normalize_method_name('Euler', 'jax')
'Euler'  # SDE method, NOT normalized to 'euler'
>>> normalize_method_name('Heun', 'jax')
'Heun'   # SDE method, NOT normalized to 'heun'

ODE methods on torch/jax use manual implementations:

>>> normalize_method_name('euler', 'torch')
'euler'
>>> normalize_method_name('heun', 'jax')
'heun'

Cross-backend normalization (torch → jax):

>>> normalize_method_name('dopri5', 'jax')
'dopri5'  # Note: 'dopri5' exists in JAX, so no mapping needed

Already correct for backend → unchanged:

>>> normalize_method_name('EM', 'numpy')
'EM'
>>> normalize_method_name('ItoMilstein', 'jax')
'ItoMilstein'

Manual implementations (portable):

>>> normalize_method_name('rk4', 'numpy')
'rk4'
>>> normalize_method_name('rk4', 'torch')
'rk4'
>>> normalize_method_name('rk4', 'jax')
'rk4'

Unknown method → pass through:

>>> normalize_method_name('my_custom_method', 'numpy')
'my_custom_method'
# Will be validated at integrator creation

Case-insensitive lookup (canonical names only):

>>> normalize_method_name('RK45', 'torch')
'dopri5'
>>> normalize_method_name('rk45', 'torch')
'dopri5'

Usage in DiscretizedSystem

Automatic normalization in init

backend = getattr(continuous_system, ’_default_backend’, ‘numpy’) method = normalize_method_name(user_method, backend) # Now ‘method’ is guaranteed to use backend conventions

Notes

  • Normalization happens before any method classification
  • Original method name should be preserved for debugging/logging
  • Some canonical names map to different methods on different backends (e.g., ‘rk45’ → ‘RK45’/‘dopri5’/‘tsit5’)
  • Not all methods can be perfectly mapped across backends (e.g., ‘lsoda’ doesn’t exist on PyTorch/JAX, maps to nearest equivalent)
  • Critical: Methods already valid for a backend are preserved to avoid breaking SDE integration (e.g., ‘Euler’ on jax must stay ‘Euler’)

See Also

validate_method : Check if method is valid for backend is_sde_method : Check if method is for stochastic systems is_fixed_step : Check if method uses fixed time stepping