|
1 | | -# chromosim/numerics/build_matrices.py |
2 | | -""" |
3 | | -Finite-volume derivative matrices in z for 1D packed-column models. |
4 | | -
|
5 | | -This is a direct Python/NumPy/SciPy port of the MATLAB build_matrices.m: |
6 | | -
|
7 | | -- First derivative (D1): backward/upwind stencil, inlet row is zeroed |
8 | | - because the inlet boundary condition is handled explicitly in the ODE. |
9 | | -- Second derivative (D2): central stencil with: |
10 | | - * inlet ghost-cell treatment: D2[0,0] = -2/dz^2 (Dirichlet-type) |
11 | | - * Danckwerts-style outlet: D2[-1,-1] = 1/dz^2 |
12 | | -
|
13 | | -Returns the same 'par' object/dict with .D1 and .D2 as CSR sparse matrices. |
14 | | -""" |
15 | | - |
16 | 1 | from __future__ import annotations |
| 2 | +from collections.abc import Mapping |
17 | 3 | import numpy as np |
18 | 4 | from scipy.sparse import diags |
| 5 | +from .sparsity import sparsity_pattern_multi |
19 | 6 |
|
20 | 7 | def _get(par, key): |
21 | | - """Support both dicts and objects with attributes.""" |
22 | | - try: |
23 | | - return getattr(par, key) |
24 | | - except AttributeError: |
| 8 | + """Get a parameter from either a Mapping (dict) or an object with attrs.""" |
| 9 | + if isinstance(par, Mapping): |
25 | 10 | return par[key] |
| 11 | + return getattr(par, key) |
26 | 12 |
|
27 | 13 | def _set(par, key, value): |
28 | | - """Support both dicts and objects with attributes.""" |
29 | | - try: |
30 | | - setattr(par, key, value) |
31 | | - except AttributeError: |
| 14 | + """Set a parameter on either a Mapping (dict) or an object with attrs.""" |
| 15 | + if isinstance(par, Mapping): |
32 | 16 | par[key] = value |
| 17 | + else: |
| 18 | + setattr(par, key, value) |
33 | 19 |
|
34 | 20 | def build_matrices(par): |
35 | 21 | """ |
36 | | - Build derivative matrices and attach them to `par`. |
37 | | -
|
38 | | - Parameters |
39 | | - ---------- |
40 | | - par : dict or object with attributes |
41 | | - Required fields: |
42 | | - - N : int number of axial cells |
43 | | - - dz : float cell length (m) |
44 | | -
|
45 | | - Side effects |
46 | | - ------------ |
47 | | - Sets: |
48 | | - par.D1 : (N x N) scipy.sparse.csr_matrix |
49 | | - par.D2 : (N x N) scipy.sparse.csr_matrix |
50 | | -
|
51 | | - Returns |
52 | | - ------- |
53 | | - par : same object/dict, for chaining. |
| 22 | + Assemble finite-volume derivative matrices in z and attach to `par`. |
| 23 | + Requires: par.N (int), par.dz (float). |
| 24 | + Adds: |
| 25 | + par.D1 -- first derivative (backward/upwind) [N x N] sparse |
| 26 | + par.D2 -- second derivative (central) [N x N] sparse |
54 | 27 | """ |
55 | 28 | N = int(_get(par, "N")) |
56 | 29 | dz = float(_get(par, "dz")) |
57 | 30 |
|
58 | 31 | e = np.ones(N) |
59 | 32 |
|
60 | | - # 1) First derivative: backward/upwind |
| 33 | + # 1) Backward/upwind first derivative |
61 | 34 | D1 = diags([-e, e], offsets=[-1, 0], shape=(N, N), format="lil") / dz |
62 | | - D1[0, :] = 0.0 # inlet row is handled via the boundary condition |
63 | | - D1 = D1.tocsr() |
| 35 | + D1[0, :] = 0 # inlet row handled via boundary condition |
| 36 | + D1 = D1.tocsc() |
64 | 37 |
|
65 | | - # 2) Second derivative: central |
66 | | - D2 = diags([e, -2*e, e], offsets=[-1, 0, 1], shape=(N, N), format="lil") / dz**2 |
67 | | - D2[0, 0] = -2.0 / dz**2 # inlet ghost cell (Dirichlet) |
68 | | - D2[-1, -1] = 1.0 / dz**2 # Danckwerts-type outlet |
69 | | - D2 = D2.tocsr() |
| 38 | + # 2) Central second derivative |
| 39 | + D2 = diags([e, -2.0 * e, e], offsets=[-1, 0, 1], shape=(N, N), format="lil") / (dz**2) |
| 40 | + D2[0, 0] = -2.0 / (dz**2) # ghost for inlet Dirichlet |
| 41 | + D2[-1, -1] = 1.0 / (dz**2) # Danckwerts outlet |
| 42 | + D2 = D2.tocsc() |
70 | 43 |
|
71 | 44 | _set(par, "D1", D1) |
72 | 45 | _set(par, "D2", D2) |
73 | 46 | return par |
74 | 47 |
|
75 | | -if __name__ == "__main__": |
76 | | - # Minimal smoke test |
77 | | - from types import SimpleNamespace |
78 | | - par = SimpleNamespace(N=5, dz=0.01) |
79 | | - par = build_matrices(par) |
80 | | - print("D1:\n", par.D1.toarray()) |
81 | | - print("D2:\n", par.D2.toarray()) |
| 48 | +def attach_jacobian_sparsity(par): |
| 49 | + """ |
| 50 | + Attach Jacobian sparsity pattern for multi-species model. |
| 51 | + Requires: par.N, par.Nsp. |
| 52 | + Adds: par.Jpattern |
| 53 | + """ |
| 54 | + N = int(_get(par, "N")) |
| 55 | + Nsp = int(_get(par, "Nsp")) |
| 56 | + Jpat = sparsity_pattern_multi(N, Nsp) |
| 57 | + _set(par, "Jpattern", Jpat) |
| 58 | + return par |
0 commit comments