Skip to content

Commit bdf1097

Browse files
mrp089claude
andcommitted
Split ChamberElastanceInductor into linear and exponential variants
Extract the exponential passive P-V relation (for atrial chambers) into a separate ChamberElastanceInductorExponential class that inherits from ChamberElastanceInductor. This removes if/else branching on Kxp==0 and makes the two physical models (linear vs exponential passive P-V) explicit through the type system. - ChamberElastanceInductor: linear E(t)*(V-Vrest) model - ChamberElastanceInductorExponential: adds Kxp, Kxv, Vaso parameters for exponential passive pressure (Sankaran 2012, Menon 2023) Base class provides a protected constructor for derived classes and virtual get_elastance_values() for the elastance computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b034874 commit bdf1097

11 files changed

Lines changed: 152 additions & 24193 deletions

src/model/BlockType.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ enum class BlockType {
3535
open_loop_coronary_var_res_bc = 19,
3636
open_loop_coronary_detailed_bc = 20,
3737
blood_vessel_rc = 21,
38-
closed_loop_heart_pulmonary_smooth = 22
38+
closed_loop_heart_pulmonary_smooth = 22,
39+
chamber_elastance_inductor_exponential = 23
3940
};
4041

4142
/**

src/model/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ set(CXXSRCS
1414
BloodVesselJunction.cpp
1515
ChamberSphere.cpp
1616
ChamberElastanceInductor.cpp
17+
ChamberElastanceInductorExponential.cpp
1718
ClosedLoopCoronaryBC.cpp
1819
ClosedLoopCoronaryLeftBC.cpp
1920
ClosedLoopCoronaryRightBC.cpp
@@ -46,6 +47,7 @@ set(HDRS
4647
BloodVesselJunction.h
4748
ChamberSphere.h
4849
ChamberElastanceInductor.h
50+
ChamberElastanceInductorExponential.h
4951
ClosedLoopCoronaryBC.h
5052
ClosedLoopCoronaryLeftBC.h
5153
ClosedLoopCoronaryRightBC.h

src/model/ChamberElastanceInductor.cpp

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
// University of California, and others. SPDX-License-Identifier: BSD-3-Clause
33
#include "ChamberElastanceInductor.h"
44

5+
#include "Model.h"
6+
57
void ChamberElastanceInductor::setup_dofs(DOFHandler& dofhandler) {
6-
// Internal variable is chamber volume
78
Block::setup_dofs_(dofhandler, 3, {"Vc"});
89
}
910

@@ -29,37 +30,9 @@ void ChamberElastanceInductor::update_time(SparseSystem& system,
2930
std::vector<double>& parameters) {
3031
get_elastance_values(parameters);
3132

32-
// Eq 0: P_in - E(t)(Vc - Vrest) = P_in - E(t)*Vc + E(t)*Vrest = 0
33-
system.F.coeffRef(global_eqn_ids[0], global_var_ids[4]) = -1 * Elas;
34-
35-
// In exponential passive mode (Kxp > 0), C[0] is set in update_solution.
36-
// In standard linear mode, set C[0] here.
37-
double Kxp = parameters[global_param_ids[ParamId::KXP]];
38-
if (Kxp <= 0.0) {
39-
system.C.coeffRef(global_eqn_ids[0]) = Elas * Vrest;
40-
}
41-
}
42-
43-
void ChamberElastanceInductor::update_solution(
44-
SparseSystem& system, std::vector<double>& parameters,
45-
const Eigen::Matrix<double, Eigen::Dynamic, 1>& y,
46-
const Eigen::Matrix<double, Eigen::Dynamic, 1>& dy) {
47-
// --- Exponential passive P-V (atrial mode, Kxp > 0) ---
48-
double Kxp = parameters[global_param_ids[ParamId::KXP]];
49-
if (Kxp <= 0.0) return;
50-
51-
double Kxv = parameters[global_param_ids[ParamId::KXV]];
52-
double Vaso = parameters[global_param_ids[ParamId::VASO]];
53-
double Emax = parameters[global_param_ids[ParamId::EMAX]];
54-
double Vc = y[global_var_ids[4]];
55-
56-
double exp_term = std::exp(Kxv * (Vc - Vaso));
57-
double psi = Kxp * (exp_term - 1.0);
58-
double psi_d = Kxp * Kxv * exp_term;
59-
60-
system.C(global_eqn_ids[0]) = act_ * Emax * Vaso + psi * (act_ - 1.0);
61-
system.dC_dy.coeffRef(global_eqn_ids[0], global_var_ids[4]) =
62-
psi_d * (act_ - 1.0);
33+
// Eq 0: F[0][4] = -E(t), C[0] = E(t)*Vrest
34+
system.F.coeffRef(global_eqn_ids[0], global_var_ids[4]) = -Elas;
35+
system.C.coeffRef(global_eqn_ids[0]) = Elas * Vrest;
6336
}
6437

6538
void ChamberElastanceInductor::get_elastance_values(
@@ -70,15 +43,8 @@ void ChamberElastanceInductor::get_elastance_values(
7043
double Vrs = parameters[global_param_ids[ParamId::VRS]];
7144

7245
act_ = activation_func_->compute(model->time);
73-
74-
double Kxp = parameters[global_param_ids[ParamId::KXP]];
75-
if (Kxp > 0.0) {
76-
Elas = act_ * Emax;
77-
Vrest = parameters[global_param_ids[ParamId::VASO]];
78-
} else {
79-
Vrest = (1.0 - act_) * (Vrd - Vrs) + Vrs;
80-
Elas = (Emax - Emin) * act_ + Emin;
81-
}
46+
Vrest = (1.0 - act_) * (Vrd - Vrs) + Vrs;
47+
Elas = (Emax - Emin) * act_ + Emin;
8248
}
8349

8450
void ChamberElastanceInductor::set_activation_function(

src/model/ChamberElastanceInductor.h

Lines changed: 20 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313

1414
#include "ActivationFunction.h"
1515
#include "Block.h"
16-
#include "Model.h"
1716
#include "SparseSystem.h"
1817
#include "debug.h"
1918

2019
/**
21-
* @brief Cardiac chamber with elastance and inductor.
20+
* @brief Cardiac chamber with linear elastance and inductor.
2221
*
2322
* Models a cardiac chamber as a time-varying capacitor (elastance with
2423
* specified resting volumes) and an inductor. See \cite kerckhoffs2007coupling
@@ -55,91 +54,26 @@
5554
* Q_{in}-Q_{out}-\dot{V}_c=0
5655
* \f]
5756
*
58-
* ### Local contributions
59-
*
60-
* \f[
61-
* \mathbf{y}^{e}=\left[\begin{array}{lllll}P_{in} & Q_{in} &
62-
* P_{out} & Q_{out} & V_c\end{array}\right]^{T} \f]
63-
*
64-
* \f[
65-
* \mathbf{E}^{e}=\left[\begin{array}{ccccc}
66-
* 0 & 0 & 0 & 0 & 0\\
67-
* 0 & 0 & 0 & -L & 0\\
68-
* 0 & 0 & 0 & 0 & -1
69-
* \end{array}\right]
70-
* \f]
71-
*
72-
* \f[
73-
* \mathbf{F}^{e}=\left[\begin{array}{ccccc}
74-
* 1 & 0 & 0 & 0 & E(t) \\
75-
* 1 & 0 & -1 & 0 & 0 \\
76-
* 0 & 1 & 0 & -1 & 0
77-
* \end{array}\right]
78-
* \f]
79-
*
80-
* \f[
81-
* \mathbf{c}^{e}=\left[\begin{array}{c}
82-
* E(t)V_{rest} \\
83-
* 0 \\
84-
* 0
85-
* \end{array}\right]
86-
* \f]
87-
*
88-
* In the above equations,
57+
* where
8958
*
9059
* \f[
9160
* V_{rest}(t)= \{1-A(t)\}(V_{rd}-V_{rs})+V_{rs}
9261
* \f]
9362
*
9463
* \f[
95-
* A(t)=-\frac{1}{2}cos(2 \pi T_{contract}/T_{twitch})
96-
* \f]
97-
*
98-
* \f[
9964
* E(t)=(E_{max}-E_{min})A(t) + E_{min}
10065
* \f]
10166
*
102-
*
10367
* ### Parameters
10468
*
105-
* Parameter sequence for constructing this block
106-
*
10769
* * `0` Emax: Maximum elastance
10870
* * `1` Emin: Minimum elastance
10971
* * `2` Vrd: Rest diastolic volume
11072
* * `3` Vrs: Rest systolic volume
111-
* * `4` t_active: Activation time
112-
* * `5` t_twitch: Twitch time
113-
* * `6` Impedance: Impedance of the outflow
114-
*
115-
* ### Usage in json configuration file
116-
*
117-
* "chambers": [
118-
* {
119-
* "type": "ChamberElastanceInductor",
120-
* "name": "ventricle",
121-
* "values": {
122-
* "Emax": 1.057,
123-
* "Emin": 0.091,
124-
* "Vrd": 26.1,
125-
* "Vrs": 18.0,
126-
* "Impedance": 0.000351787
127-
* },
128-
* "activation_function": {
129-
* "type": "half_cosine",
130-
* "t_active": 0.2,
131-
* "t_twitch": 0.3
132-
* }
133-
* }
134-
* ],
135-
* "initial_condition": {
136-
* "Vc:ventricle": 96.07
137-
* }
73+
* * `4` Impedance: Impedance of the outflow
13874
*
13975
* ### Internal variables
14076
*
141-
* Names of internal variables in this block's output:
142-
*
14377
* * `Vc`: Chamber volume
14478
*
14579
*/
@@ -158,93 +92,46 @@ class ChamberElastanceInductor : public Block {
15892
{"Emin", InputParameter()},
15993
{"Vrd", InputParameter()},
16094
{"Vrs", InputParameter()},
161-
{"Impedance", InputParameter()},
162-
{"Kxp", InputParameter(true)},
163-
{"Kxv", InputParameter(true)},
164-
{"Vaso", InputParameter(true)}}) {}
95+
{"Impedance", InputParameter()}}) {}
16596

