From 88bfb792faef33545dedfa15dd2386e932578353 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 24 Jun 2024 14:21:10 -0600 Subject: [PATCH 01/41] added cycle to cycle error and exprtk functionailty to main.md. initial version of docs to add new blocks --- docs/pages/developer_guide.md | 36 ++++++++++++++++++++++++++++----- docs/pages/main.md | 38 +++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index 749367290..58b8c40eb 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -14,10 +14,6 @@ of svZeroDSolver, namely: * svZerodSolver in `svzerodsolver.cpp` * Python API in `pysvzerod.cpp` -The header-based library in the `src` folder contains classes and functions that are collectively used by -all applications. A good overview over the general architecture can be found in the -list of namespaces. - ## Build in debug mode @@ -38,6 +34,36 @@ pip install -e ".[dev]" ``` This is useful when continuously running the integration tests during development. +## Adding new blocks + +**1. Add the new block to the following lists/dictionaries:** +- `BlockType` in src/model/BlockType.h +- `block_factory_map` in src/model/Model.cpp. Here the dictionary key should match the string that specifies the type of block read from the JSON config file. +- If required, `BlockClass` if it's a new kind of block that might require special handling (most blocks don't). + +**2. Create a new class inherited from `Block` for the new block. Specific things to pay attention to:** +- Define a constructor of the form: +`GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, {{"Param1", InputParameter()}, {"Param2", InputParameter()}, ..., {"ParamN", InputParameter()}}) {}` +- In the constructor, `GenericBlock` is the name of the new class +- `block_type` and `block_class` are the same as what was added in Step 1 above. +- The input parameters of the block are `Param1`, ... , `ParamN`. The properties of each parameter is defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, and its default value. See `InputParameter` in `Parameter.h` for details. +- The names `Param1`, ... , `ParamN` should be the same as the strings read from the JSON config file. +- The class can have an `enum ParamId` object that relates the parameter indices to their names. This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). The order of parameters in the `ParamId` object should match the order in the constructor. +- The class should have `setup_dofs` and `update_constant` functions. It should also have a `TripletsContributions num_triplets{?, ?, ?}` object that specifies how many elements the governing equations of the block contribute to the `F`, `E` and `dCdy` matrices respectively. The class may also contain `update_time` and `update_solution` functions. Details are below. + +**3. Now implement the governing equations for the block.** +- The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable1, ..., InternalVariableN]`. Here, `InternalVariable*` refers to any variable in the governing equations that are not in the inlet and outlet flow and pressure. +- The equations should be written in the form `E(t)*ydot + F(t)*y + c(y,t) = 0`. Here, `y` is the local state vector mentioned above, `ydot` is the time-derivative of the state vector, `E` and `F` are matrices and `c` is a vector containing all non-linear and constant terms in the equation. `E` and `F` are size (number_of_equations*size_of_state_vector), while `c` is length number_of_equations. +- All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. +- The elements of the `E` and `F` matrix are populated using the syntax `system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = A`. Here, `current_block_equation_id` goes from 0 to number_of_equations (for the current block) and `current_block_variable_ids` goes from 0 to size_of_state_vector for the current block. The indices correspond to the block's local state vector mentioned above. +- If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. +- For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax `system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = A`, where `A` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id'. For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. + +**4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** + +This looks quite daunting when listed out like this. We need a way to explain this better before putting it into the documentation. + + ## Code Style We follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). @@ -105,7 +131,7 @@ a+b=c ### Citations If you want to cite a piece literature in your documentation, add -a respective BibTeX citation to `docs/cpp/references.bib` and use `\cite name_of_citation` to +a respective BibTeX citation to `docs/references.bib` and use `\cite name_of_citation` to cite the document. ### Drawing circuits diff --git a/docs/pages/main.md b/docs/pages/main.md index 1268661f5..030613de4 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -26,7 +26,7 @@ time-dependent; they are unable to simulate spatial patterns in the hemodynamics. 0D models are analogous to electrical circuits. The flow rate simulated by 0D models represents electrical current, while the pressure represents voltage. Three primary building blocks of 0D models are resistors, -capacitors, and inductors Resistance captures the viscous effects of blood +capacitors, and inductors. Resistance captures the viscous effects of blood flow, capacitance represents the compliance and distensibility of the vessel wall, and inductance represents the inertia of the blood flow. Different combinations of these building blocks, as well as others, can be formed to @@ -34,19 +34,26 @@ reflect the hemodynamics and physiology of different cardiovascular anatomies. These 0D models are governed by differential algebraic equations (DAEs). -For more background information on 0D models, have a look at the detailed documentation: -- System of equations: SparseSystem -- Time integration: Integrator -- Overview of available 0D elements (blocks): Block +The main categories of blocks implemented in svZeroDSolver are: +- Blood vessels +- Junctions +- Boundary conditions +- Heart chambers +- Heart valves -You can find more details about governing equations in individual blocks, for example: +For an overview of available 0D elements (blocks) see: Block + +You can find more details about governing equations in individual blocks in their respective documentation pages. For example: - BloodVessel - BloodVesselJunction - WindkesselBC For implementation details, have a look at the [source code](https://github.com/simvascular/svZeroDSolver). +Mathematics details can be found in the following classes: +- System of equations: SparseSystem +- Time integration: Integrator -[About SimVascular](https://simvascular.github.io) +[More information about SimVascular](https://simvascular.github.io) # Installation @@ -258,11 +265,14 @@ output_interval | The frequency of writing timesteps to output_mean_only | Write only the mean values over every timestep to output file | false output_derivative | Write time derivatives to output file | false output_all_cycles | Write all cardiac cycles to output file | false +use_cycle_to_cycle_error | Use cycle-to-cycle error to determine number of cycles for convergence | false +sim_cycle_to_cycle_percent_error | Percentage error threshold for cycle-to-cycle pressure and flow difference | 1.0 +The option `use_cycle_to_cycle_error` allows the solver to change the number of cardiac cycles it runs depending on the cycle-to-cycle convergence of the simulation. For simulations with no RCR boundary conditions, the simulation will add extra cardiac cycles until the difference between the mean pressure and flow in consecutive cycles is below the threshold set by `sim_cycle_to_cycle_percent_error` at all inlets and outlets of the model. If there is at least one RCR boundary condition, the number of cycles is determined based on equation 21 of \cite pfaller21, using the RCR boundary condition with the largest time constant. ### Vessels -More information about the vessels can be found in their respective class references. +More information about the vessels can be found in their respective class references. Below is a template vessel block with boundary conditions, `INFLOW` and `OUT`, at its inlet and outlet respectively. ```python { @@ -284,7 +294,7 @@ Blood vessel with \n optional stenosis | MODEL::BloodVessel | `BloodVess ### Junctions -More information about the junctions can be found in their respective class references. +More information about the junctions can be found in their respective class references. Below is a template junction block that connects vessel ID 0 with vessel IDs 1 and 2. ```python { @@ -304,6 +314,8 @@ Blood vessel \n junction | MODEL::BloodVesselJunction | `BloodVess ### Boundary conditions +More information about the boundary conditions can be found in their respective class references. Below is a template `FLOW` boundary condition. + ```python { "bc_name": "INFLOW", # Name of the boundary condition @@ -314,11 +326,15 @@ Blood vessel \n junction | MODEL::BloodVesselJunction | `BloodVess Description | Class | `bc_type` | `bc_values` ------------------------------------- | --------------------------- | --------------------- | ----------- -Prescribed (transient) flow | MODEL::FlowReferenceBC | `FLOW` | `Q`: Time-dependent flow values \n `t`: Time stamps -Prescribed (transient) pressure | MODEL::PressureReferenceBC | `PRESSURE` | `P`: Time-dependent pressure values \n `t`: Time stamps +Prescribed (transient) flow | MODEL::FlowReferenceBC | `FLOW` | `Q`: Time-dependent flow values \n `t`: Time steps \n `fn`: Mathematical expression +Prescribed (transient) pressure | MODEL::PressureReferenceBC | `PRESSURE` | `P`: Time-dependent pressure values \n `t`: Time steps \n `fn`: Mathematical expression Resistance | MODEL::ResistanceBC | `RESISTANCE` | `R`: Resistance \n `Pd`: Time-dependent distal pressure \n `t`: Time stamps Windkessel | MODEL::WindkesselBC | `RCR` | `Rp`: Proximal resistance \n `C`: Capacitance \n `Rd`: Distal resistance \n `Pd`: Distal pressure +Coronary outlet | MODEL::OpenLoopCoronaryBC | `CORONARY` | `Ra`: Proximal resistance \n `Ram`: Microvascular resistance \n `Rv`: Venous resistance \n `Ca`: Small artery capacitance \n `Cim`: Intramyocardial capacitance \n `Pim`: Intramyocardial pressure \n `Pv`: Venous pressure + +The above table describes the most commonly used boundary conditions. In addition, svZeroDSolver includes various closed-loop boundary conditions. Examples can be found in `svZeroDSolver/tests/cases`. +Note that the `FLOW` and `PRESSURE` boundary conditions accept mathematical expressions in `bc_values`. For an example where values of the boundary condition are specified as a function of time, see `svZeroDSolver/tests/cases/pulsatileFlow_R_RCR.json`. For an example with a mathematical expression for the boundary condition, see `svZeroDSolver/tests/cases/timeDep_Flow.json`. # svZeroDCalibrator From 4faa7201d91b0a29a7f95748ac7fb16fd416a656 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 24 Jun 2024 14:21:50 -0600 Subject: [PATCH 02/41] typo in microvascular --- src/model/OpenLoopCoronaryBC.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/OpenLoopCoronaryBC.h b/src/model/OpenLoopCoronaryBC.h index f15c0d882..831f9ca39 100644 --- a/src/model/OpenLoopCoronaryBC.h +++ b/src/model/OpenLoopCoronaryBC.h @@ -96,7 +96,7 @@ * Parameter sequence for constructing this block * * * `0` Ra: Small artery resistance - * * `1` Ram: Microvascualr resistance + * * `1` Ram: Microvascualar resistance * * `2` Rv: Venous resistance * * `3` Ca: Small artery capacitance * * `4` Cim: Intramyocardial capacitance From c9982c19ca9d9d087eff709d82c1d5c5f1066bcb Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 11:51:19 -0600 Subject: [PATCH 03/41] cleaning up new block info --- docs/pages/developer_guide.md | 67 +++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index 58b8c40eb..3afa1bbda 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -36,34 +36,57 @@ This is useful when continuously running the integration tests during developmen ## Adding new blocks +The modular architecture of svZeroDSolver relies on "blocks", such as blood vessels, junctions, valves, boundary conditions, etc. These blocks are assembled in a manner specified by the `.json` configuration file, which dictates the assembled governing equations for the model. We are always interested in adding new blocks to expand the funcitonality of svZeroDSolver. + +To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. + +Below are details on the steps required to implement a new block in svZeroDSolver. + **1. Add the new block to the following lists/dictionaries:** -- `BlockType` in src/model/BlockType.h -- `block_factory_map` in src/model/Model.cpp. Here the dictionary key should match the string that specifies the type of block read from the JSON config file. -- If required, `BlockClass` if it's a new kind of block that might require special handling (most blocks don't). - -**2. Create a new class inherited from `Block` for the new block. Specific things to pay attention to:** -- Define a constructor of the form: -`GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, {{"Param1", InputParameter()}, {"Param2", InputParameter()}, ..., {"ParamN", InputParameter()}}) {}` -- In the constructor, `GenericBlock` is the name of the new class -- `block_type` and `block_class` are the same as what was added in Step 1 above. -- The input parameters of the block are `Param1`, ... , `ParamN`. The properties of each parameter is defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, and its default value. See `InputParameter` in `Parameter.h` for details. -- The names `Param1`, ... , `ParamN` should be the same as the strings read from the JSON config file. -- The class can have an `enum ParamId` object that relates the parameter indices to their names. This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). The order of parameters in the `ParamId` object should match the order in the constructor. -- The class should have `setup_dofs` and `update_constant` functions. It should also have a `TripletsContributions num_triplets{?, ?, ?}` object that specifies how many elements the governing equations of the block contribute to the `F`, `E` and `dCdy` matrices respectively. The class may also contain `update_time` and `update_solution` functions. Details are below. +* `BlockType` in src/model/BlockType.h +* `block_factory_map` in src/model/Model.cpp + * *Note:* In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block. +* If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h + +**2. Create a new class inherited from `Block` for the new block.** +* Define a constructor of the form: +```GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, {{"Param_1", InputParameter()}, {"Param_2", InputParameter()}, ..., {"Param_N", InputParameter()}}) {}``` + * In the constructor, `GenericBlock` is the name of the new class + * `block_type` and `block_class` are the same as what was added in Step 1 above. + * The input parameters of the block are `Param_1`, ... , `Param_N`. + * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. See `InputParameter` for details. + * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. +* The class should have a `setup_dofs(DOFHandler &dofhandler)` function. + * This function typically only includes a call to the following function: + ```Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names)``` + * In the above function, `num_equations` is the number of governing equations for the new block. `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. +* The class should have a `TripletsContributions num_triplets{?, ?, ?}` object. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dCdy` matrices respectively. Details are below. +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. +* (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. + * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). + * The order of parameters in the `ParamId` object should match the order in the constructor. +* The best way to implement a new block class is to look at examples of existing block classes. + * See the `ValveTanh` class for an example. **3. Now implement the governing equations for the block.** -- The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable1, ..., InternalVariableN]`. Here, `InternalVariable*` refers to any variable in the governing equations that are not in the inlet and outlet flow and pressure. -- The equations should be written in the form `E(t)*ydot + F(t)*y + c(y,t) = 0`. Here, `y` is the local state vector mentioned above, `ydot` is the time-derivative of the state vector, `E` and `F` are matrices and `c` is a vector containing all non-linear and constant terms in the equation. `E` and `F` are size (number_of_equations*size_of_state_vector), while `c` is length number_of_equations. -- All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. -- The elements of the `E` and `F` matrix are populated using the syntax `system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = A`. Here, `current_block_equation_id` goes from 0 to number_of_equations (for the current block) and `current_block_variable_ids` goes from 0 to size_of_state_vector for the current block. The indices correspond to the block's local state vector mentioned above. -- If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. -- For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax `system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = A`, where `A` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id'. For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. +* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. + * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs(DOFHandler &dofhandler)`. + * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. +* The equations should be written in the form `E(t)*ydot + F(t)*y + c(y,t) = 0`. +* The equations should be written in the form $$E(t)*ydot + F(t)*y + c(y,t) = 0$$. + * `y` is the local state vector mentioned above + * `ydot` is the time-derivative of the local state vector + * `E` and `F` are matrices of size + * `c` is a vector containing all non-linear and constant terms in the equation. + * `E` and `F` are size (number_of_equations*size_of_state_vector), while `c` is length number_of_equations. +* All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. +* The elements of the `E` and `F` matrix are populated using the syntax `system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = A`. Here, `current_block_equation_id` goes from 0 to number_of_equations (for the current block) and `current_block_variable_ids` goes from 0 to size_of_state_vector for the current block. The indices correspond to the block's local state vector mentioned above. +* If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. +* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax `system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = A`, where `A` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id'. For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. **4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** -This looks quite daunting when listed out like this. We need a way to explain this better before putting it into the documentation. - - ## Code Style We follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). From c4f6114e79144ceeed446ca5e4ee5d27abcf38ea Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 13:25:12 -0600 Subject: [PATCH 04/41] test formatting for new blocks --- docs/pages/developer_guide.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index 3afa1bbda..0e5c5819f 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -49,35 +49,43 @@ Below are details on the steps required to implement a new block in svZeroDSolve * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h **2. Create a new class inherited from `Block` for the new block.** +* *Note:* The best way to implement a new block is to look at examples of existing block classes. + * See the `ValveTanh` class for an example. * Define a constructor of the form: -```GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, {{"Param_1", InputParameter()}, {"Param_2", InputParameter()}, ..., {"Param_N", InputParameter()}}) {}``` - * In the constructor, `GenericBlock` is the name of the new class +``` + GenericBlock(int id, Model *model) + : Block(id, model, BlockType::block_type, BlockClass::block_class, {{Param_1, InputParameter()}, {Param_2, InputParameter()}, ..., {Param_N, InputParameter()}}) {} +``` + * `GenericBlock` is the name of the new class * `block_type` and `block_class` are the same as what was added in Step 1 above. - * The input parameters of the block are `Param_1`, ... , `Param_N`. - * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. See `InputParameter` for details. + * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. + * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. + * The class should have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: - ```Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names)``` + ``` + Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) + ``` * In the above function, `num_equations` is the number of governing equations for the new block. `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. * The class should have a `TripletsContributions num_triplets{?, ?, ?}` object. - * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dCdy` matrices respectively. Details are below. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dCdy` matrices respectively. Details are in Step 3 below. * The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. * (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. -* The best way to implement a new block class is to look at examples of existing block classes. - * See the `ValveTanh` class for an example. **3. Now implement the governing equations for the block.** * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. - * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs(DOFHandler &dofhandler)`. + * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. * The equations should be written in the form `E(t)*ydot + F(t)*y + c(y,t) = 0`. -* The equations should be written in the form $$E(t)*ydot + F(t)*y + c(y,t) = 0$$. +* The equations should be written in the form $E(t)*ydot + F(t)*y + c(y,t) = 0$. +* The equations should be written in the form $\`E(t)*ydot + F(t)*y + c(y,t) = 0\`$. * `y` is the local state vector mentioned above * `ydot` is the time-derivative of the local state vector - * `E` and `F` are matrices of size + * `E` and `F` are matrices of size (number_of_equations*size_of_state_vector). + * `c` is a vector of length number_of_equations. * `c` is a vector containing all non-linear and constant terms in the equation. * `E` and `F` are size (number_of_equations*size_of_state_vector), while `c` is length number_of_equations. * All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. From f0786bf4e7661be10839c143e5e64a9d4da2975a Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 14:13:51 -0600 Subject: [PATCH 05/41] added equation example --- docs/pages/developer_guide.md | 63 +++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index 0e5c5819f..d21793e1a 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -49,12 +49,14 @@ Below are details on the steps required to implement a new block in svZeroDSolve * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h **2. Create a new class inherited from `Block` for the new block.** -* *Note:* The best way to implement a new block is to look at examples of existing block classes. - * See the `ValveTanh` class for an example. +* *Note:* The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example. * Define a constructor of the form: ``` GenericBlock(int id, Model *model) - : Block(id, model, BlockType::block_type, BlockClass::block_class, {{Param_1, InputParameter()}, {Param_2, InputParameter()}, ..., {Param_N, InputParameter()}}) {} + : Block(id, model, BlockType::block_type, BlockClass::block_class, i + {{Param_1, InputParameter()}, + {Param_2, InputParameter()}, ..., + {Param_N, InputParameter()}}) {} ``` * `GenericBlock` is the name of the new class * `block_type` and `block_class` are the same as what was added in Step 1 above. @@ -67,10 +69,14 @@ Below are details on the steps required to implement a new block in svZeroDSolve ``` Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) ``` - * In the above function, `num_equations` is the number of governing equations for the new block. `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. -* The class should have a `TripletsContributions num_triplets{?, ?, ?}` object. + * In the above function, `num_equations` is the number of governing equations for the new block. + * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. + +* The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dCdy` matrices respectively. Details are in Step 3 below. + * The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. + * (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. @@ -79,19 +85,48 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. -* The equations should be written in the form `E(t)*ydot + F(t)*y + c(y,t) = 0`. -* The equations should be written in the form $E(t)*ydot + F(t)*y + c(y,t) = 0$. -* The equations should be written in the form $\`E(t)*ydot + F(t)*y + c(y,t) = 0\`$. + +* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. * `y` is the local state vector mentioned above * `ydot` is the time-derivative of the local state vector - * `E` and `F` are matrices of size (number_of_equations*size_of_state_vector). - * `c` is a vector of length number_of_equations. - * `c` is a vector containing all non-linear and constant terms in the equation. - * `E` and `F` are size (number_of_equations*size_of_state_vector), while `c` is length number_of_equations. + * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. + * `c` is a vector of length `number_of_equations`. + * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. + * `C` is a vector containing all non-linear and constant terms in the equation. + * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. + +* For example, let's assume a block has the following non-linear governing equations: +``` +a*dQ_in/dt + b*P_in + c*(dP_in/dt)*P_in + d = 0 +``` +``` +e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 +``` + * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. + * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. + * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. + * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*P_in + d` and `C[1] = f*Q_out*Q_out`. + * The contributions to the local `dC_dy` matrix are `dC_dy[0,0] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. + * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*dP_in`. + * All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. -* The elements of the `E` and `F` matrix are populated using the syntax `system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = A`. Here, `current_block_equation_id` goes from 0 to number_of_equations (for the current block) and `current_block_variable_ids` goes from 0 to size_of_state_vector for the current block. The indices correspond to the block's local state vector mentioned above. + +* The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax +``` +system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a +``` + * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block. + * If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. -* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax `system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = A`, where `A` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id'. For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. + +* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax +``` +system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a +``` + * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. + * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. + * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. **4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** From 2af216fb0b81f49e5ec82a0a7a815b3c480fafd1 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 14:40:41 -0600 Subject: [PATCH 06/41] new file for add_block --- docs/pages/add_block.md | 106 ++++++++++++++++++++++++++++++++++ docs/pages/developer_guide.md | 48 ++++++++------- 2 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 docs/pages/add_block.md diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md new file mode 100644 index 000000000..34be0504e --- /dev/null +++ b/docs/pages/add_block.md @@ -0,0 +1,106 @@ +@page add_block Adding New Blocks + +[TOC] + +# Adding new blocks + +The modular architecture of svZeroDSolver relies on "blocks", such as blood vessels, junctions, valves, boundary conditions, etc. These blocks are assembled in a manner specified by the `.json` configuration file, which dictates the assembled governing equations for the model. We are always interested in adding new blocks to expand the funcitonality of svZeroDSolver. + +To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. + +Below are details on the steps required to implement a new block in svZeroDSolver. + +**1. Add the new block to the following lists/dictionaries:** +* `BlockType` in src/model/BlockType.h +* `block_factory_map` in src/model/Model.cpp + * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* +* If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h + +**2. Create a new class inherited from `Block` for the new block.** +* *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* +* Define a constructor of the form: +``` + GenericBlock(int id, Model *model) + : Block(id, model, BlockType::block_type, BlockClass::block_class, + {{Param_1, InputParameter()}, + {Param_2, InputParameter()}, + ..., + {Param_N, InputParameter()}}) {} +``` + * `GenericBlock` is the name of the new class + * `block_type` and `block_class` are the same as what was added in Step 1 above. + * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. + * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. + * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. + +* The class should have a `setup_dofs(DOFHandler &dofhandler)` function. + * This function typically only includes a call to the following function: + ``` + Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) + ``` + * In the above function, `num_equations` is the number of governing equations for the new block. + * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. + +* The class should have a `TripletsContributions num_triplets{*, *, *}` object. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. + +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. + +* (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. + * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). + * The order of parameters in the `ParamId` object should match the order in the constructor. + +**3. Implement the governing equations for the block.** +* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. + * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. + * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. + +* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. + * `y` is the local state vector mentioned above + * `ydot` is the time-derivative of the local state vector + * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. + * `c` is a vector of length `number_of_equations`. + * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. + * `C` is a vector containing all non-linear and constant terms in the equation. + * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. + +* *For example, let's assume a block has the following non-linear governing equations:* +``` +a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 +``` +``` +e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 +``` + * *For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. + * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. + * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. + * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. + * The contributions to the local `dC_dy` matrix are `dC_dy[0,1] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. + * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. + * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`.* + +* All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. + +* Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. + +* Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. + +* The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax +``` +system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a +``` + * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block. + +* If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. + +* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax +``` +system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a +``` + * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. + * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. + * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. + +**4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** + diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index d21793e1a..f3996168d 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -15,7 +15,7 @@ of svZeroDSolver, namely: * Python API in `pysvzerod.cpp` -## Build in debug mode +# Build in debug mode For debug purposes it is recommended to build svZeroDSolver in Debug mode. @@ -26,7 +26,7 @@ cmake -DCMAKE_BUILD_TYPE=Debug .. cmake --build . ``` -## Install with pip +# Install with pip Execute this command in the root folder to install the current source: ```bash @@ -34,28 +34,31 @@ pip install -e ".[dev]" ``` This is useful when continuously running the integration tests during development. -## Adding new blocks +# Adding new blocks The modular architecture of svZeroDSolver relies on "blocks", such as blood vessels, junctions, valves, boundary conditions, etc. These blocks are assembled in a manner specified by the `.json` configuration file, which dictates the assembled governing equations for the model. We are always interested in adding new blocks to expand the funcitonality of svZeroDSolver. To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. +Detailed steps required to implement a new block in svZeroDSolver are available [here](@ref add_block) + Below are details on the steps required to implement a new block in svZeroDSolver. **1. Add the new block to the following lists/dictionaries:** * `BlockType` in src/model/BlockType.h * `block_factory_map` in src/model/Model.cpp - * *Note:* In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block. + * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h **2. Create a new class inherited from `Block` for the new block.** -* *Note:* The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example. +* *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* * Define a constructor of the form: ``` GenericBlock(int id, Model *model) - : Block(id, model, BlockType::block_type, BlockClass::block_class, i + : Block(id, model, BlockType::block_type, BlockClass::block_class, {{Param_1, InputParameter()}, - {Param_2, InputParameter()}, ..., + {Param_2, InputParameter()}, + ..., {Param_N, InputParameter()}}) {} ``` * `GenericBlock` is the name of the new class @@ -73,7 +76,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. * The class should have a `TripletsContributions num_triplets{*, *, *}` object. - * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dCdy` matrices respectively. Details are in Step 3 below. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. * The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. @@ -81,7 +84,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. -**3. Now implement the governing equations for the block.** +**3. Implement the governing equations for the block.** * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. @@ -95,22 +98,27 @@ Below are details on the steps required to implement a new block in svZeroDSolve * `C` is a vector containing all non-linear and constant terms in the equation. * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. -* For example, let's assume a block has the following non-linear governing equations: +* *For example, let's assume a block has the following non-linear governing equations:* ``` -a*dQ_in/dt + b*P_in + c*(dP_in/dt)*P_in + d = 0 +a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` ``` e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` - * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + * *For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. - * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. - * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*P_in + d` and `C[1] = f*Q_out*Q_out`. - * The contributions to the local `dC_dy` matrix are `dC_dy[0,0] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. - * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*dP_in`. + * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. + * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. + * The contributions to the local `dC_dy` matrix are `dC_dy[0,1] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. + * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. + * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`.* + +* All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. + +* Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. -* All matrix elements that are constant are specified in `update_constant`. Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. +* Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. * The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax ``` @@ -130,7 +138,7 @@ system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[ **4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** -## Code Style +# Code Style We follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). @@ -173,7 +181,7 @@ requirements. On Sherlock at Stanford, clang-format is included in the `llvm` module. -## Documentation +# Documentation We use [Doxygen](https://doxygen.nl) to automatically build an html documentation from source code. Please have at Doxygen's [Documentation Guide](https://www.doxygen.nl/manual/docblocks.html) @@ -187,7 +195,7 @@ and cannot be merged.** In the following you can find a short recap of the most important commands: -### Latex equations +## Latex equations For inline equations use `\f$a+b=c\f$` and for block equations use: ``` \f[ From 777fa8baed341795d61c60d56891ef2cac6ed696 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 15:03:02 -0600 Subject: [PATCH 07/41] test spacing and clean up developer guide --- docs/pages/add_block.md | 57 +++++++++++++------- docs/pages/developer_guide.md | 98 +---------------------------------- 2 files changed, 40 insertions(+), 115 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 34be0504e..1dd78ae0e 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -2,22 +2,19 @@ [TOC] -# Adding new blocks - -The modular architecture of svZeroDSolver relies on "blocks", such as blood vessels, junctions, valves, boundary conditions, etc. These blocks are assembled in a manner specified by the `.json` configuration file, which dictates the assembled governing equations for the model. We are always interested in adding new blocks to expand the funcitonality of svZeroDSolver. - -To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. - Below are details on the steps required to implement a new block in svZeroDSolver. -**1. Add the new block to the following lists/dictionaries:** +# 1. Add the new block to the relevant lists/dictionaries. + * `BlockType` in src/model/BlockType.h * `block_factory_map` in src/model/Model.cpp * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h -**2. Create a new class inherited from `Block` for the new block.** +# 2. Create a new class inherited from `Block` for the new block. + * *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* + * Define a constructor of the form: ``` GenericBlock(int id, Model *model) @@ -44,52 +41,73 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. -* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 3 below. -* (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. +* *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. -**3. Implement the governing equations for the block.** +# 3. Implement the governing equations for the block. + * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. + * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. + * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. * The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. + * `y` is the local state vector mentioned above + * `ydot` is the time-derivative of the local state vector + * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. + * `c` is a vector of length `number_of_equations`. + * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. + * `C` is a vector containing all non-linear and constant terms in the equation. + * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. -* *For example, let's assume a block has the following non-linear governing equations:* +* For example, let's assume a block has the following non-linear governing equations: ``` a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` ``` e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` - * *For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + + * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. + * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. + * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. + * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. + * The contributions to the local `dC_dy` matrix are `dC_dy[0,1] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. + * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. - * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`.* + + * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`. + +* Implement the `update_constant`, `update_time` and `update_solution` functions. -* All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. + * All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. -* Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. + * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. -* Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. + * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. * The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax ``` system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a ``` + * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block. * If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. @@ -98,9 +116,12 @@ system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[curr ``` system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a ``` + * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. + * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. + * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. -**4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** +# 4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to `src/model/CMakeLists.txt` diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index f3996168d..09dc8f4f4 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -40,103 +40,7 @@ The modular architecture of svZeroDSolver relies on "blocks", such as blood vess To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. -Detailed steps required to implement a new block in svZeroDSolver are available [here](@ref add_block) - -Below are details on the steps required to implement a new block in svZeroDSolver. - -**1. Add the new block to the following lists/dictionaries:** -* `BlockType` in src/model/BlockType.h -* `block_factory_map` in src/model/Model.cpp - * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* -* If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h - -**2. Create a new class inherited from `Block` for the new block.** -* *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* -* Define a constructor of the form: -``` - GenericBlock(int id, Model *model) - : Block(id, model, BlockType::block_type, BlockClass::block_class, - {{Param_1, InputParameter()}, - {Param_2, InputParameter()}, - ..., - {Param_N, InputParameter()}}) {} -``` - * `GenericBlock` is the name of the new class - * `block_type` and `block_class` are the same as what was added in Step 1 above. - * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. - * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. - * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. - -* The class should have a `setup_dofs(DOFHandler &dofhandler)` function. - * This function typically only includes a call to the following function: - ``` - Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) - ``` - * In the above function, `num_equations` is the number of governing equations for the new block. - * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. - -* The class should have a `TripletsContributions num_triplets{*, *, *}` object. - * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. - -* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These funtions implement the governing equations for the block. Details are below. - -* (Optional) The class can have an `enum ParamId` object that relates the parameter indices to their names. - * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). - * The order of parameters in the `ParamId` object should match the order in the constructor. - -**3. Implement the governing equations for the block.** -* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. - * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. - * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. - -* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. - * `y` is the local state vector mentioned above - * `ydot` is the time-derivative of the local state vector - * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. - * `c` is a vector of length `number_of_equations`. - * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. - * `C` is a vector containing all non-linear and constant terms in the equation. - * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. - -* *For example, let's assume a block has the following non-linear governing equations:* -``` -a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 -``` -``` -e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 -``` - * *For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. - * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. - * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. - * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. - * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. - * The contributions to the local `dC_dy` matrix are `dC_dy[0,1] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. - * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. - * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`.* - -* All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. - -* Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. - -* Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. - -* The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax -``` -system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a -``` - * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block. - -* If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. - -* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax -``` -system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a -``` - * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. - * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. - * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. - -**4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to src/model/CMakeLists.txt** +Detailed steps required to implement a new block in svZeroDSolver are available [here](@ref add_block). # Code Style From da3afcc3aade9d239c31bf0545d950c4f278d2dd Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 15:16:30 -0600 Subject: [PATCH 08/41] test formatting --- docs/pages/add_block.md | 54 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 1dd78ae0e..db7b19756 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -4,6 +4,8 @@ Below are details on the steps required to implement a new block in svZeroDSolver. +*Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* + # 1. Add the new block to the relevant lists/dictionaries. * `BlockType` in src/model/BlockType.h @@ -11,11 +13,11 @@ Below are details on the steps required to implement a new block in svZeroDSolve * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h -# 2. Create a new class inherited from `Block` for the new block. +# 2. Create a class for the new block -* *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* +## The new class will be inherited from `Block`. -* Define a constructor of the form: +## Define a constructor of the form: ``` GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, @@ -30,6 +32,42 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. +## The class should have a `setup_dofs(DOFHandler &dofhandler)` function. + * This function typically only includes a call to the following function: + ``` + Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) + ``` + * In the above function, `num_equations` is the number of governing equations for the new block. + * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. + +## The class should have a `TripletsContributions num_triplets{*, *, *}` object. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. + +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. + +## *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. + * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). + * The order of parameters in the `ParamId` object should match the order in the constructor. + +# 2. Create a class for the new block + +* The new class will be inherited from `Block`. +


