Windkessel model
Copyright (C) 2022 Miodrag Bolic
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details <https://www.gnu.org/licenses/>.
This code was developed by Miodrag Bolic for the book PERVASIVE CARDIAC AND RESPIRATORY MONITORING DEVICES.
% Changing the path from main_folder to a particular chapter
main_path=fileparts(which('Main_Content.mlx'));
%addpath(append(main_path,'/Chapter2'))
cd (append(main_path,'/Chapter4/Windkessel'))
addpath(append(main_path,'/Service'))
SAVE_FLAG=0; % saving the figures in a file
Introduction
The Windkessel model
The Windkessel model is an interpretative model that allows us to achieve the following:
Given the aortic flow and the parameters of the model, we can generate aortic and peripheral pressure signals.
Given the aortic flow and the aortic pressure, we can estimate the values of parameters such as arterial compliance that have physiological meaning and can be interpreted.
The pressure plays the role of the voltage at different points and the blood flow of the current.
Parameters include:
- Z0 is the characteristic impedance of the artery
- R is the peripheral resistance
- CA is the arterial compliance which is the change in arterial blood volume (due to a given change in arterial blood pressure,
- L is the arterial inertance and it is defined as the change in pressure in the arterial segment versus the acceleration of the blood.
In this section Windkessel model will be presented.
Forward modeling
inp1(:,2)=TFP(:,2); % flow
param=[0.039 1.95 0.71 0.011; 0.043 1.5 0.95 0.015; 0.05 0.7 1.4 0.02];
cases=['hypotensive', 'normotensive', 'hypertensive'];
L = sdo.getParameterFromModel(model_name, 'L');
sdo.setValueInModel(model_name,L);
Z0 = sdo.getParameterFromModel(model_name, 'Z0');
sdo.setValueInModel(model_name,Z0);
R = sdo.getParameterFromModel(model_name, 'R');
sdo.setValueInModel(model_name,R);
CA = sdo.getParameterFromModel(model_name, 'CA');
sdo.setValueInModel(model_name,CA);
% Collect these parameters into a vector.
simOut = sim(model_name, 'CaptureErrors', 'on');
%plot(simOut.simout.Time, simOut.simout.Data)
plot(simOut.simout.Data(14000:14970))
ylabel("Aortic pressure (mmHg)")
legend({'Hypotensive', 'Normal', 'Hypertensive'})
title("Aortic pressure generated using Windkessel model")
annonation_save('b)',"Fig4.4b.jpg", SAVE_FLAG);
plot(inp1(14000:14950,2))
ylabel("Aortic flow (ml/s)")
title("Aortic flow at the input of Windkessel model")
annonation_save('a)',"Fig4.4a.jpg", SAVE_FLAG);
Sensitivity analysis and fitting the model
In this section, we will load data that correspond to the flow and pressure of a hypotensive subject with low blood pressure. Then we will perform the following steps:
- Sensitivity analysis to determine the initial values of the parameters for the estimation
- Selection of parameters that need to be optimized.
- Fitting the parameters of the model.
- Determining the accuracy of the fitting since we already know the parameters for the hypotensive subject.
Sensitivity analysis
% Modeling Case 1 - hypotensive subject
out(:,2)=TFP(:,3); % we can also ake the BP signal from the hypotensive subject above
[EvalResult, ParamValues, p] = sensitivityEvaluationWK(out)
ps =
ParameterSpace with properties:
ParameterNames: {'Z0' 'CA' 'R' 'L'}
ParameterDistributions: [1×4 prob.UniformDistribution]
RankCorrelation: []
Options: [1×1 sdo.SampleOptions]
Notes: []
EvalResult = 100×1 table
| CustomReq |
---|
1 | 406.2856 |
---|
2 | 1.5245e+04 |
---|
3 | 3.5740e+03 |
---|
4 | 1.1509e+03 |
---|
5 | 3.1156e+03 |
---|
6 | 7.3713e+03 |
---|
7 | 103.7613 |
---|
8 | 1.2459e+03 |
---|
9 | 1.6702e+04 |
---|
10 | 631.7304 |
---|
11 | 3.4012e+03 |
---|
12 | 1.4067e+04 |
---|
13 | 4.1379e+03 |
---|
14 | 4.3811e+03 |
---|
⋮ |
---|
ParamValues = 100×4 table
| Z0 | CA | R | L |
---|
1 | 0.0365 | 1.8888 | 0.5146 | 0.0089 |
---|
2 | 0.0484 | 1.5390 | 1.9386 | 0.0079 |
---|
3 | 0.0533 | 2.2124 | 0.1244 | 0.0071 |
---|
4 | 0.0396 | 0.9233 | 0.3962 | 0.0077 |
---|
5 | 0.0496 | 0.7046 | 0.1711 | 0.0010 |
---|
6 | 0.0124 | 2.1282 | 1.5687 | 0.0031 |
---|
7 | 0.0206 | 2.0947 | 0.8075 | 0.0045 |
---|
8 | 0.0309 | 0.8024 | 1.0483 | 0.0053 |
---|
9 | 0.0135 | 0.5800 | 1.9814 | 0.0030 |
---|
10 | 0.0746 | 1.3362 | 0.4713 | 0.0097 |
---|
11 | 0.0256 | 1.5545 | 1.2854 | 0.0012 |
---|
12 | 0.0696 | 0.5816 | 1.8808 | 0.0096 |
---|
13 | 0.0781 | 1.7203 | 1.3466 | 0.0064 |
---|
14 | 0.0765 | 0.5029 | 1.3375 | 0.0013 |
---|
⋮ |
---|
p(1,1) =
Name: 'Z0'
Value: 0.0500
Minimum: 0.0100
Maximum: 0.1000
Free: 1
Scale: 0.0625
Info: [1×1 struct]
p(2,1) =
Name: 'CA'
Value: 2
Minimum: 0.5000
Maximum: 3
Free: 1
Scale: 1
Info: [1×1 struct]
p(3,1) =
Name: 'R'
Value: 0.6000
Minimum: 0.1000
Maximum: 2
Free: 1
Scale: 2
Info: [1×1 struct]
p(4,1) =
Name: 'L'
Value: 0.0050
Minimum: 5.0000e-04
Maximum: 0.0100
Free: 1
Scale: 0.0312
Info: [1×1 struct]
4x1 param.Continuous
lists of methods, superclasses
sdo.scatterPlot(ParamValues,EvalResult)
ylabel('Mean square error (mmHg^2)')
title("The scatter plot of mean square error vs. Windkessel parameters")
exportgraphics(gcf,"Fig4.5a.jpg", 'Resolution',600)
opts = sdo.AnalyzeOptions;
opts.Method = 'Correlation';
sensitivities = sdo.analyze(ParamValues,EvalResult, opts);
disp(sensitivities)
CustomReq
__________
Z0 -0.0040993
CA -0.11084
R 0.81746
L -0.0018477
Selection of parameters
[fval, idx_min] = min(EvalResult.CustomReq);
L.Value = ParamValues.L(idx_min);
Z0.Value = ParamValues.Z0(idx_min);
R.Value = ParamValues.R(idx_min);
CA.Value = ParamValues.CA(idx_min);
Optimization
% Selection of parameters from the sensitivity analysis to be used in the
Exp = sdo.Experiment(model_name);
Exp = setEstimatedValues(Exp, v); % use vector of parameters/states
Simulator = createSimulator(Exp);
% Simulator = sim(Simulator);
% % Retrieve logged signal data.
% SimLog = find(Simulator.LoggedData,get_param('WK','SignalLoggingName'));
% Sig_Log = find(SimLog,'Sig');
opts = sdo.OptimizeOptions;
estFcn = @(v) WK_OptEvalFcn(v, Simulator, Exp,out);
vOpt = sdo.optimize(estFcn, v, opts);
Optimization started 14-Feb-2023 20:04:11
max First-order
Iter F-count f(x) constraint Step-size optimality
0 9 46.7518 0
1 21 26.3315 0 0.241 1.42e+03
2 32 19.0406 0 0.411 863
3 46 18.648 0 0.155 579
4 53 10.82 0 0.569 340
5 59 7.295 0 0.447 110
6 70 4.43566 0 0.363 363
7 78 2.69023 0 0.224 30.6
8 86 1.11433 0 0.19 35.7
9 94 0.657785 0 0.0668 7.39
10 105 0.656918 0 0.0171 4.77
11 113 0.653798 0 0.00317 0.756
12 121 0.652268 0 0.00599 1.84
13 122 0.652268 0 0.000882 1.84
Local minimum possible. Constraints satisfied.
fmincon stopped because the size of the current step is less than
the value of the step size tolerance and constraints are
satisfied to within the value of the constraint tolerance.
disp(vOpt)
(1,1) =
Name: 'L'
Value: 0.0138
Minimum: 0.0046
Maximum: 0.0138
Free: 1
Scale: 0.0156
Info: [1×1 struct]
(1,2) =
Name: 'Z0'
Value: 0.0386
Minimum: 0.0209
Maximum: 0.0628
Free: 1
Scale: 0.0625
Info: [1×1 struct]
(1,3) =
Name: 'R'
Value: 0.7127
Minimum: 0.3234
Maximum: 0.9702
Free: 1
Scale: 1
Info: [1×1 struct]
(1,4) =
Name: 'CA'
Value: 2.0364
Minimum: 0.8742
Maximum: 2.6226
Free: 1
Scale: 2
Info: [1×1 struct]
1x4 param.Continuous
lists of methods, superclasses
%% Visualizing Result of Optimization
% Obtain the model response after estimation. Search for the
% model_residual signal in the logged simulation data.
Exp = setEstimatedValues(Exp, vOpt); % use vector of parameters/states
Simulator = createSimulator(Exp);
Simulator = sim(Simulator);
plot(out(start_pulse_ind:end_pulse_ind,1), out(start_pulse_ind:end_pulse_ind,2))
plot(Simulator.LoggedData.simout.Time(start_pulse_ind:end_pulse_ind), Simulator.LoggedData.simout.Data(start_pulse_ind:end_pulse_ind))
ylabel('Blood pressure (mmHg)');
legend({'Original signal', 'Output of the model'},'Location','Best')
title("Aortic pressure pulse")
annonation_save('b)',"Fig4.5b.jpg", SAVE_FLAG);
function Vals = WK_OptEvalFcn(v,Simulator,Exp,out)
% Function called at each iteration of the evaluation problem.
% The function is called with a set of parameter values, P, and returns
% the evaluated cost, Vals.
% See the sdoExampleCostFunction function and sdo.evaluate for a more
% detailed description of the function signature.
Exp = setEstimatedValues(Exp, v); % use vector of parameters/states
Simulator = createSimulator(Exp,Simulator);
Simulator = sim(Simulator);
% Retrieve logged signal data.
% SimLog = find(Simulator.LoggedData,get_param('WK','SignalLoggingName'));
% Sig_Log = find(SimLog,'PaSig');
PaSig=Simulator.LoggedData.simout.Data;
% Evaluate the design requirements.
Vals.F = mean((PaSig(start_pulse_ind:end_pulse_ind)-out(start_pulse_ind:end_pulse_ind,2)).^2);