16697
/**
16798
* @brief Local IDs of the parameters
168-
*
16999
*/
170100
enum ParamId {
171101
EMAX = 0,
172102
EMIN = 1,
173103
VRD = 2,
174104
VRS = 3,
175105
IMPEDANCE = 4,
176-
KXP = 5, ///< Passive pressure scaling (optional, 0 = linear mode)
177-
KXV = 6, ///< Passive volume scaling (optional)
178-
VASO = 7 ///< Passive resting volume (optional)
179106
};
180107

181-
/**
182-
* @brief Set up the degrees of freedom (DOF) of the block
183-
*
184-
* Set global_var_ids and global_eqn_ids of the element based on the
185-
* number of equations and the number of internal variables of the
186-
* element.
187-
*
188-
* @param dofhandler Degree-of-freedom handler to register variables and
189-
* equations at
190-
*/
191108
void setup_dofs(DOFHandler& dofhandler);
192-
193-
/**
194-
* @brief Update the constant contributions of the element in a sparse
195-
system
196-
*
197-
* @param system System to update contributions at
198-
* @param parameters Parameters of the model
199-
*/
200109
void update_constant(SparseSystem& system, std::vector<double>& parameters);
201-
202-
/**
203-
* @brief Update the time-dependent contributions of the element in a sparse
204-
* system
205-
*
206-
* @param system System to update contributions at
207-
* @param parameters Parameters of the model
208-
*/
209110
void update_time(SparseSystem& system, std::vector<double>& parameters);
210111

