Matrix Ops¶
matrix_ops
¶
Matrix operations and linear solvers for multiphysics modeling.
This module provides small, performance-oriented numerical utilities used by multiphysics engines:
- Construction of common 1D linear operators (e.g., upwind advection, Laplacian).
- Cached implicit solves for repeated linear systems with fixed operators.
- High-throughput aggregation utilities for large numbers of subpopulations.
- Optional Kronecker composition utilities for separable multi-axis operators.
Design notes
- Dense implicit solves use the input state's Array-API
linalg.solve. - Sparse acceleration is selected structurally from a registry containing SciPy and, when installed, CuPy adapters.
- Cache semantics: sparse factorizations are keyed by (id(left_op), id(right_op)) within each ecosystem and retain those exact operator objects to prevent stale hits after Python ID recycling. For caching to be effective, operator objects must be constructed once and reused.
Stage operator factories (IMEX/TR-BDF2 support): TR-BDF2 and similar IMEX methods can require stage-specific implicit operators that depend on: - dt (time step) - scale (method stage scalar) - t (stage time) - y (stage state) - stage (a label, e.g. "tr" or "bdf2")
This module supports dynamic base operators via a builder:
base_builder(t, y, stage) -> Operator
Then the stage-operator factory produces (L, R) to solve:
L @ y_next = R @ y_in
where (L, R) follow schemes like implicit Euler or trapezoidal.
DiffusionConfig(coeff, dtype=np.float64, bc='neumann')
dataclass
¶
Configuration for diffusion-like linear operators.
Attributes:
| Name | Type | Description |
|---|---|---|
coeff |
float
|
Physical diffusion coefficient D (units length^2 / time). |
dtype |
DTypeLike
|
Floating dtype (e.g. np.float64). |
bc |
str
|
Boundary condition; either "neumann" or "absorbing". |
__post_init__()
¶
Validate context-free diffusion parameters.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the coefficient or boundary condition is invalid. |
Source code in src/op_engine/matrix_ops.py
157 158 159 160 161 162 163 164 165 166 167 168 | |
GridGeometry(n, dx)
dataclass
¶
Geometry of a 1D spatial grid.
Attributes:
| Name | Type | Description |
|---|---|---|
n |
int
|
Number of grid points. |
dx |
float
|
Grid spacing. |
__post_init__()
¶
Validate context-free grid geometry.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the grid size or spacing is invalid. |
Source code in src/op_engine/matrix_ops.py
129 130 131 132 133 134 135 136 137 138 139 140 | |
SparseAdapter(ecosystem_id, issparse, factorize=None, solve=None, cache_key=None, linear_solver=None, solve_options=LinearSolveOptions(), preconditioner=None, context=None, accepts_right=None)
dataclass
¶
Function bundle in the existing sparse/operator registry.
Sparse acceleration is optional: dense operators always fall back to the
input array's Array-API linalg.solve implementation. Adapters are
selected structurally with their own issparse predicate. Existing
factorize/solve/cache_key bundles retain their historical identity cache.
Alternatively, linear_solver opts into a checked, uncached eager NumPy
solve. The two routes are mutually exclusive. accepts_right optionally
recognizes mixed right operators; otherwise issparse must accept both.
New callbacks own preparation, context invalidation and preconditioner use.
__post_init__()
¶
Reject incomplete or conflicting dispatch routes.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the identifier or route configuration is invalid. |
TypeError
|
If a predicate or callback is not callable. |
Source code in src/op_engine/matrix_ops.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
StageOperatorContext(t, y, stage=None, extra=None)
dataclass
¶
Context passed to time/state-dependent operator builders.
Attributes:
| Name | Type | Description |
|---|---|---|
t |
float
|
Stage time. |
y |
Array
|
Stage state in its runtime Array-API namespace. |
stage |
StageName
|
Optional stage label (e.g. "tr", "bdf2"). |
extra |
Any | None
|
Optional extra payload for future use (kept generic). |
build_advection_matrix(n, dx, velocity, *, bc='absorbing', reference=None)
¶
Build a dense first-order upwind finite-volume operator.
The returned matrix A acts on a column state as dy = A @ y.
Positive velocity transports values toward increasing coordinate indices;
negative velocity transports toward decreasing indices. The velocity may be
an Array-API scalar, so its value stays dynamic under transformations such
as JAX jit and grad.
Boundary modes are:
absorbing: zero inflow at the upstream boundary and free outflow at the downstream boundary;reflecting: zero flux through the downstream boundary;periodic: downstream outflow wraps to the upstream boundary.
The namespace comes from reference when provided, then from velocity;
plain numeric velocities use NumPy. Grid geometry and the boundary mode are
static structural inputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of uniformly spaced finite-volume cells. |
required |
dx
|
float
|
Positive cell width. |
required |
velocity
|
float | Array
|
Signed scalar transport velocity. |
required |
bc
|
str
|
Boundary mode: |
'absorbing'
|
reference
|
Array | None
|
Optional array whose namespace and floating dtype control the result. |
None
|
Returns:
| Type | Description |
|---|---|
Array
|
Dense |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the grid, velocity shape/value, or boundary mode is invalid. |
Source code in src/op_engine/matrix_ops.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | |
build_crank_nicolson_operator(geom, cfg, dt)
¶
Build Crank-Nicolson operators with dense/sparse autodispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geom
|
GridGeometry
|
Grid geometry. |
required |
cfg
|
DiffusionConfig
|
Diffusion configuration. |
required |
dt
|
float
|
Time step. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Operator, Operator]
|
Tuple of (L, R) operators for Crank-Nicolson scheme. |
Source code in src/op_engine/matrix_ops.py
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
build_diffusion_matrix(n, dx, coefficient, *, grid=None, bc='neumann', reference=None)
¶
Build a dense second-order 1D finite-volume diffusion operator.
The returned matrix A acts on a column state as dy = A @ y.
Supply either a uniform cell width with dx or strictly increasing
cell-center coordinates with grid. On a non-uniform grid, interior
flux differences are divided by the local Voronoi-cell width. Boundary
faces are inferred one half-spacing beyond the first and last centers.
The coefficient may be an Array-API scalar, so it stays dynamic under
transformations such as JAX jit and grad. Static geometry is
assembled with NumPy and transferred once to the namespace selected by
reference or coefficient.
Boundary modes are:
neumannorreflecting: zero flux at both boundaries;absorbing: zero-valued exterior ghost cells;periodic: the two grid ends are adjacent. A grid passed explicitly must be uniform because its coordinates do not determine wrap spacing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of finite-volume cells. |
required |
dx
|
float | None
|
Positive uniform cell width, or |
required |
coefficient
|
float | Array
|
Non-negative scalar diffusion coefficient. |
required |
grid
|
ArrayLike | None
|
Optional strictly increasing cell-center coordinates of shape
|
None
|
bc
|
str
|
Boundary mode: neumann, reflecting, absorbing, or periodic. |
'neumann'
|
reference
|
Array | None
|
Optional array whose namespace and floating dtype control the result. |
None
|
Returns:
| Type | Description |
|---|---|
Array
|
Dense |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the geometry, coefficient, or boundary mode is invalid. |
Source code in src/op_engine/matrix_ops.py
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 | |
build_identity_operator(n, *, dtype=np.float64, prefer_sparse=None)
¶
Build an identity operator with dense/sparse autodispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Size of the identity operator (n x n). |
required |
dtype
|
DTypeLike
|
Floating dtype (e.g. np.float64). |
float64
|
prefer_sparse
|
bool | None
|
If True, always return a sparse operator; if False, always return a dense operator; if None, autodispatch based on n. |
None
|
Returns:
| Type | Description |
|---|---|
Operator
|
Identity operator of shape (n, n) as either a dense ndarray or CSR matrix. |
Source code in src/op_engine/matrix_ops.py
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 | |
build_implicit_euler_operators(base_op, dt_scale)
¶
Build implicit Euler operators for a time-scaled linear operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_op
|
Operator
|
Base linear operator A. |
required |
dt_scale
|
float
|
Time-step scaling factor (dt * scale). |
required |
Returns:
| Type | Description |
|---|---|
tuple[Operator, Operator]
|
Tuple of (L, R) operators for implicit Euler scheme. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If dt_scale is not finite. |
Source code in src/op_engine/matrix_ops.py
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 | |
build_laplacian_tridiag(n, dx, coeff, dtype=np.float64, bc='neumann', *, grid=None)
¶
Build a sparse 1D finite-volume Laplacian.
The resulting operator corresponds to coeff * Δ_h. Supply either a
uniform cell width with dx or strictly increasing cell-center
coordinates with grid. No time-step scaling is applied here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of finite-volume cells. |
required |
dx
|
float | None
|
Positive uniform cell width, or |
required |
coeff
|
float
|
Physical diffusion coefficient D (units length^2 / time). |
required |
dtype
|
DTypeLike
|
Floating dtype (e.g. np.float64). |
float64
|
bc
|
str
|
Boundary condition; either |
'neumann'
|
grid
|
ArrayLike | None
|
Optional strictly increasing cell-center coordinates of shape
|
None
|
Returns:
| Type | Description |
|---|---|
csr_matrix
|
Sparse CSR matrix representing the Laplacian operator. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the geometry or boundary condition is invalid. |
Source code in src/op_engine/matrix_ops.py
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 | |
build_predictor_corrector(base_matrix)
¶
Build predictor-corrector matrices with dense/sparse autodispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_matrix
|
DenseOperator | csr_matrix
|
Base linear operator A. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Operator, Operator, Operator]
|
Tuple of (predictor, L, R) operators for predictor-corrector scheme. |
Source code in src/op_engine/matrix_ops.py
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 | |
build_trapezoidal_operators(base_op, dt_scale)
¶
Build trapezoidal operators for a time-scaled linear operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_op
|
Operator
|
Base linear operator A. |
required |
dt_scale
|
float
|
Time-step scaling factor (dt * scale). |
required |
Returns:
| Type | Description |
|---|---|
tuple[Operator, Operator]
|
Tuple of (L, R) operators for trapezoidal scheme. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If dt_scale is not finite. |
Source code in src/op_engine/matrix_ops.py
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 | |
clear_implicit_solver_cache()
¶
Clear the internal implicit solver cache.
Source code in src/op_engine/matrix_ops.py
1064 1065 1066 | |
encode_groups(group_ids, n_groups, *, prefer_sparse=None, dtype=np.float64)
¶
Encode group IDs into a one-hot group membership matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_ids
|
NDArray[integer]
|
1D array of integer group IDs for each item. |
required |
n_groups
|
int
|
Total number of groups. |
required |
prefer_sparse
|
bool | None
|
If True, always return a sparse matrix; if False, always return a dense array; if None, autodispatch based on n_groups. |
None
|
dtype
|
DTypeLike
|
Data type for the output matrix. |
float64
|
Returns:
| Type | Description |
|---|---|
csr_matrix | DenseOperator
|
A (n_groups, n_items) one-hot encoded group membership matrix. |
Source code in src/op_engine/matrix_ops.py
1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 | |
grouped_count_ids(group_ids, n_groups)
¶
Perform grouped count using group IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_ids
|
NDArray[integer]
|
1D array of integer group IDs. |
required |
n_groups
|
int
|
Total number of groups. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 1D array of length n_groups where each element contains the count of |
NDArray[floating]
|
occurrences of the corresponding group ID. |
Source code in src/op_engine/matrix_ops.py
1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 | |
grouped_sum_ids(values, group_ids, n_groups)
¶
Perform grouped sum over 1D values array using group IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
NDArray[floating]
|
1D array of values. |
required |
group_ids
|
NDArray[integer]
|
1D array of integer group IDs. |
required |
n_groups
|
int
|
Total number of groups. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 1D array of length n_groups where each element contains the sum of values |
NDArray[floating]
|
for the corresponding group ID. |
Source code in src/op_engine/matrix_ops.py
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 | |
grouped_sum_ids_2d(values, group_ids, n_groups)
¶
Perform grouped sum over 2D values array using group IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
NDArray[floating]
|
2D (N, K) array where N is num of items and K is num of features. |
required |
group_ids
|
NDArray[integer]
|
1D array of integer group IDs of length N. |
required |
n_groups
|
int
|
Total number of groups. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 2D array of shape (n_groups, K) where each row contains the sum of values |
NDArray[floating]
|
for the corresponding group ID. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If values is not 2D or if group_ids length does not match the number of items in values. |
Source code in src/op_engine/matrix_ops.py
1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 | |
implicit_solve(left_op, right_op, x)
¶
Perform an implicit solve with dense fallback and cached sparse dispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left_op
|
object
|
Left operator L in the equation L @ y = R @ x. |
required |
right_op
|
object
|
Right operator R in the equation L @ y = R @ x. |
required |
x
|
Array
|
1D or 2D array representing the input vector(s). |
required |
Returns:
| Type | Description |
|---|---|
Array
|
A 1D or 2D array containing the solution vector(s) y. |
Source code in src/op_engine/matrix_ops.py
1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 | |
kron_prod(a, b)
¶
Compute the Kronecker product of two operators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Operator
|
First operator. |
required |
b
|
Operator
|
Second operator. |
required |
Returns:
| Type | Description |
|---|---|
Operator
|
The Kronecker product operator. |
Source code in src/op_engine/matrix_ops.py
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 | |
kron_sum(ops)
¶
Compute a Kronecker sum of square operators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ops
|
list[Operator]
|
List of 2D square operators. |
required |
Returns:
| Type | Description |
|---|---|
Operator
|
The Kronecker sum operator. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If ops is empty or if operators are not square or have incompatible shapes. |
Source code in src/op_engine/matrix_ops.py
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 | |
make_constant_base_builder(operator)
¶
Convenience: wrap a constant operator as a BaseOperatorBuilder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
Operator
|
Constant operator to wrap. |
required |
Returns:
| Type | Description |
|---|---|
BaseOperatorBuilder
|
A BaseOperatorBuilder that always returns the given operator. |
Source code in src/op_engine/matrix_ops.py
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 | |
make_stage_operator_factory(base_builder, *, scheme='implicit-euler')
¶
Create a stage operator factory supporting time/state dependent base ops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_builder
|
BaseOperatorBuilder
|
Function that builds a base operator given stage context. |
required |
scheme
|
str
|
Implicit scheme; either "implicit-euler" or "trapezoidal". |
'implicit-euler'
|
Returns:
| Type | Description |
|---|---|
StageOperatorFactory
|
A StageOperatorFactory that builds (L, R) operators for the given scheme. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an unknown scheme is provided. |
Source code in src/op_engine/matrix_ops.py
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 | |
matrix_grouped_count(group_matrix)
¶
Perform grouped count using a group matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_matrix
|
csr_matrix | DenseOperator
|
2D group matrix (csr_matrix or dense ndarray). |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 1D array containing the counts for each group. |
Source code in src/op_engine/matrix_ops.py
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 | |
matrix_grouped_sum(group_matrix, values)
¶
Perform grouped sum using a group matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_matrix
|
csr_matrix | DenseOperator
|
2D group matrix (csr_matrix or dense ndarray). |
required |
values
|
NDArray[floating]
|
1D array of values to be summed. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 1D array containing the grouped sums. |
Source code in src/op_engine/matrix_ops.py
1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 | |
matrix_masked_sum(mask_matrix, data)
¶
Perform masked sum using a mask matrix and data array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mask_matrix
|
csr_matrix | DenseOperator
|
2D mask matrix (csr_matrix or dense ndarray). |
required |
data
|
NDArray[floating]
|
1D or 2D data array to be masked and summed. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
A 1D or 2D array containing the masked sums. |
Source code in src/op_engine/matrix_ops.py
1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 | |
register_sparse_adapter(adapter, *, replace=False)
¶
Register an operator adapter in the existing sparse dispatch registry.
Replacement clears that ecosystem's cached factors. Predicates must be pure; overlapping pair matches are rejected when dispatching a solve.
Raises:
| Type | Description |
|---|---|
ValueError
|
If an identifier already exists without explicit replacement. |
Source code in src/op_engine/matrix_ops.py
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 | |
smooth(x, alpha=0.02, out=None)
¶
Apply simple smoothing along the last axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
NDArray[floating]
|
Input array to smooth. |
required |
alpha
|
float
|
Smoothing factor between 0 and 1. |
0.02
|
out
|
NDArray[floating] | None
|
Optional output array to store the result. |
None
|
Returns:
| Type | Description |
|---|---|
NDArray[floating]
|
Smoothed array with the same shape as x. |
Source code in src/op_engine/matrix_ops.py
1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 | |
unregister_sparse_adapter(ecosystem_id)
¶
Remove an adapter and release all of its cached factors.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the adapter is not registered. |
Source code in src/op_engine/matrix_ops.py
1091 1092 1093 1094 1095 1096 1097 1098 | |