Release Notes¶
0.8.3¶
Tooling-only release. No Python source-code, test, or runtime behaviour changes. The PyPI wheel for 0.8.3 is bit-identical to 0.8.2 modulo the new in-tree skill files (which are not bundled into the wheel).
Tooling¶
New Claude Code skill at
.claude/skills/solverz-modeling/that teaches Claude how to use Solverz for symbolic modeling and numerical simulation. Bundles:SKILL.md— 4-step workflow (equation type → build → compile → solve),Var/Param/Eqn/Odeidioms, inline vsmodule_printerdecision, every built-in solver with when-to-use notes,Mat_Mulfast vs fallback path with the full rewrite table, common pitfalls table, and a quick-reference card.references/ecosystem.md— chapter map of the Solverz Cookbook, every reusable block in SolMuseum (gt,pv,st,eb,eps_network,heat_network,gas_network,pde.heat,pde.gas), and every helper in SolUtil (PowerFlow,DhsFlow,GasFlow,DhsFaultFlow).references/examples/— six canonical end-to-end runnable examples covering AE / DAE / FDAE / mutable Jacobian / events /AliasVar/Mat_Mul/model.add()composition:bouncing-ball.md— minimal DAE with event handlingpower-flow.md— canonical AE withMat_Mul(case30, rectangular coordinates)heat-flow.md— AE with mutable-matrix Jacobianm3b9-dynamics.md— DAE withTimeSeriesParamfault scenariogas-characteristics.md— FDAE withAliasVar(method of characteristics)integrated-energy-system.md— multi-domain DAE composition using all three SolUtil flow solvers + the major SolMuseum DAE / AE blocks viamodel.add(...)
README.md— install command (symlink) and sync rule for contributors.
Install on a contributor’s machine:
ln -sfn "$(pwd)/.claude/skills/solverz-modeling" \ ~/.claude/skills/solverz-modeling
After the symlink is in place,
git pullon a Solverz checkout updates the skill content automatically — no re-install step. Inside a Solverz checkout itself the skill auto-loads even without the global symlink, because Claude Code reads<cwd>/.claude/skills/.
The skill is not bundled into the PyPI wheel — it lives only in the source tree. Pip-installed users who want the skill should clone the repo separately.
The contribution guide in .claude/skills/solverz-modeling/README.md
documents the sync rule for Solverz contributors: when a PR changes
Solverz’s public API, the same PR should update the relevant skill
files. Reviewers will check both.
0.8.2¶
Documentation-only release. No source-code, test, or behaviour changes. The PyPI wheel for 0.8.2 is bit-identical to 0.8.1 modulo the in-tree documentation.
The release rewrites the Matrix-Vector Calculus chapter to address eight reviewer comments about misleading language, stale code references, missing terminology, and content duplication with the Solverz Cookbook power-flow chapter.
Documentation¶
New Glossary section at the top of the matrix calculus chapter, defining SpMV, SpMM, CSC / CSR, @njit, scatter-add, fancy indexing, fast path, fallback path, lambdify, hot F / hot J, cold compile, LICM, inline mode, and module printer mode. Every body usage of these terms now hyperlinks to the glossary entry.
Supported Operations table clarified. The third column was renamed from “Derivative” to “Jacobian block (∂/∂x for vector x)” and a paragraph was added above the table explaining that Solverz first computes the elementwise vector derivative (e.g.
cos(x)forsin(x)) and only inserts thediag(...)matrix wrapper at Jacobian assembly time. The previous wording risked making vector-equation users thinksin(x)produces a matrix directly.Stale
Mat_Mul+ scipy.sparse claim removed. The first note block under## Supported Operationspreviously said “Mat_Mul uses scipy.sparse directly”, which was true for 0.8.0 but false for 0.8.1 (where the fast path moves the matvec intoSolCF.csc_matvecinsideinner_F). The note now describes the fast / fallback split correctly and points users at the Layer 1 discussion.Newton-step language replaced with solver-neutral phrasing.
matrix_calculus.mdis the API reference for Solverz’s matrix calculus engine, which is shared across AE / FDAE / DAE / ODE. Wherever the previous text said “every Newton step assembles Jacobian block data”, it now says “everyJ_(y, p)call” or “every solver step”, with one explicit paragraph noting that the cost model applies to allJ_consumers, not just algebraic-equation Newton solvers. The remaining “Newton” references are intentional and concrete (e.g. naming Newton-Raphson as one specific solver among several).Layer 1 narrative refreshed for the second-pass review fixes. References to the obsolete
_is_csc_matvec_fast_pathhelper were updated to the current_classify_matmul_placeholders(with inner shape predicate_shape_is_fast). A new paragraph describes the dependency-aware demotion introduced in the second-pass review fix: a fast-path candidate consumed by a fallback placeholder’s matrix or operand expression is demoted to fallback so the wrapper never emits a reference to a not-yet-materialised placeholder.Fallback path subsections rewritten. The earlier “Why not fancy indexing?” subsection title and prose suggested that fancy indexing was an option Solverz “still supports”. The new title is “Two paths: scatter-add (fast) and fancy indexing (fallback)”, and the body makes explicit that both paths always co-exist in every generated module — the runtime picks per Jacobian block based on what the symbolic classifier recognised at code-gen time. There is no on/off switch.
Benchmark environment block added to the Performance subsection: hardware (Apple M4), OS (macOS 26.4), Python (3.11.13), library versions (numpy 2.3.3, scipy 1.16.0, numba 0.65.0, sympy 1.13.3, Solverz 0.8.1+), and methodology (10 warm-up + 5000–20000 timed iterations, median of three repeats). Without this metadata the absolute numbers in the doc were unreproducible.
“When to use Mat_Mul” section slimmed down, deferring the full case30 decision matrix and the known hot-F regression case to the Solverz Cookbook power-flow chapter. The Solverz-dev doc is the API reference and now keeps just the 3-bullet API guidance plus the “matrix shapes that fall out of the fast path” reference list. The Cookbook is the right place for the case-driven narrative; this avoids contradictions when one of the two docs drifts.
The companion Solverz-Cookbook v0.8.2 release does the same
three things in pf.md (terminology cleanup, benchmark environment
block, refined “Newton step” wording on the one line where it
overgeneralised). The Cookbook’s heat flow chapter is unchanged.
0.8.1¶
Hotfix release addressing correctness findings in the 0.8.0 Mat_Mul
mutable-matrix Jacobian code generation. Users of Mat_Mul should
upgrade — 0.8.0 has a latent miscompilation when two independent
Diag(...) terms land on the same (i,i) positions (see Bug Fixes).
The release also brings a large runtime-performance improvement to
the Mat_Mul hot-F path (see Performance below).
Performance¶
Mat_Mulhot F: 4.4× speedup on small networks. Before 0.8.1 everyMat_Mul(A, v)precompute executed as ascipy.sparseSpMV in the PythonF_wrapper (_sz_mm_N = A @ v). On small systems this was dispatch-bound: each SpMV crossed Python → scipy → C → back and cost ≈ 1.5 µs per call of pure overhead, regardless of how little arithmetic the matrix actually had. Eight SpMVs per power-flowF_()call added up to ≈ 12 µs of scipy dispatch on case30, compared to well under 1 µs of actual matvec work.The 0.8.1 code generator now recognises plain sparse
dim=2Paramatrix operands and emits the matvec insideinner_Fusing the existingSolCF.csc_matvecNumba helper (Solverz/num_api/custom_function.py). The CSC decomposition (<name>_data/<name>_indices/<name>_indptr/<name>_shape0) thatSolverz/model/basic.pyalready emits for every sparsedim=2Paramis the direct input for the helper — no newsettingentries, no new helper function.Case30 power-flow benchmark (per
F_()call, Apple M4):Formulation
0.8.1 baseline
0.8.1 fast path
Speedup
Mat_Mul(rectangular)14.1 µs
3.23 µs
4.4×
For-loop (polar) reference
1.40 µs
1.11 µs
—
The
Mat_Mul/ polar hot-F ratio drops from 10.1× to 2.9×.Jcall, cold compile, module render, and every other phase are unchanged. The remaining 2.9× gap is the structural cost of 8SolCF.csc_matveccalls + 3 sub-function calls + dispatcher in Mat_Mul vs 53 inlined scalar kernels in polar; closing it further would require SpMV fusion or switching to CSR format, neither of which is in this release. See When to use (and not use) Mat_Mul in the matrix calculus guide for a full decision matrix.Fallback path —
Mat_Mulplaceholders whose matrix operand is not a plain sparsePara(negated matricesMat_Mul(-A, x), nested matrix expressions, densedim=2params) keep the old scipy SpMV path in the wrapper. They are functionally correct but do not benefit from the fast path; users who hit performance regressions should check whether they can rewrite-Mat_Mul(A,x)instead ofMat_Mul(-A,x), or declare matrices withsparse=True.print_Fdead-load cleanup. With the fast path in place, theF_wrapper no longer loadsA = p_["A"]for sparsedim=2matrices that are used only as fast-pathMat_Muloperands — previously that line was dead but still emitted. The filter inspects each placeholder’smatrix_argand only keeps the matrix load if at least one fallbackMat_Mulneeds it.
Bug Fixes¶
Multiple
Diagterms now accumulate correctly. Equations whose Jacobians contain two or more independentDiagterms sharing output positions — e.g.x*(A@x) + x*(B@x)producingdiag(A@x) + diag(B@x) + diag(x)@A + diag(x)@B— previously had one of the two diagonal contributions silently overwritten in the module-printer path. The scatter-add kernel now uses+=for every term, including diag. Inline mode was already correct. Regression test:test_multi_diag_accumulation.Modelno longer crashes on densedim=2parameters.model.create_instance()unconditionally tried to decompose everydim=2parameter into sparse CSC flat arrays (.data,.indices,.indptr), which for a densendarrayfed amemoryviewintoArrayand raisedTypeError: Unsupported array type <class 'memoryview'>. Decomposition is now restricted tosparse and dim == 2; dense matrices pass through untouched.Selective
@njitgating respects sparse parameter content. Theinner_F/inner_Jhelpers are now generated without@njitwhen the generated parameter list contains a sparsedim=2param, a triggerable param, or aTimeSeriesParam— objects Numba cannot lower. Pure element-wise models are unaffected.FormJacandJacBlockagree on the mutable-matrix predicate. The “is this block a mutable-matrix block?” decision is now made in a single place using the same criterion on both sides — matrix-valued derivative that is not a plainPara/-Para. PreviouslyFormJacadditionally required the expression to contain bothMat_MulandDiag, whileJacBlock.is_mutable_matrixdid not. The divergence would have let a derivative likeDiag(x)skip theSpDiagperturbation step and produce a shrunkenCooRow/CooColat a flat start — the downstream scatter-add kernel would then write to fewer output positions than the runtime expected. No known model hit this in practice but the fix closes the corner case.
API Changes¶
Time-varying sparse
dim=2Params are now rejected at construction. Declaring aParam(..., dim=2, sparse=True, triggerable=True, ...)or aTimeSeriesParam(..., dim=2, sparse=True)raisesNotImplementedErrorat the point of construction, regardless of whether the parameter is ever referenced in an equation.Every Solverz code path that consumes a sparse
dim=2Paramcaches its CSC decomposition (<name>_data,<name>_indices,<name>_indptr,<name>_shape0) at model-build time: the legacyMatVecMulpipeline, the new 0.8.1Mat_MulSolCF.csc_matvecfast path, and the mutable-matrix Jacobian scatter-add kernel all read the frozen flats. A runtimetrigger_funfiring or aget_v_t(t)update simply gets lost, and the Newton iteration either diverges or silently converges to the wrong solution.Earlier 0.8.1 drafts narrowed this check to “sparse dim=2 time-varying Para used as the matrix operand of a
Mat_Mul”, catching the combination only atFormJactime. That was a loophole: legacyMatVecMulusage, or any other code path that consumes the CSC flats, slipped through. The 0.8.1 policy closes the loophole by rejecting the shape at the exact line where it is declared — the error message now points at the user’s source, not a deep internal.The check lives in
ParamBase.__init__andTimeSeriesParam.__init__, with a backstop inEquations._check_no_timevar_sparse_matrices(runs on everyFormJaccall) for the edge case where a taintedParamwas built via__new__+ attribute assignment, bypassing the__init__guard.Allowed alternatives: triggerable / time-series vectors or scalars sitting next to a
Mat_Mul, element-wise formulations where per-row coefficients are 1-D time-varying parameters, and densedim=2parameters (sparse=False, which takes theMutableMatJacDataModulefallback path that re-evaluates the full block expression on everyJ_()call and tolerates runtime updates). See the Restrictions section of the Matrix Calculus guide for full workarounds.Reserved symbol prefixes
_sz_mm_and_sz_mb_. Any user symbol (Var,Param,iVar,Para, …) whose name starts with either prefix is rejected at construction time with aValueError. These prefixes are used by the code generator for Mat_Mul precompute helpers and mutable-matrix Jacobian block helpers. The check is bypassed internally viainternal_use=True.Dense
dim=2params inMat_Mulemit a one-shotUserWarning. A parameter declared withdim=2, sparse=Falseand used inside aMat_Mulworks correctly via the fallback path but forfeits the scatter-add fast path.FormJacnow warns once per offending parameter to flag the performance cost. See the new Restrictions and reserved names section of the matrix calculus guide for migration guidance.
Documentation¶
Extended Matrix-Vector Calculus:
New Restrictions and reserved names section documenting the three API boundaries introduced in 0.8.1.
Code examples updated to show the
_sz_mm_/_sz_mb_helper names actually emitted by the code printer.Explicit note about the
+=accumulation rule for diagonal scatter-add terms.New explicit list of the cases that push a mutable-matrix block onto the fallback path — useful when a model is slower than expected and you want to know whether a block is hitting the fast or slow path.
The immutability warning now spells out that the restriction only strictly applies to matrices used in the vectorised
Mat_Mulfast path (the fallback path re-evaluates the full expression each call and would reflect mutations), but still recommends treating all sparse matrix params as immutable.
Internal cleanup¶
Removed the dead
include_sparse_in_listparameter fromprint_F/print_inner_F/print_J/print_inner_Jand from_has_sparse_in_param_list. With the Mat_Mul precompute architecture every caller hard-codedFalseand the parameter was vestigial.Dropped the unused
_var_base_name/_var_access_exprhelpers frommutable_mat_analyzer; they were leftovers from an earlier draft ofprint_inner_J.
Full Changelog¶
0.8.0¶
Highlights¶
Complete Matrix-Vector Calculus — Solverz now fully supports symbolic
differentiation of mixed matrix-vector equations. Write equations like
e*(G@e - B@f) + f*(B@e + G@f) - P and get analytical Jacobians automatically.
Unified Mat_Mul Interface — Mat_Mul(A, x) replaces the legacy MatVecMul
as the standard matrix-vector product. It uses scipy.sparse directly (faster than
the old csc_matvec) and supports full matrix calculus.
New Features¶
Matrix calculus operators:
exp,sin,cos,ln, power (**),transpose, andDiagnow work inside matrix-vector expressions with automatic differentiation.from Solverz import Mat_Mul, Var, Param, Model, Eqn from Solverz.sym_algebra.functions import exp m = Model() m.A = Param('A', [[1, 0], [0, 2]], dim=2, sparse=True) m.x = Var('x', [1, 1]) m.f = Eqn('f', exp(Mat_Mul(m.A, m.x))) # Jacobian: diag(exp(A@x)) @ A
Mutable matrix Jacobian: Variable-dependent matrix derivatives (e.g.,
diag(e)@G + diag(f)@Bfrom power flow equations) are now evaluated dynamically at each Newton step. The sparsity pattern is determined at initialization and remains fixed; only the data values are updated.Selective Numba
@njit: In module mode, Numba compilation is applied selectively — equations usingMat_Mulrun withscipy.sparse(fast C-level sparse operations), while pure element-wise equations retain@njitacceleration.atan2symbolic function: Addedatan2(y, x)for computing the two-argument arctangent in symbolic equations.Plugin-based module discovery: Third-party numerical modules (e.g., SolMuseum) are now discovered via
entry_points(group='solverz.num_api')instead of hard-coded imports. Packages register viapyproject.toml[project.entry-points."solverz.num_api"]. Closes #118.Improved solution stats: Solvers now record more detailed statistics and profiling information in the solution object.
Bug Fixes¶
Stabilized solution slicing and incidence matrix helpers.
Deprecations¶
MatVecMulis deprecated — useMat_Mul(A, x)instead.MatVecMulwill emit aDeprecationWarningwhen used. It will be removed in a future release.# Before (deprecated): from Solverz import MatVecMul m.f = Eqn('f', MatVecMul(m.A, m.x) - m.b) # After (recommended): from Solverz import Mat_Mul m.f = Eqn('f', Mat_Mul(m.A, m.x) - m.b)
Documentation¶
New: Matrix-Vector Calculus — functionality, mathematical background, and application examples (power flow, heat network, nonlinear equations).
New: Extending Matrix Calculus — developer guide for adding new operations to the matrix calculus module.
Updated: Getting Started — matrix equation examples now use
Mat_Mul.