Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migrating from 0.1 to 0.2

symplex 0.2 is a breaking release. Most changes are mechanical (a Result where there was an Option, a dropped dummy argument); a few change results because 0.1 was wrong. The full list is in the CHANGELOG; this page shows the code.

compile returns Result

// 0.1
let f = expr.compile(&["x"]).expect("unsupported node");   // Option<Box<dyn Fn>>
// 0.2
let f = expr.compile(&["x"])?;                              // Result<CompiledFn, SymplexError>
f(&[2.0]);                 // still callable
f.arity();                 // new
f.try_call(&[2.0])?;       // new: arity-checked

CompiledFn is Clone + Send + Sync. The error tells you why: FreeSymbol { name } or NotImplemented(node). Coverage grew to every numerically evaluable node (special functions, Bessel, orthogonal polynomials, piecewise), so expressions that were None in 0.1 now compile.

definite_integralintegrate_definite

// 0.1  — computed F(b) − F(a) blindly: ∫₋₁¹ dx/x² gave −2
let v = expr.definite_integral(&x, &a, &b);
// 0.2
let v = expr.integrate_definite(&x, &a, &b);          // Ex; Integral node if undecided
let v = expr.try_integrate_definite(&x, &a, &b)?;     // Err(Divergent) / Err(ComputationFailed)

If you relied on F(b) − F(a) for a proper integral, results are unchanged. For integrals across a pole you now get Err(Divergent) (or an unevaluated node), which is the correct answer.

solve semantics

// 0.1: identities and contradictions both gave Ok(vec![]) (or a guess)
// 0.2:
match expr.solve(&x) {
    Ok(roots) => …,
    Err(SymplexError::InfiniteSolutions { .. }) => …,   // x − x = 0
    Err(SymplexError::NoSolution { .. }) => …,          // sin x = 2, eˣ = −1, |x| = −1
    Err(e) => …,
}

Roots are now eval’d: asin(1/2) comes back as π/6. If you matched on strings, update the expected text. solve_or_empty still returns Vec<Ex> and swallows every error.

Matrices

// 0.1                                     // 0.2
m.eigenvals(&lam)?                         m.eigenvals()?
m.eigenvects(&lam)?                        m.eigenvects()?
m.diagonalize(&lam)?                       m.diagonalize()?
m.jordan_form(&lam)?                       m.jordan_form()?
m.matrix_exp(&t)?                          m.matrix_exp_t(&t)?      // or matrix_exp() for e^A
m.is_diagonalizable(&lam) -> bool          m.is_diagonalizable() -> Option<bool>
m.is_symmetric() -> bool                   m.is_symmetric() -> Option<bool>
m.cholesky() -> Option<Matrix>             m.cholesky() -> Result<Matrix>
m.lu() -> (L, U, perm)                     m.lu() -> Result<(L, U, perm)>
m.minor(i, j) -> Matrix                    m.minor_matrix(i, j)?    // sub-matrix
                                           m.minor(i, j)?           // Result<Ex>: its determinant
Matrix::from_i64(&ctx, rows) -> Matrix     Matrix::from_i64(&ctx, rows)?
m.add_elementwise(&n) / sub_elementwise    m.add(&n)? / m.sub(&n)?   (or &m + &n)
Matrix::try_identity / try_zeros           Matrix::identity / zeros

char_poly(&lam) still takes the variable you want in the output. Structure tests on symbolic matrices return None when undecidable — replace if m.is_symmetric() with if m.is_symmetric() == Some(true).

Linear systems

// 0.1
let values: Vec<Ex> = ctx.solve_system(&eqs, &vars);
// 0.2
match ctx.solve_system(&eqs, &vars)? {
    LinearSolution::Unique(pairs) => …,
    LinearSolution::Parametric { solution, free } => …,
    LinearSolution::Inconsistent => …,
}
// or: let sol = linsolve(&eqs, &vars)?; sol.get(&x)

polysys::solve_system_ex now returns algebraic solutions (radicals) where 0.1 returned only rational ones, and Err(InfiniteSolutions) for positive-dimensional systems.

Complex parts

let z = ctx.symbol("z");
z.re()          // 0.1: z        (assumed real — wrong)
                // 0.2: re(z)    (unevaluated until z is known real)
z.conjugate()   // 0.1: z        // 0.2: conjugate(z)

Declare ctx.symbol_with("z", &[Assumption::Real]) to recover the 0.1 behaviour where it was intended.

has_unevaluated and RootOf

RootOf / RootSum no longer count as unevaluated, so try_integrate, try_solve_ode, … succeed on results containing them. If you used has_unevaluated() to detect degree-≥5 roots, check for the node instead: expr_type() still reports ExprType::Unevaluated for a RootOf at the root of an expression, so root.expr_type() == ExprType::Unevaluated keeps working for the solutions returned by solve.

Other signature changes

0.10.2
StateSpace::poles(&s)StateSpace::poles()
vector::is_conservative(…) -> bool-> Option<bool> (also is_irrotational, is_solenoidal)
SetEx::contains(&e) structuralset membership, Option<bool>
expr.textplot(…) -> String-> Result<String> (all plotting methods)
Ex::differentiate_finite(...)differentiate_finite(&var, &points, order)
FormalPowerSeries over Ratioover Ex (coefficient(k) -> Ex, coefficient_rational(k) -> Option<Ratio>)
finite_diff::* over Ratioover Ex
iter.sum::<Ex>() on empty → 0panics; use ctx.sum(iter) or Option<Ex>
Debug for Ex prints idsprints Ex(x^2 + 1)
expand() splits (x·y)^ano longer for unknown-sign symbols; expand_power_base(true)
Assumption enumnew variants ExtendedReal, NotPositive, NotZero, … — add a _ => arm
OdeType enumnew variants — add a _ => arm
d/dx digamma(x) → formal derivativepolygamma(1, x)
Digamma(5) staysfolds to -EulerGamma + 25/12

Results that changed because 0.1 was wrong

  • fourier_series of |x|, sign(x) and piecewise inputs (coefficients are now exact definite integrals).
  • One-sided limits: limit returns a Limit node when the two one-sided limits differ, instead of one of them.
  • Several Gruntz limits of exp/ln towers.
  • matrix_exp with numeric complex eigenvalues (a sin(−1) parity error).
  • Factoring is no longer truncated at small degrees: factor may now split polynomials that 0.1 left whole.
  • Shifted alternating half-integer p-series had a sign error.

New things worth adopting

  • Context::from_f64 (exact dyadic) / from_f64_approx(v, max_denominator) for ingesting floats — symplex-build and symplex-wasm now use these for DH parameters (0.33/10).
  • solve_general for periodic equations; linsolve for linear systems; solve_ode_ivp for initial-value problems.
  • to_c_fn for C targets; compile_many for gradients.
  • simplify_traced / rewrite_traced when a simplification surprises you.