RustedSciThe 0.4.17

Rust framework for symbolic and numerical computing:BVP ( Newton-Raphson frozen/damped/with collocations ), IVP( BDF, Radau, Backward Euler, LSODE, LSODA, RK45, DoPri), nonlinear equations ( Levenberg, Gavin) and more
Documentation
# Nonlinear Systems User Guide

## Recommended Typed Path

For new code, prepare a symbolic system once and keep numeric parameter
bindings separate from that preparation:

```rust
use nalgebra::DVector;
use RustedSciThe::numerical::Nonlinear_systems::prelude::*;

let prepared = PreparedSymbolicNonlinearProblem::from_strings(
    vec!["a*x + y - 3".into(), "x - y".into()],
    SymbolicProblemOptions::new()
        .with_variables(vec!["x".into(), "y".into()])
        .with_equation_parameters(vec!["a".into()]),
)?;

let bound = prepared.bind_values(DVector::from_vec(vec![2.0]))?;
let options = SolveOptions {
    tolerance: 1e-10,
    max_iterations: 64,
    diagnostics: DiagnosticsOptions {
        collect_history: false,
        collect_statistics: true,
        ..DiagnosticsOptions::default()
    },
    ..SolveOptions::default()
};
let result = NonlinearSolverMethod::DampedNewton(DampedNewtonMethod::default())
    .solve(&bound, DVector::from_vec(vec![1.0, 1.0]), options)?;
```

`PreparedSymbolicNonlinearProblem` owns the parsed equations, variable order,
symbolic Jacobian preparation, and callable backend. `bind_values` validates a
numeric vector against the declared parameter schema and returns a borrowed
bound view. Rebinding different values does not repeat symbolic preparation;
the same prepared object can be used for continuation-like sweeps and several
initial guesses.

`SymbolicProblemOptions::with_lambdify_backend()` is the default explicit
choice for the in-process symbolic route. Generated AOT uses the generic AOT
lifecycle and must be materialized, built, registered, and linked before an
AOT-only problem can execute. `BuildIfMissing` and `RequirePrebuilt` are
lifecycle policies, not interchangeable solve methods; see the nonlinear
`STORY_TESTS.md` ledger for the tested artifact contract.

### Parameter Updates And Structural Rebuilds

The order passed to `with_equation_parameters` is the parameter ABI. A call to
`bind_values` (or the compatibility `set_parameter_values`) changes only the
numeric values in that existing schema. It does not reparse equations,
differentiate, lambdify, rebuild an AOT artifact, or change the variable order.
Changing equations, variables, parameter names/order, or generated chunking is
a structural change and requires preparing a new problem.

### Per-Attempt Diagnostics

When statistics are collected, `SolveStatistics::attempts` contains one entry
for each outer nonlinear iteration after the initial state evaluation. The
entries use the same solver-level counter semantics as the aggregate fields:
one residual/Jacobian evaluation means one complete provider request, even if
an AOT provider executes several generated jobs underneath it. Each entry also
reports Jacobian refresh/reuse, factorizations, linear solves, accepted and
rejected trials, and inclusive stage durations:

```rust
for attempt in &result.statistics.attempts {
    println!(
        "iteration={} R/J={}/{} refresh/reuse={}/{} factor/linear={}/{}",
        attempt.iteration,
        attempt.residual_evaluations,
        attempt.jacobian_evaluations,
        attempt.jacobian_refreshes,
        attempt.jacobian_reuses,
        attempt.linear_factorizations,
        attempt.linear_solves,
    );
}
```

The initial residual/Jacobian evaluation belongs to aggregate statistics, not
to an attempt entry. `termination_retries` is explicitly `None` on the
generic engine because it has no separate method-specific termination-retry
boundary. Backend-internal generated job/chunk counts belong to
`SymbolicPreparationReport`, not to solver counters. With statistics disabled,
`attempts` is empty and timing/counter fields are not measurements.

### AOT Artifact Lifecycle