+* Define a constructor of the form: +``` + GenericBlock(int id, Model *model) + : Block(id, model, BlockType::block_type, BlockClass::block_class, + {{Param_1, InputParameter()}, + {Param_2, InputParameter()}, + ..., + {Param_N, InputParameter()}}) {} +``` + * `GenericBlock` is the name of the new class + * `block_type` and `block_class` are the same as what was added in Step 1 above. + * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. + * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. + * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. +


* The class should have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: ``` @@ -41,13 +79,13 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. -* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 3 below. +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. * *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. -# 3. Implement the governing equations for the block. +# 3. Set up the governing equations for the block. * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. @@ -95,6 +133,8 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`. +# 4. Implement the matrix equations for the block. + * Implement the `update_constant`, `update_time` and `update_solution` functions. * All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. @@ -123,5 +163,7 @@ system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[ * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. -# 4. Add the new files (`GenericBlock.h` and `GenericBlock.cpp`) to `src/model/CMakeLists.txt` +# 4. Add the new block to the build system + +* Add `GenericBlock.h` and `GenericBlock.cpp` to `src/model/CMakeLists.txt` From 97ec8597f0d481b9e5ef62f4204a57a92bbb52fa Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 15:25:28 -0600 Subject: [PATCH 09/41] bug --- docs/pages/add_block.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index db7b19756..c984356cc 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -43,7 +43,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve ## The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. -* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. +## The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. ## *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). @@ -137,11 +137,13 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 * Implement the `update_constant`, `update_time` and `update_solution` functions. - * All matrix elements that are constant are specified in `update_constant(SparseSystem &system, std::vector ¶meters)`. + * All matrix elements that are constant are specified in `update_constant`. - * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time(SparseSystem &system, std::vector ¶meters)`. + * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. - * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. Not all blocks will require the latter two functions. + * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. + + * Not all blocks will require the `update_time` and `update_solution` functions. * The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax ``` From be78f3bd89ed89484b27af9f08bb17aaac105611 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 15:50:41 -0600 Subject: [PATCH 10/41] test_spacing --- docs/pages/add_block.md | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index c984356cc..c45ee62a8 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -15,42 +15,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 2. Create a class for the new block -## The new class will be inherited from `Block`. - -## Define a constructor of the form: -``` - GenericBlock(int id, Model *model) - : Block(id, model, BlockType::block_type, BlockClass::block_class, - {{Param_1, InputParameter()}, - {Param_2, InputParameter()}, - ..., - {Param_N, InputParameter()}}) {} -``` - * `GenericBlock` is the name of the new class - * `block_type` and `block_class` are the same as what was added in Step 1 above. - * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. - * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. - * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. - -## The class should have a `setup_dofs(DOFHandler &dofhandler)` function. - * This function typically only includes a call to the following function: - ``` - Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) - ``` - * In the above function, `num_equations` is the number of governing equations for the new block. - * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. - -## The class should have a `TripletsContributions num_triplets{*, *, *}` object. - * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. - -## The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. - -## *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. - * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). - * The order of parameters in the `ParamId` object should match the order in the constructor. - -# 2. Create a class for the new block - * The new class will be inherited from `Block`.