211-
/**
212-
* @brief Update the solution-dependent contributions of the element in a
213-
* sparse system. Only active when Kxp > 0 (exponential passive atrial mode).
214-
*/
215-
void update_solution(SparseSystem& system, std::vector<double>& parameters,
216-
const Eigen::Matrix<double, Eigen::Dynamic, 1>& y,
217-
const Eigen::Matrix<double, Eigen::Dynamic, 1>& dy);
218-
219-
/**
220-
* @brief Number of triplets of element
221-
*
222-
* Number of triplets that the element contributes to the global system
223-
* (relevant for sparse memory reservation)
224-
*/
225-
TripletsContributions num_triplets{6, 2, 1};
112+
TripletsContributions num_triplets{6, 2, 0};
226113

227-
private:
228-
double Elas; // Chamber Elastance
229-
double Vrest; // Rest Volume
230-
double act_ = 0.0; // Last computed activation
231-
std::unique_ptr<ActivationFunction> activation_func_; // Activation function
114+
void set_activation_function(std::unique_ptr<ActivationFunction> af) override;
232115

233-
public:
116+
protected:
234117
/**
235-
* @brief Set the activation function (takes ownership)
236-
*
237-
* @param af Unique pointer to the activation function
118+
* @brief Construct a ChamberElastanceInductor with custom block type and
119+
* parameters. Used by derived classes.
238120
*/
239-
void set_activation_function(std::unique_ptr<ActivationFunction> af) override;
121+
ChamberElastanceInductor(
122+
int id, Model* model, BlockType block_type,
123+
std::vector<std::pair<std::string, InputParameter>> params)
124+
: Block(id, model, block_type, BlockClass::chamber, params) {}
125+
126+
double Elas = 0.0; ///< Current chamber elastance
127+
double Vrest = 0.0; ///< Current rest volume
128+
double act_ = 0.0; ///< Last computed activation
129+
std::unique_ptr<ActivationFunction> activation_func_;
240130