For a cold production-style run, request an output directory and use
`BuildIfMissing`:

```rust
let cold = SymbolicNonlinearProblem::from_strings_with_generated_backend(
    equations.clone(),
    problem_options.clone(),
    SymbolicGeneratedBackendConfig::build_if_missing_release(&output_dir),
)?;
let resolver = cold.updated_resolver.clone();
println!(
    "cold: backend={:?}, action={:?}, key={:?}, build={:?}",
    cold.selected_backend,
    cold.preparation_report.artifact_action,
    cold.preparation_report.artifact_key,
    cold.preparation_report.build_duration,
);
```

After the artifact has been published and its resolver is retained, a strict
warm run uses `RequirePrebuilt` and never compiles:

```rust
let warm = SymbolicNonlinearProblem::from_strings_with_generated_backend(
    equations,
    problem_options,
    SymbolicGeneratedBackendConfig::require_prebuilt().with_resolver(resolver),
)?;
assert!(warm.build_result.is_none());
assert_eq!(warm.preparation_report.artifact_action, SymbolicArtifactAction::Reused);
```

`RequirePrebuilt` reports a typed error for a missing, incompatible, or
unlinked artifact; it is not a silent fallback to Lambdify. The resolver is an
in-process snapshot, so a fresh process must discover the persisted compatible
artifact from the configured output location. Parameter rebinding remains a
cheap numerical operation after either lifecycle stage.

## Bounds And Diagnostics

Use `SolveOptions::bounds` for a box-constrained solve. Bounds belong to the
solver options, while equations and parameter bindings belong to the problem:

```rust
let bounds = Bounds::new(vec![(0.0, 3.0), (0.0, 2.0)])?;
let options = SolveOptions {
    bounds: Some(bounds),
    ..SolveOptions::default()
};
```

With `collect_statistics: true`, `SolveResult::statistics` reports solver-level
residual, Jacobian, linear-solve, accepted/rejected-step counters and
cumulative stage durations. `collect_history` is separate and can add memory
and allocation pressure. Disabled diagnostics should be used for hot-path
timing when history is not part of the question.

For pure numerical problems, implement `NonlinearProblem` and
`JacobianProvider` with caller-owned `DVector`/`DMatrix` values. The optional
`residual_into` and `jacobian_into` hooks let a provider fill solver-owned
buffers and are the appropriate route for allocation-sensitive workloads.
The mathematical method is unchanged; the hooks only change value transport.

## Choosing A Method

Plain Newton is usually the fastest near a well-scaled solution but is more
sensitive to a remote initial guess or a singular Jacobian. Damped Newton is a
good first choice when backtracking or bounds are needed. Levenberg-Marquardt
and trust-region variants are useful for difficult least-squares-like or
poorly scaled systems. No single wall-clock number ranks all methods: compare
convergence, residual quality, rejected trials, and stage timings on the
problem class that matters.

## Allocation Audit

Run the process-level release audit with:

```text
cargo bench --bench nonlinear_systems_allocation_audit
```

The audit covers dimensions 32, 128, and 512, accepted solves with history
off, history comparisons at the smallest and largest dimensions, reusable
callback outputs, and a separate rejection-heavy control. It reports
allocation/deallocation counts and bytes for the complete solve-result
lifetime. The counting allocator changes process behavior, so its elapsed
times are diagnostic only and must not be compared with ordinary Criterion or
application wall-clock measurements. The audit also does not identify every
individual copy or measure peak resident memory; use it as evidence for a
focused optimization hypothesis, then require correctness tests before a
hot-path change.

The executable example is:

```text
cargo run --example nonlinear_systems_modern_guide
```

The compatibility and lifecycle examples are:

```text
cargo run --example nonlinear_lambdify_legacy_guide
cargo run --example nonlinear_lambdify_prepared_guide
cargo run --example nonlinear_aot_lifecycle_guide
```

The older `nonlinear_systems_guide` remains available as a compatibility
showcase for the legacy LM wrapper.