* Define a constructor of the form: @@ -75,10 +39,12 @@ Below are details on the steps required to implement a new block in svZeroDSolve ``` * In the above function, `num_equations` is the number of governing equations for the new block. * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. - +
* The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. +


+ * The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. * *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. From 571019b025cda73bd41f4574fb08820346a7bd63 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 17:06:07 -0600 Subject: [PATCH 11/41] added spacing --- docs/pages/add_block.md | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index c45ee62a8..54c673c87 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -13,11 +13,11 @@ Below are details on the steps required to implement a new block in svZeroDSolve * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h +


+ # 2. Create a class for the new block -* The new class will be inherited from `Block`. -


-* Define a constructor of the form: +* The new class will be inherited from `Block`. Define a constructor of the form: ``` GenericBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, @@ -31,7 +31,9 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. +


+ * The class should have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: ``` @@ -39,18 +41,24 @@ Below are details on the steps required to implement a new block in svZeroDSolve ``` * In the above function, `num_equations` is the number of governing equations for the new block. * `internal_var_names` is a list of strings that specify names for variables that are internal to the block, i.e. all variables for the block apart from the flow and pressure at the block's inlets and outlets. -
+ +


+ * The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below.


-* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Step 4 below. +* The class should have an `update_constant` function and may also contain `update_time` and `update_solution` functions. These functions implement the governing equations for the block. Details are in Steps 3-4 below. + +