241-
private:
242131
/**
243-
* @brief Update the elastance functions which depend on time
244-
*
245-
* @param parameters Parameters of the model
132+
* @brief Compute elastance and rest volume from activation and parameters.
246133
*/
247-
void get_elastance_values(std::vector<double>& parameters);
134+
virtual void get_elastance_values(std::vector<double>& parameters);
248135
};
249136

250137
#endif // SVZERODSOLVER_MODEL_CHAMBERELASTANCEINDUCTOR_HPP_
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the
2+
// University of California, and others. SPDX-License-Identifier: BSD-3-Clause
3+
#include "ChamberElastanceInductorExponential.h"
4+
5+
#include "Model.h"
6+
7+
void ChamberElastanceInductorExponential::get_elastance_values(
8+
std::vector<double>& parameters) {
9+
double Emax = parameters[global_param_ids[ParamId::EMAX]];
10+
11+
act_ = activation_func_->compute(model->time);
12+
Elas = act_ * Emax;
13+
Vrest = parameters[global_param_ids[ExponentialParamId::VASO]];
14+
}
15+
16+
void ChamberElastanceInductorExponential::update_time(
17+
SparseSystem& system, std::vector<double>& parameters) {
18+
get_elastance_values(parameters);
19+
20+
// Eq 0: F[0][4] = -E(t). C[0] is set in update_solution (nonlinear).
21+
system.F.coeffRef(global_eqn_ids[0], global_var_ids[4]) = -Elas;
22+
}
23+
24+
void ChamberElastanceInductorExponential::update_solution(
25+
SparseSystem& system, std::vector<double>& parameters,
26+
const Eigen::Matrix<double, Eigen::Dynamic, 1>& y,
27+
const Eigen::Matrix<double, Eigen::Dynamic, 1>& dy) {
28+
double Kxp = parameters[global_param_ids[ExponentialParamId::KXP]];
29+
double Kxv = parameters[global_param_ids[ExponentialParamId::KXV]];
30+
double Vaso = parameters[global_param_ids[ExponentialParamId::VASO]];
31+
double Emax = parameters[global_param_ids[ParamId::EMAX]];
32+
double Vc = y[global_var_ids[4]];
33+
34+
double exp_term = std::exp(Kxv * (Vc - Vaso));
35+
double psi = Kxp * (exp_term - 1.0);
36+
double psi_d = Kxp * Kxv * exp_term;
37+
38+
system.C(global_eqn_ids[0]) = act_ * Emax * Vaso + psi * (act_ - 1.0);
39+
system.dC_dy.coeffRef(global_eqn_ids[0], global_var_ids[4]) =
40+
psi_d * (act_ - 1.0);
41+
}

0 commit comments

Comments
 (0)