* *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. +


+ # 3. Set up the governing equations for the block. * The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. @@ -59,6 +67,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. +


+ * The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. * `y` is the local state vector mentioned above @@ -75,6 +85,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. +


+ * For example, let's assume a block has the following non-linear governing equations: ``` a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 @@ -111,6 +123,8 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 * Not all blocks will require the `update_time` and `update_solution` functions. +


+ * The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax ``` system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a @@ -118,8 +132,12 @@ system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[curr * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block. +


+ * If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. +


+ * For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax ``` system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a @@ -131,6 +149,8 @@ system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[ * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. +


+ # 4. Add the new block to the build system * Add `GenericBlock.h` and `GenericBlock.cpp` to `src/model/CMakeLists.txt` From 6cca7799c930a2a118375a7bf7c59a08f7efa7f0 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 17:10:06 -0600 Subject: [PATCH 12/41] minor edits --- docs/pages/add_block.md | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 54c673c87..543a48782 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -53,7 +53,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-* *(Optional)* The class can have an `enum ParamId` object that relates the parameter indices to their names. +* *Optional:* The class can have an `enum ParamId` object that relates the parameter indices to their names. * This makes it easier to reference the parameters while implementing the governing equations of the block (discussed below). * The order of parameters in the `ParamId` object should match the order in the constructor. @@ -61,28 +61,19 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 3. Set up the governing equations for the block. -* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. - +* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. - * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables.


* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. - * `y` is the local state vector mentioned above - * `ydot` is the time-derivative of the local state vector - * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. - * `c` is a vector of length `number_of_equations`. - * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. - * `C` is a vector containing all non-linear and constant terms in the equation. - * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`.


@@ -94,33 +85,21 @@ a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` - * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. - * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. - * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. - * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. - * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. - * The contributions to the local `dC_dy` matrix are `dC_dy[0,1] = c*(dP_in/dt)` and `dC_dy[1,3] = 2*f*Q_out`. - * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. - * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`. # 4. Implement the matrix equations for the block. * Implement the `update_constant`, `update_time` and `update_solution` functions. - * All matrix elements that are constant are specified in `update_constant`. - * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. - * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. - * Not all blocks will require the `update_time` and `update_solution` functions.


@@ -129,12 +108,14 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a ``` - * Here, `current_block_equation_id` goes from 0 to `number_of_equations-1` (for the current block) and `current_block_variable_ids` goes from 0 to `size_of_state_vector-1` for the current block.


-* If the block contains non-linear equations, these terms must be specified in `update_solution` as `system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term`. +* If the block contains non-linear equations, these terms must be specified in `update_solution` as +``` +system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term +```


@@ -142,11 +123,8 @@ system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[curr ``` system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a ``` - * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. - * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. - * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`.


From 8f424d60843aec901ba3369c82ae01f8374d4142 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 17:17:51 -0600 Subject: [PATCH 13/41] test_space --- docs/pages/add_block.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 543a48782..69770315d 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -97,8 +97,9 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 # 4. Implement the matrix equations for the block. * Implement the `update_constant`, `update_time` and `update_solution` functions. - * All matrix elements that are constant are specified in `update_constant`. - * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. + * All matrix elements that are constant are specified in `update_constant`.\ + * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. + \ * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. * Not all blocks will require the `update_time` and `update_solution` functions. From 69f82f5a0d8a49466d98bd31c5a2b1b8c7120048 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 17:45:32 -0600 Subject: [PATCH 14/41] cleaned up --- docs/pages/add_block.md | 56 ++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 69770315d..46b039b79 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -6,12 +6,13 @@ Below are details on the steps required to implement a new block in svZeroDSolve *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* -# 1. Add the new block to the relevant lists/dictionaries. +# 1. Name the new block -* `BlockType` in src/model/BlockType.h -* `block_factory_map` in src/model/Model.cpp +* The name should then be added to the following lists/dictionaries: + * `BlockType` in src/model/BlockType.h + * `block_factory_map` in src/model/Model.cpp * *Note: In `block_factory_map`, the dictionary key should match the string specifying the type of block in the `.json` configuration/input file, and the dictionary value should match the class constructor name for the block.* -* If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h + * If the new block requires special handling that is different from the current blocks (most new blocks do not), add a new category to `BlockClass` in src/model/BlockType.h


@@ -45,7 +46,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve


* The class should have a `TripletsContributions num_triplets{*, *, *}` object. - * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. Details are in Step 3 below. + * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. + * Details are in Step 3 below.


@@ -61,24 +63,25 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 3. Set up the governing equations for the block. -* The local state vector for each block is always arranged as `[P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. +* The local state vector for each block is always arranged as `y = [P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. - * The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables. + * The corresponding time-derivative of this state vector is `ydot = dP_in/dt, dQ_in/dt, ...]`. + * *Note: The length of the state vector is typically four (inlet and outlet pressure and flow) plus the number of internal variables.*


-* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,t) = 0`. - * `y` is the local state vector mentioned above - * `ydot` is the time-derivative of the local state vector +* The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,ydot,t) = 0`. + * `y` is the local state vector mentioned above. + * `ydot` is the time-derivative of the local state vector. * `E` and `F` are matrices of size `number_of_equations*size_of_state_vector`. - * `c` is a vector of length `number_of_equations`. + * `C` is a vector of length `number_of_equations`. * `E` and `F` contain terms of the governing equation that multiply the respective components of `ydot` and `y` respectively. - * `C` is a vector containing all non-linear and constant terms in the equation. - * If the equation contains non-linear terms, the developer should also derive the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`. + * `C` contains all non-linear and constant terms in the equation. + * If the equation contains non-linear terms, the developer should also write out the derivative of `C` with respect to `y` and `ydot`. These will be stored in the block's `dC_dy` and `dC_dydot` matrices, both of which are size `number_of_equations*size_of_state_vector`.


-* For example, let's assume a block has the following non-linear governing equations: +* **Example:** Assume a block has the following non-linear governing equations: ``` a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` @@ -94,18 +97,19 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 * The contributions to the local `dC_dydot` matrix are `dC_dydot[0,0] = c*Q_in`. * In this case, the block has 3 contributions to `F`, 2 contributions to `E`, and 2 constributions to `dC_dy`. So the class will have a member `TripletsContributions num_triplets{3, 2, 2}`. +


+ # 4. Implement the matrix equations for the block. * Implement the `update_constant`, `update_time` and `update_solution` functions. - * All matrix elements that are constant are specified in `update_constant`.\ - * Matrix elements that depend only on time (not the solution of the problem itself) are specified in `update_time`. - \ + * All matrix elements that are constant are specified in `update_constant`. + * Matrix elements that depend only on time (not the state variables) are specified in `update_time`. * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. - * Not all blocks will require the `update_time` and `update_solution` functions. + * *Note: Not all blocks will require the `update_time` and `update_solution` functions.*


-* The elements of the `E`, `F`, `dC_dy` and `dC_dydot` matrices are populated using the syntax +* The elements of the matrices `E`, `F`, `dC_dy` and `dC_dydot` are populated using the following syntax: ``` system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a ``` @@ -113,20 +117,26 @@ system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[curr


-* If the block contains non-linear equations, these terms must be specified in `update_solution` as +* If the governing equations contain non-linear terms, these terms must be specified in `update_solution` as: ``` system.C(global_eqn_ids[current_block_equation_id]) = non_linear_term ```


-* For non-linear equations, the derivative of the term specified above with respect to each state variable must also be provided. This goes into a `dC_dy` matrix using the following syntax +* For non-linear equations, the derivative of the terms in `C` with respect to each state variable `y` and `ydot` must also be provided. These go into `dC_dy` and `dC_dydot` matrices. + * A `dC_dy` matrix contribution can be specified using the following syntax: ``` system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_id]) = a ``` * Here, `a` is the derivative of the non-linear term in the equation with ID `current_block_equation_id` with respect to the local state variable with ID `current_block_variable_id`. - * For example, if the non-linear term is in the first equation, `current_block_equation_id = 0`. - * For the derivative of this term with respect to `P_in`, `current_block_variable_id = 0` and for the derivative of this term with respect to `P_out`, `current_block_variable_id = 2`. + * For example, if the non-linear term is in the first equation, then `current_block_equation_id = 0`. + * For the derivative of this term with respect to `P_in`, set `current_block_variable_id = 0`, and for the derivative of this term with respect to `P_out`, set `current_block_variable_id = 2`. + * The same indexing applies to derivatives with respect to the `ydot` state variables, i.e. for the derivative of the term with respect to `dP_in/dt`, set `current_block_variable_id = 0`. + +


+ +* *Note: Any matrix and vector components that are not specified are 0 by default.*


From c9a4b76cd6235b26bf9db37d04e1d1818c92fa4a Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 18:00:36 -0600 Subject: [PATCH 15/41] subheadings --- docs/pages/add_block.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 46b039b79..ef0a49dec 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -18,6 +18,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 2. Create a class for the new block +## Class constructor + * The new class will be inherited from `Block`. Define a constructor of the form: ``` GenericBlock(int id, Model *model) @@ -35,6 +37,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


+## Set up the degrees of freedom * The class should have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: ``` @@ -45,6 +48,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve


+## Other class members + * The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. * Details are in Step 3 below. @@ -63,6 +68,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 3. Set up the governing equations for the block. +## State vector + * The local state vector for each block is always arranged as `y = [P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. * The corresponding time-derivative of this state vector is `ydot = dP_in/dt, dQ_in/dt, ...]`. @@ -70,6 +77,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve


+## Governing equations + * The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,ydot,t) = 0`. * `y` is the local state vector mentioned above. * `ydot` is the time-derivative of the local state vector. @@ -81,7 +90,9 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-* **Example:** Assume a block has the following non-linear governing equations: +### An example + +Assume a block has the following non-linear governing equations: ``` a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` @@ -101,6 +112,8 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 # 4. Implement the matrix equations for the block. +## Functions that implement the equations + * Implement the `update_constant`, `update_time` and `update_solution` functions. * All matrix elements that are constant are specified in `update_constant`. * Matrix elements that depend only on time (not the state variables) are specified in `update_time`. @@ -109,6 +122,8 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0


+## Implementation details + * The elements of the matrices `E`, `F`, `dC_dy` and `dC_dydot` are populated using the following syntax: ``` system.F.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[current_block_variable_ids]) = a From fc7d09f6e5f342436fcfd2a4ee5804d0e44460ba Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 18:20:12 -0600 Subject: [PATCH 16/41] modified subheadings --- docs/pages/add_block.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index ef0a49dec..d038711b9 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -18,7 +18,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve # 2. Create a class for the new block -## Class constructor +### Class constructor * The new class will be inherited from `Block`. Define a constructor of the form: ``` @@ -37,7 +37,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-## Set up the degrees of freedom +### Set up the degrees of freedom * The class should have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: ``` @@ -48,7 +48,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-## Other class members +### Other class members * The class should have a `TripletsContributions num_triplets{*, *, *}` object. * This specifies how many elements the governing equations of the block contribute to the global `F`, `E` and `dC_dy` matrices respectively. @@ -66,9 +66,9 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-# 3. Set up the governing equations for the block. +# 3. Set up the governing equations for the block -## State vector +### State vector * The local state vector for each block is always arranged as `y = [P_in, Q_in, P_out, Q_out, InternalVariable_1, ..., InternalVariable_N]`. * Here, `InternalVariable*` refers to any variable in the governing equations that are not the inlet and outlet flow and pressure. These are the same as those discussed above in the function `setup_dofs`. @@ -77,7 +77,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-## Governing equations +### Governing equations * The equations should be written in the form `E(t)*ydot + F(t)*y + C(y,ydot,t) = 0`. * `y` is the local state vector mentioned above. @@ -110,9 +110,9 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0


-# 4. Implement the matrix equations for the block. +# 4. Implement the matrix equations for the block -## Functions that implement the equations +### Functions that contain the equations * Implement the `update_constant`, `update_time` and `update_solution` functions. * All matrix elements that are constant are specified in `update_constant`. @@ -122,7 +122,7 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0


-## Implementation details +### Implementation details * The elements of the matrices `E`, `F`, `dC_dy` and `dC_dydot` are populated using the following syntax: ``` From e4661330924a0121049a620d423a62400f06f63e Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 18:29:39 -0600 Subject: [PATCH 17/41] subheading foix --- docs/pages/add_block.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index d038711b9..68863254c 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -6,7 +6,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* -# 1. Name the new block +## 1. Name the new block * The name should then be added to the following lists/dictionaries: * `BlockType` in src/model/BlockType.h @@ -16,7 +16,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-# 2. Create a class for the new block +## 2. Create a class for the new block ### Class constructor @@ -66,7 +66,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-# 3. Set up the governing equations for the block +## 3. Set up the governing equations for the block ### State vector @@ -110,7 +110,7 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0


-# 4. Implement the matrix equations for the block +## 4. Implement the matrix equations for the block ### Functions that contain the equations From 50d65484a3e1fcfe37968326f287946004e09dec Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 25 Jun 2024 19:01:11 -0600 Subject: [PATCH 18/41] cleaned up subheadings --- docs/pages/add_block.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 68863254c..1f4474007 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -6,7 +6,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve *Note: The best way to implement a new block is to look at examples of existing block classes. See the `ValveTanh` class for an example.* -## 1. Name the new block +## 1. Name the new block. * The name should then be added to the following lists/dictionaries: * `BlockType` in src/model/BlockType.h @@ -16,7 +16,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-## 2. Create a class for the new block +## 2. Create a class for the new block. ### Class constructor @@ -66,7 +66,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve


-## 3. Set up the governing equations for the block +## 3. Set up the governing equations for the block. ### State vector @@ -92,7 +92,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve ### An example -Assume a block has the following non-linear governing equations: +* Assume a block has the following non-linear governing equations: ``` a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` @@ -110,9 +110,7 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0


-## 4. Implement the matrix equations for the block - -### Functions that contain the equations +## 4. Implement the matrix equations for the block. * Implement the `update_constant`, `update_time` and `update_solution` functions. * All matrix elements that are constant are specified in `update_constant`. @@ -155,7 +153,7 @@ system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[


-# 4. Add the new block to the build system +## 4. Add the new block to the build system. * Add `GenericBlock.h` and `GenericBlock.cpp` to `src/model/CMakeLists.txt` From 64450de7e2dbc9ccfb8846039e3faa489e5d7821 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Wed, 26 Jun 2024 08:47:32 -0700 Subject: [PATCH 19/41] added contributing section --- docs/pages/developer_guide.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index 09dc8f4f4..c4e876740 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -34,12 +34,24 @@ pip install -e ".[dev]" ``` This is useful when continuously running the integration tests during development. +# Contributing to svZeroDSolver + +**NOTE: To contribute new developments to the main branch of svZeroDSolver, developers must first open an issue on the svZeroDSolver Github repository to describe the planned changes.** + +* The changes should be implemented in a feature branch of the developer's fork of svZeroDSolver. +* Once the changes are implemented, the developer should make sure the build, documentation, and code format tests are passing on the user's feature branch. + * The tests are automatically run when pushing changes to the developer's remote branch on Github. + * Alternatively, the developer can run the tests locally. + * The build tests can be run using the `pip` install and `pytest`. + * The tests for the C++ interface require the `CMake` install and can be run by building the tests in `svZeroDSolver/tests/test_interface`. + * Code formatting can be performed using the instructions in the [Formatting](#formatting) section below. + * The documentation can be built following the instructions in the [Documentation](#documentation) section below. +* Once all the tests are passing, the developer should open a pull request from the feature branch and link the relevant issue. + # Adding new blocks The modular architecture of svZeroDSolver relies on "blocks", such as blood vessels, junctions, valves, boundary conditions, etc. These blocks are assembled in a manner specified by the `.json` configuration file, which dictates the assembled governing equations for the model. We are always interested in adding new blocks to expand the funcitonality of svZeroDSolver. -To add a new block, developers must first open an issue to describe the planned block on the svZeroDSolver Github repository. The new block should be implemented in a feature branch of the developer's fork of svZeroDSolver. Once the new block is implemented, the developer should open a pull request from the feature branch and link the relevant issue. - Detailed steps required to implement a new block in svZeroDSolver are available [here](@ref add_block). # Code Style From be1718dfd23e46d0c2b298b4752ab3bf15bb5670 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Wed, 26 Jun 2024 09:06:34 -0700 Subject: [PATCH 20/41] test link section --- docs/pages/developer_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index c4e876740..b41f1a27e 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -44,8 +44,8 @@ This is useful when continuously running the integration tests during developmen * Alternatively, the developer can run the tests locally. * The build tests can be run using the `pip` install and `pytest`. * The tests for the C++ interface require the `CMake` install and can be run by building the tests in `svZeroDSolver/tests/test_interface`. - * Code formatting can be performed using the instructions in the [Formatting](#formatting) section below. - * The documentation can be built following the instructions in the [Documentation](#documentation) section below. + * Code formatting can be performed using the instructions in the [Formatting] section below. + * The documentation can be built following the instructions in the [Documentation] section below. * Once all the tests are passing, the developer should open a pull request from the feature branch and link the relevant issue. # Adding new blocks From db7dedaabaf463580f98c4c47f4090ffd0cd21ef Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Wed, 26 Jun 2024 09:25:24 -0700 Subject: [PATCH 21/41] clean up --- docs/pages/developer_guide.md | 12 ++++++------ docs/pages/main.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/pages/developer_guide.md b/docs/pages/developer_guide.md index b41f1a27e..204f6ebe7 100644 --- a/docs/pages/developer_guide.md +++ b/docs/pages/developer_guide.md @@ -44,8 +44,8 @@ This is useful when continuously running the integration tests during developmen * Alternatively, the developer can run the tests locally. * The build tests can be run using the `pip` install and `pytest`. * The tests for the C++ interface require the `CMake` install and can be run by building the tests in `svZeroDSolver/tests/test_interface`. - * Code formatting can be performed using the instructions in the [Formatting] section below. - * The documentation can be built following the instructions in the [Documentation] section below. + * Code formatting can be performed following the instructions in the Formatting section below. + * The documentation can be built following the instructions in the Documentation section below. * Once all the tests are passing, the developer should open a pull request from the feature branch and link the relevant issue. # Adding new blocks @@ -119,12 +119,12 @@ a+b=c \f] ``` -### Citations +## Citations If you want to cite a piece literature in your documentation, add a respective BibTeX citation to `docs/references.bib` and use `\cite name_of_citation` to cite the document. -### Drawing circuits +## Drawing circuits As the elements of the svZeroDSolver are often represented in the form of electrical circuits, we use [CircuiTikZ](https://ctan.org/pkg/circuitikz?lang=en) to draw circuits in the documentation (see blocks in Block for examples). @@ -137,7 +137,7 @@ To start a CircuitTikZ drawing use the following command: \f] ``` -### Build +## Build The documentation is automatically built in the GitHub CI/CD and published on GitHub pages. If you want to build the documentation locally, you can use: @@ -149,7 +149,7 @@ You can then view the documentation locally in your browser by opening `docs/bui If you do not have Doxygen install you can do that with `brew install doxygen` on macOS or with `sudo apt-get install doxygen` on Linux. -## Profiling +# Profiling Profiling helps to easily identify bottlenecks in the code. A profiling report lists the executation time spend on certain parts of the code. If you experience diff --git a/docs/pages/main.md b/docs/pages/main.md index 030613de4..7ada19813 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -100,7 +100,7 @@ cmake --build . If you are a developer and want to contribute to svZeroDSolver, you can find more helpful information in our [Developer Guide](@ref developer_guide). -# svZeroDSolver +# svZeroDSolver - Quick Guide svZeroDSolver can be used to run zero-dimensional (0D) cardiovascular simulations based on a given configuration. @@ -336,7 +336,7 @@ The above table describes the most commonly used boundary conditions. In additio Note that the `FLOW` and `PRESSURE` boundary conditions accept mathematical expressions in `bc_values`. For an example where values of the boundary condition are specified as a function of time, see `svZeroDSolver/tests/cases/pulsatileFlow_R_RCR.json`. For an example with a mathematical expression for the boundary condition, see `svZeroDSolver/tests/cases/timeDep_Flow.json`. -# svZeroDCalibrator +# svZeroDCalibrator - Quick Guide svZeroDCalibrator can be used to calibrate cardiovascular 0D models (i.e. infer optimal parameters for the 0D elements) based on a given transient result (i.e. from a From 084f865d55926ea558f8236e70b5b84e48a97fa2 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 8 Jul 2024 21:16:12 -0700 Subject: [PATCH 22/41] review comments --- docs/pages/add_block.md | 18 ++++----- docs/pages/main.md | 84 ++++++++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 1f4474007..c1e477a71 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -22,23 +22,23 @@ Below are details on the steps required to implement a new block in svZeroDSolve * The new class will be inherited from `Block`. Define a constructor of the form: ``` - GenericBlock(int id, Model *model) + MyNewBlock(int id, Model *model) : Block(id, model, BlockType::block_type, BlockClass::block_class, {{Param_1, InputParameter()}, {Param_2, InputParameter()}, ..., {Param_N, InputParameter()}}) {} ``` - * `GenericBlock` is the name of the new class + * `MyNewBlock` is the name of the new class * `block_type` and `block_class` are the same as what was added in Step 1 above. * The names of the input parameters of the block are `Param_1`, ... , `Param_N`. * The properties of each parameter are defined by `InputParameter`, which specifies whether it is optional, an array, a scalar, a function, and its default value. - * The names `Param_1`, ... , `Param_N` should be the same as the parameter names within the block definition in the `.json` configuration/input file. + * The names `Param_1`, ... , `Param_N` must be the same as the parameter names within the block definition in the `.json` configuration/input file.


### Set up the degrees of freedom -* The class should have a `setup_dofs(DOFHandler &dofhandler)` function. +* The class must have a `setup_dofs(DOFHandler &dofhandler)` function. * This function typically only includes a call to the following function: ``` Block::setup_dofs_(DOFHandler &dofhandler, int num_equations, const std::list &internal_var_names) @@ -99,7 +99,7 @@ a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` - * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. + * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. Test $dQ{in}/dt$. * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. @@ -113,9 +113,9 @@ e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ## 4. Implement the matrix equations for the block. * Implement the `update_constant`, `update_time` and `update_solution` functions. - * All matrix elements that are constant are specified in `update_constant`. - * Matrix elements that depend only on time (not the state variables) are specified in `update_time`. - * Matrix elements that change with the solution (i.e. depend on the state variables themselves) are specified in `update_solution`. + * All matrix elements that are constant must be specified in `update_constant`. + * Matrix elements that depend only on time (not the state variables) must be specified in `update_time`. + * Matrix elements that change with the solution (i.e. depend on the state variables themselves) must be specified in `update_solution`. * *Note: Not all blocks will require the `update_time` and `update_solution` functions.*


@@ -155,5 +155,5 @@ system.dC_dy.coeffRef(global_eqn_ids[current_block_equation_id], global_var_ids[ ## 4. Add the new block to the build system. -* Add `GenericBlock.h` and `GenericBlock.cpp` to `src/model/CMakeLists.txt` +* Add `MyNewBlock.h` and `MyNewBlock.cpp` to `src/model/CMakeLists.txt` diff --git a/docs/pages/main.md b/docs/pages/main.md index 7ada19813..4fefe7224 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -253,20 +253,20 @@ The svZeroDSolver can be configured with the following options in the default value must be specified. -Parameter key | Description | Default value ---------------------------------------- | ----------------------------------------- | ----------- -number_of_cardiac_cycles | Number of cardiac cycles to simulate | - -number_of_time_pts_per_cardiac_cycle | Number of time steps per cardiac cycle | - -absolute_tolerance | Absolute tolerance for time integration | \f$10^{-8}\f$ -maximum_nonlinear_iterations | Maximum number of nonlinear iterations for time integration | \f$30\f$ -steady_initial | Toggle whether to use the steady solution as the initial condition for the simulation | true -output_variable_based | Output solution based on variables (i.e. flow+pressure at nodes and internal variables) | false -output_interval | The frequency of writing timesteps to the output (1 means every time step is written to output) | \f$1\f$ -output_mean_only | Write only the mean values over every timestep to output file | false -output_derivative | Write time derivatives to output file | false -output_all_cycles | Write all cardiac cycles to output file | false -use_cycle_to_cycle_error | Use cycle-to-cycle error to determine number of cycles for convergence | false -sim_cycle_to_cycle_percent_error | Percentage error threshold for cycle-to-cycle pressure and flow difference | 1.0 +Parameter key | Description | Default value +----------------------------------------- | ----------------------------------------- | ----------- +`number_of_cardiac_cycles` | Number of cardiac cycles to simulate | - +`number_of_time_pts_per_cardiac_cycle` | Number of time steps per cardiac cycle | - +`absolute_tolerance` | Absolute tolerance for time integration | \f$10^{-8}\f$ +`maximum_nonlinear_iterations` | Maximum number of nonlinear iterations for time integration | \f$30\f$ +`steady_initial` | Toggle whether to use the steady solution as the initial condition for the simulation | true +`output_variable_based` | Output solution based on variables (i.e. flow+pressure at nodes and internal variables) | false +`output_interval` | The frequency of writing timesteps to the output (1 means every time step is written to output) | \f$1\f$ +`output_mean_only` | Write only the mean values over every timestep to output file | false +`output_derivative` | Write time derivatives to output file | false +`output_all_cycles` | Write all cardiac cycles to output file | false +`use_cycle_to_cycle_error` | Use cycle-to-cycle error to determine number of cycles for convergence | false +`sim_cycle_to_cycle_percent_error` | Percentage error threshold for cycle-to-cycle pressure and flow difference | 1.0 The option `use_cycle_to_cycle_error` allows the solver to change the number of cardiac cycles it runs depending on the cycle-to-cycle convergence of the simulation. For simulations with no RCR boundary conditions, the simulation will add extra cardiac cycles until the difference between the mean pressure and flow in consecutive cycles is below the threshold set by `sim_cycle_to_cycle_percent_error` at all inlets and outlets of the model. If there is at least one RCR boundary condition, the number of cycles is determined based on equation 21 of \cite pfaller21, using the RCR boundary condition with the largest time constant. @@ -287,9 +287,9 @@ More information about the vessels can be found in their respective class refere } ``` -Description | Class | `zero_d_element_type` | `zero_d_element_values` -------------------------------------- | --------------------------- | --------------------- | ------------------------ -Blood vessel with \n optional stenosis | MODEL::BloodVessel | `BloodVessel` | `C`: Capacity \n `L`: Inductance \n `R_poiseuille`: Poiseuille resistance \n `stenosis_coefficient`: Stenosis coefficient +Description | Class | `zero_d_element_type` | `zero_d_element_values` +---------------------------------------- | --------------------------- | --------------------- | ------------------------ +Blood vessel with \n optional stenosis | BloodVessel | `BloodVessel` | `C`: Capacity \n `L`: Inductance \n `R_poiseuille`: Poiseuille resistance \n `stenosis_coefficient`: Stenosis coefficient ### Junctions @@ -306,11 +306,11 @@ More information about the junctions can be found in their respective class refe } ``` -Description | Class | `junction_type` | `junction_values` -------------------------------------- | --------------------------- | --------------------- | ----------- -Purely mass \n conserving \n junction | MODEL::Junction | `NORMAL_JUNCTION` | - -Resistive \n junction | MODEL::ResistiveJunction | `resistive_junction` | `R`: Ordered list of resistances for all inlets and outlets -Blood vessel \n junction | MODEL::BloodVesselJunction | `BloodVesselJunction` | Same as for `BloodVessel` element but \n as ordered list for each inlet and outlet +Description | Class | `junction_type` | `junction_values` +------------------------------------- | ---------------------| --------------------- | ----------- +Purely mass \n conserving \n junction | Junction | `NORMAL_JUNCTION` | - +Resistive \n junction | ResistiveJunction | `resistive_junction` | `R`: Ordered list of resistances for all inlets and outlets +Blood vessel \n junction | BloodVesselJunction | `BloodVesselJunction` | Same as for `BloodVessel` element but \n as ordered list for each inlet and outlet ### Boundary conditions @@ -324,17 +324,41 @@ More information about the boundary conditions can be found in their respective }, ``` -Description | Class | `bc_type` | `bc_values` -------------------------------------- | --------------------------- | --------------------- | ----------- -Prescribed (transient) flow | MODEL::FlowReferenceBC | `FLOW` | `Q`: Time-dependent flow values \n `t`: Time steps \n `fn`: Mathematical expression -Prescribed (transient) pressure | MODEL::PressureReferenceBC | `PRESSURE` | `P`: Time-dependent pressure values \n `t`: Time steps \n `fn`: Mathematical expression -Resistance | MODEL::ResistanceBC | `RESISTANCE` | `R`: Resistance \n `Pd`: Time-dependent distal pressure \n `t`: Time stamps -Windkessel | MODEL::WindkesselBC | `RCR` | `Rp`: Proximal resistance \n `C`: Capacitance \n `Rd`: Distal resistance \n `Pd`: Distal pressure -Coronary outlet | MODEL::OpenLoopCoronaryBC | `CORONARY` | `Ra`: Proximal resistance \n `Ram`: Microvascular resistance \n `Rv`: Venous resistance \n `Ca`: Small artery capacitance \n `Cim`: Intramyocardial capacitance \n `Pim`: Intramyocardial pressure \n `Pv`: Venous pressure +Description | Class | `bc_type` | `bc_values` +------------------------------------- | ---------------------- | --------------------- | ----------- +Prescribed (transient) flow | FlowReferenceBC | `FLOW` | `Q`: Time-dependent flow values \n `t`: Time steps \n `fn`: Mathematical expression \n Note: Either specify `Q` and `t` together, or just `fn` +Prescribed (transient) pressure | PressureReferenceBC | `PRESSURE` | `P`: Time-dependent pressure values \n `t`: Time steps \n `fn`: Mathematical expression \n Note: Either specify `Q` and `t` together, or just `fn` +Resistance | ResistanceBC | `RESISTANCE` | `R`: Resistance \n `Pd`: Time-dependent distal pressure \n `t`: Time stamps +Windkessel | WindkesselBC | `RCR` | `Rp`: Proximal resistance \n `C`: Capacitance \n `Rd`: Distal resistance \n `Pd`: Distal pressure +Coronary outlet | OpenLoopCoronaryBC | `CORONARY` | `Ra`: Proximal resistance \n `Ram`: Microvascular resistance \n `Rv`: Venous resistance \n `Ca`: Small artery capacitance \n `Cim`: Intramyocardial capacitance \n `Pim`: Intramyocardial pressure \n `Pv`: Venous pressure The above table describes the most commonly used boundary conditions. In addition, svZeroDSolver includes various closed-loop boundary conditions. Examples can be found in `svZeroDSolver/tests/cases`. -Note that the `FLOW` and `PRESSURE` boundary conditions accept mathematical expressions in `bc_values`. For an example where values of the boundary condition are specified as a function of time, see `svZeroDSolver/tests/cases/pulsatileFlow_R_RCR.json`. For an example with a mathematical expression for the boundary condition, see `svZeroDSolver/tests/cases/timeDep_Flow.json`. +Note that the `FLOW` and `PRESSURE` boundary conditions accept mathematical expressions in `bc_values`. For example, values of the boundary condition can be specified as a function of time as follow: +```python +{ + "bc_name": "INFLOW", # Name of the boundary condition + "bc_type": "FLOW", # Type of the boundary condition + "bc_values": { + "Q": [ ..., ..., ... ], + "t": [ ..., ..., ... ] + } +}, +``` +See `svZeroDSolver/tests/cases/pulsatileFlow_R_RCR.json` for an example. + +They can also be specified as a mathematica expression as follow: +```python +{ + "bc_name": "INFLOW", # Name of the boundary condition + "bc_type": "FLOW", # Type of the boundary condition + "bc_values": { + "fn": "2.0 * (4*atan(1.)) * cos(2.0 * (4*atan(1.)) * t)" + } +}, +``` +For an example with a mathematical expression for the boundary condition, see `svZeroDSolver/tests/cases/timeDep_Flow.json`. + # svZeroDCalibrator - Quick Guide From 8ee2c7cfb57fa1cb471150b4d663440de8ed6bf8 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 8 Jul 2024 21:37:09 -0700 Subject: [PATCH 23/41] test eqn --- docs/pages/add_block.md | 7 ++++++- docs/pages/main.md | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index c1e477a71..287292d8c 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -99,7 +99,12 @@ a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 ``` e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` - * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. Test $dQ{in}/dt$. + +\f$e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0\f$ + +\f$$e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0\f$$ + + * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. diff --git a/docs/pages/main.md b/docs/pages/main.md index 4fefe7224..189fd0865 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -340,8 +340,8 @@ Note that the `FLOW` and `PRESSURE` boundary conditions accept mathematical expr "bc_name": "INFLOW", # Name of the boundary condition "bc_type": "FLOW", # Type of the boundary condition "bc_values": { - "Q": [ ..., ..., ... ], - "t": [ ..., ..., ... ] + "Q": [ ..., ..., ... ], # Comma-separated list of values + "t": [ ..., ..., ... ] # Comma-separated list of corresponding time stamps } }, ``` From a244c761dccb69233d05595e4605d4cbb3390676 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 8 Jul 2024 21:59:11 -0700 Subject: [PATCH 24/41] test eqn again --- docs/pages/add_block.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 287292d8c..cec2f7275 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -100,9 +100,9 @@ a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` -\f$e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0\f$ +\f$edP_out/dt + fQ_outQ_out + gP_out + hI_1 = 0\f$ -\f$$e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0\f$$ +\f$$edP_out/dt + fQ_outQ_out + gP_out + hI_1 = 0\f$$ * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. From 0af7068f904f1090bb3dacb46c3fa78cc5c15c43 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Mon, 8 Jul 2024 22:12:37 -0700 Subject: [PATCH 25/41] mod eqn --- docs/pages/add_block.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index cec2f7275..1d149304d 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -100,9 +100,7 @@ a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 ``` -\f$edP_out/dt + fQ_outQ_out + gP_out + hI_1 = 0\f$ - -\f$$edP_out/dt + fQ_outQ_out + gP_out + hI_1 = 0\f$$ +\f$ e dP_out /dt + fQ_out Q_out + gP_out + hI_1 = 0 \f$ * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. From 385f25d87abc6c9848cd3ebff9ed879c80d92e19 Mon Sep 17 00:00:00 2001 From: "Martin R. Pfaller" Date: Tue, 9 Jul 2024 08:42:10 -0400 Subject: [PATCH 26/41] GitHub actions: remove old os, add latest --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e26b9ae73..39788c3b9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-11, macos-12] + os: [ubuntu-22.04, ubuntu-latest, macos-12, macos-latest] fail-fast: false steps: - uses: actions/checkout@v4 From 51dba6bd744cb0bfa378bce8dc39abc9a974e50f Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 07:06:08 -0700 Subject: [PATCH 27/41] doxy latex font size --- docs/Doxyfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Doxyfile b/docs/Doxyfile index f1cf2e449..671b95337 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -225,6 +225,7 @@ USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain +FORMULA_FONTSIZE = 20 #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- From 458c46e96b27134a81cd76617a238afed3695eb5 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 07:18:47 -0700 Subject: [PATCH 28/41] test conda PATH --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39788c3b9..c682407c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,7 @@ jobs: run: sudo apt update && sudo apt install build-essential cmake lcov - name: Install svZeroDSolver run: | + export PATH="/usr/share/miniconda/bin:$PATH" conda create -n zerod python=3.11.4 conda run -n zerod pip install -e ".[dev]" - name: Test the build From add1b162c56b79947a7d16e60e2700dd66893956 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 07:22:06 -0700 Subject: [PATCH 29/41] try codna alias --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c682407c9..e11f251e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,7 @@ jobs: - name: Install svZeroDSolver run: | export PATH="/usr/share/miniconda/bin:$PATH" + alias conda="$CONDA/bin/conda" conda create -n zerod python=3.11.4 conda run -n zerod pip install -e ".[dev]" - name: Test the build From 4791f6fab98e6bc523f6c05a9a66167df48fd419 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 14:24:52 -0600 Subject: [PATCH 30/41] added eqns, modified size --- docs/Doxyfile | 2 +- docs/pages/add_block.md | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 671b95337..59fff9b0a 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -225,7 +225,7 @@ USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -FORMULA_FONTSIZE = 20 +FORMULA_FONTSIZE = 15 #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 1d149304d..10697835d 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -93,17 +93,13 @@ Below are details on the steps required to implement a new block in svZeroDSolve ### An example * Assume a block has the following non-linear governing equations: -``` -a*dQ_in/dt + b*P_in + c*(dP_in/dt)*Q_in + d = 0 -``` -``` -e*dP_out/dt + f*Q_out*Q_out + g*P_out + h*I_1 = 0 -``` -\f$ e dP_out /dt + fQ_out Q_out + gP_out + hI_1 = 0 \f$ +\f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 + +\f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ - * For this block, the `P_in` and `Q_in` are the pressure and flow at the inlet respectively, `P_out` and `Q_out` are the pressure and flow at the outlet, and `I_1` is an internal variable. - * The state vector is `[P_in, Q_in, P_out, Q_out, I_1]`. + * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. + * The state vector is \f$[P_{in}, Q_{in}, P_{out}, Q_{out}, I_{1}]\f$. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. From 9b809a6121f969be5f9567370c883467094a0427 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 14:41:20 -0600 Subject: [PATCH 31/41] test conda-incubator in GH actions to find conda --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e11f251e7..1e2a597c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,16 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 + - uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true - name: Install ubuntu dependencies if: startsWith(matrix.os, 'ubuntu') run: sudo apt update && sudo apt install build-essential cmake lcov - name: Install svZeroDSolver run: | - export PATH="/usr/share/miniconda/bin:$PATH" - alias conda="$CONDA/bin/conda" + #export PATH="/usr/share/miniconda/bin:$PATH" + #alias conda="$CONDA/bin/conda" conda create -n zerod python=3.11.4 conda run -n zerod pip install -e ".[dev]" - name: Test the build From 48141d929bd4c327312906848778f30a76096ccb Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 14:46:51 -0600 Subject: [PATCH 32/41] bug in eqns --- docs/pages/add_block.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 10697835d..870998d24 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,7 +94,11 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: -\f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 +\f[ +a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 +\f] + +\f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ \f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ From 515bc530be7ebed291ab8be08bac369e5372b09f Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 14:54:18 -0600 Subject: [PATCH 33/41] eqns clean --- docs/pages/add_block.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 870998d24..3d9754167 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,10 +94,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: -\f[ -a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 -\f] - \f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ \f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ From ba56182a272ab6d353c2f0398a17a5de44157148 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 15:21:14 -0600 Subject: [PATCH 34/41] added outputs description, debug eqn --- docs/pages/add_block.md | 2 -- docs/pages/main.md | 23 +++++++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 3d9754167..b4ed4c67f 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,8 +94,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: -\f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ - \f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. diff --git a/docs/pages/main.md b/docs/pages/main.md index 189fd0865..f8871d2d1 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -100,7 +100,7 @@ cmake --build . If you are a developer and want to contribute to svZeroDSolver, you can find more helpful information in our [Developer Guide](@ref developer_guide). -# svZeroDSolver - Quick Guide +# svZeroDSolver - Quick User Guide svZeroDSolver can be used to run zero-dimensional (0D) cardiovascular simulations based on a given configuration. @@ -114,7 +114,7 @@ file. svzerodsolver tests/cases/steadyFlow_RLC_R.json result_steadyFlow_RLC_R.csv ``` -The result will be written to a csv file. +The result will be written to a CSV file. ## Run svZeroDSolver from other programs @@ -228,7 +228,6 @@ There is also a function to retrieve the full result directly based on a given c ``` - ## Configuration svZeroDSolver is configured using either a JSON file or a Python @@ -359,8 +358,24 @@ They can also be specified as a mathematica expression as follow: ``` For an example with a mathematical expression for the boundary condition, see `svZeroDSolver/tests/cases/timeDep_Flow.json`. +## Simulation Outputs + +When running `svZeroDSolver` from the command-line using `svzerodsolver .json .csv` , the outputs will be saved in the specified CSV file. The format of the file depends on the user-specified configuration within the `simulation_parameters` block of the JSON configuration file. + +If `output_variable_based` is set to `true`, the CSV file will contain all the degrees-of-freedom in the simulation. Otherwise, only the flow and pressure at the inlets and outlets of vessels is written. + +The degrees-of-freedom (DOFs) follow the following naming scheme: + +- Flow DOFs are labelled `flow::`. +- Pressure DOFs are labelled `pressure::`. +- Internal DOFs (i.e., variables internal to a block and not connected to upstream/downstream blocks) are labelled `:`. The internal variables for each block are listed in the blocks' [class documentation](https://simvascular.github.io/svZeroDSolver/annotated.html). + +When the outputs are written in the variable-based and vessel-based forms, the user can specify whether they want outputs written for all cardiac cycles or just the last cardiac cycle using the `output_all_cycles` option. By default, only the last cycle is written. This makes the simulation more efficient. + +The number of timesteps between each time the output is written is specified by `output_interval`. By default, output is written at every time step. + -# svZeroDCalibrator - Quick Guide +# svZeroDCalibrator - Quick User Guide svZeroDCalibrator can be used to calibrate cardiovascular 0D models (i.e. infer optimal parameters for the 0D elements) based on a given transient result (i.e. from a From 7360821624a64f56ff729b85ffefa05754a17f9c Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 15:31:09 -0600 Subject: [PATCH 35/41] test square bracket --- docs/pages/add_block.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index b4ed4c67f..d88de4c27 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -97,7 +97,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve \f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. - * The state vector is \f$[P_{in}, Q_{in}, P_{out}, Q_{out}, I_{1}]\f$. + * The state vector is \f$\[P_{in}, Q_{in}, P_{out}, Q_{out}, I_{1}\]\f$. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. From 9e90624a4371fef60a2ffca5850ea18c071f2457 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 15:36:40 -0600 Subject: [PATCH 36/41] remove sq bracket --- docs/pages/add_block.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index d88de4c27..0b2a7203c 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -97,7 +97,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve \f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. - * The state vector is \f$\[P_{in}, Q_{in}, P_{out}, Q_{out}, I_{1}\]\f$. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. From c76a8f51942ff5954d3d2e3689230d950e852675 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 15:42:39 -0600 Subject: [PATCH 37/41] remove eq --- docs/pages/add_block.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 0b2a7203c..60e503400 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,8 +94,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: -\f$ e \frac{dP_{out}}{dt} + f {Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ - * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. From 3f6017f3947c46b5d5aed878ff03588671f71dc4 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 16:21:41 -0600 Subject: [PATCH 38/41] add back eq --- docs/pages/add_block.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 60e503400..51604752a 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,6 +94,10 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: +\f a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ + +\f$ e \frac{dP_{out}}{dt} + f Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ + * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. From cd8e0960dba6a36397bbb94882176b0c27908cdb Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 16:30:02 -0600 Subject: [PATCH 39/41] remove eq1 --- docs/pages/add_block.md | 2 -- docs/pages/main.md | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 51604752a..b505a351e 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,8 +94,6 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: -\f a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ - \f$ e \frac{dP_{out}}{dt} + f Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. diff --git a/docs/pages/main.md b/docs/pages/main.md index f8871d2d1..bbf71fbcc 100644 --- a/docs/pages/main.md +++ b/docs/pages/main.md @@ -360,7 +360,11 @@ For an example with a mathematical expression for the boundary condition, see `s ## Simulation Outputs -When running `svZeroDSolver` from the command-line using `svzerodsolver .json .csv` , the outputs will be saved in the specified CSV file. The format of the file depends on the user-specified configuration within the `simulation_parameters` block of the JSON configuration file. +The siumulation outputs will be saved in the specified CSV file (`.csv`) when running `svZeroDSolver` from the command line as follows: +```bash +svzerodsolver .json .csv +``` +If the name of the CSV file is not specified, the default is `output.csv`. The format of the file depends on the user-specified configuration within the `simulation_parameters` block of the JSON configuration file. If `output_variable_based` is set to `true`, the CSV file will contain all the degrees-of-freedom in the simulation. Otherwise, only the flow and pressure at the inlets and outlets of vessels is written. From e68fd502a9a52e2683db7ba6e398f346cd6cc6b0 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 17:15:54 -0600 Subject: [PATCH 40/41] state vector --- docs/pages/add_block.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index b505a351e..79e42b1fb 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -97,6 +97,7 @@ Below are details on the steps required to implement a new block in svZeroDSolve \f$ e \frac{dP_{out}}{dt} + f Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable. + * The state vector is \f$[P_{in}, Q_{in}, P_{out}, Q_{out}, I_{1}]\f$. * The contributions to the local `F` matrix are `F[0,0] = b`, `F[1,2] = g` and `F[1,4] = h`. * The contributions to the local `E` matrix are `E[0,1] = a` and `E[1,2] = e`. * The contributions to the local `C` vector are `C[0] = c*(dP_in/dt)*Q_in + d` and `C[1] = f*Q_out*Q_out`. From 4ab6371fdcbb0821c72745aaaf548f12a67cb4f9 Mon Sep 17 00:00:00 2001 From: Karthik Menon Date: Tue, 9 Jul 2024 17:24:09 -0600 Subject: [PATCH 41/41] fix eqn 1 --- docs/pages/add_block.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/add_block.md b/docs/pages/add_block.md index 79e42b1fb..cf6d4ca26 100644 --- a/docs/pages/add_block.md +++ b/docs/pages/add_block.md @@ -94,6 +94,8 @@ Below are details on the steps required to implement a new block in svZeroDSolve * Assume a block has the following non-linear governing equations: +\f$ a \frac{dQ_{in}}{dt} + b P_{in} + c \frac{dP_{in}}{dt} Q_{in} + d = 0 \f$ + \f$ e \frac{dP_{out}}{dt} + f Q_{out} Q_{out} + g P_{out} + h I_{1} = 0 \f$ * For this block, \f$P_{in}\f$ and \f$Q_{in}\f$ are the pressure and flow at the inlet respectively, \f$P_{out}\f$ and \f$Q_{out}\f$ are the pressure and flow at the outlet, and \f$I_{1}\f$ is an internal variable.