The formal model underlying the CARA simulation applet assumes a fixed decomposition of the CARA system into a number of communicating modules, which may be regarded as state machines. These modules fall into one of two classes: modules that represent entities (such as the patient or the infusion pump) external to the CARA system, and modules internal to the CARA system.
The modules representing external entities are as follows:
ArterialSource:
Models the arterial line blood pressure sensor.
CuffSource:
Models the cuff blood pressure sensor.
Patient:
Models the patient.
PulseWaveSource:
Models the pulse wave velocity blood pressure sensor.
Pump:
Models the infusion pump.
The modules internal to the CARA system are as follows:
AlarmControl:
Handles the raising and clearing of the various alarms.
ArterialBP:
Handles the obtaining of blood pressure readings from the
arterial line sensor.
Corroborate:
Performs the corroboration function for the blood pressure sensors,
thereby determining which sensor will be used for control.
CuffBP:
Handles the obtaining of blood pressure readings from the
cuff sensor.
Dialog:
Handles caregiver/operator input via buttons and dialog boxes.
Display:
Creates the caregiver/operator information display.
Logging:
Handles the writing of messages to the resuscitation log.
Mode:
Keeps track of the current operating mode and manages transitions
between modes.
PulseWaveBP:
Handles the obtaining of blood pressure readings from the
pulse wave velocity sensor.
PumpControl:
Uses blood pressure and set point information to calculate control
voltage to be supplied to pump. Also monitors for falling
blood pressure.
PumpMonitor:
Tracks the status information supplied by the pump and
estimates flow rate and total infused volume.
Timer:
Provides timing services for other modules.
Each of the above modules is represented by a Java class of the same name. Besides these classes, there are several auxiliary classes whose purpose is to define data types relevant to the model. These are:
FlowRate:
Models a rate of fluid flow.
Pressure:
Models a blood pressure.
Time:
Models a time interval.
Voltage:
Models a voltage.
Volume:
Models a volume of fluid.
Below we give a brief overview of each of the CARA modules listed above.
The arterial line sensor is a "beat-to-beat" blood pressure sensor that provides a direct measurement of blood pressure via a transducer inserted into an artery.
In our model, a blood pressure reading is obtained from the arterial
line when the ArterialSource module invokes the
ArterialBP.setBP() method.
This currently occurs every 15 seconds, though it might be more
realistic if it occurred in synchrony with a simulated patient
heartbeat.
The run() method of the ArterialSource
module contains a loop that obtains the current blood pressure from the
Patient module every 15 seconds.
The pressure supplied by the ArterialSource module
consists of this "true" blood pressure, plus some Gaussian
noise whose standard deviation is specified as a percentage of the
true pressure.
Also incorporated into the ArterialSource module is a
simple two-state Markov failure model.
At any time, the module is either in the "failed" or "not failed"
state.
When the module is in the "not failed" state, it supplies blood
pressure readings periodically as described above.
When the module is in the "failed" state, it does not supply any
readings.
Every 15 seconds, there is a possible transition between states.
Transitions from "not failed" to "failed" occur with a probability
chosen to produce a specified mean time to failure (MTTF).
Transitions from "failed" to "not failed" occur with a probability
chosen to produce a specified mean time to repair (MTTR).
The cuff sensor uses a traditional sphygnomanometric method that involves inflating a cuff to a pressure somewhat over the systolic blood pressure of the patient and observing the waveforms associated with heartbeats as the cuff pressure is slowly reduced. In contrast to the "beat-to-beat" methods, the cuff sensor produces a blood pressure reading only when activated, and substantial time (on the order of one minute) may elapse between the time the reading is requested and the time it is obtained.
In our model, when a cuff pressure is required, the
CuffBP module invokes the
CuffSource.pollBP() method.
In contrast to the ArterialSource and
PulseWaveSource modules, the CuffSource
module does not immediately return a pressure.
Rather, there is a random delay which we model using a
geometric distribution having a specified mean
(the default is 40 seconds).
When the delay time expires, the current blood pressure is
returned via a callback to the CuffBP.setBP()
method.
The run() method of the CuffSource module
contains a loop that obtains the current blood pressure from the
Patient module every 15 seconds.
The pressure returned in response to a pollBP()
request consists of this "true" blood pressure, plus some Gaussian
noise whose standard deviation is specified as a percentage of the
true pressure.
Also incorporated into the CuffSource module is a
simple two-state Markov failure model.
At any time, the module is either in the "failed" or "not failed"
state.
When the module is in the "not failed" state, it responds to
a pollBP() request by returning a blood pressure as
described above.
When the module is in the "failed" state, it does not return
any blood pressure.
Once per second, there is a possible transition between states.
Transitions from "not failed" to "failed" occur with a probability
chosen to produce a specified mean time to failure (MTTF).
Transitions from "failed" to "not failed" occur with a probability
chosen to produce a specified mean time to repair (MTTR).
The Patient module models the response of the
human patient to fluid infusion.
We use a very simple-minded model in which the blood pressure
of the patient is directly proportional to the current volume of blood
in the patient's circulatory system.
The infusion of fluid is modeled by the Pump module
periodically invoking the Patient.addInfusedVolume()
to add a small amount of fluid volume.
The run() method of the Patient module
contains a loop that runs once per second to remove a small
amount of fluid volume, to model bleeding and excretion.
The various blood pressure sensors periodically obtain the
"true" current blood pressure of the patient by calling the
Patient.getBP() method.
(NOTE: This has to be changed, since it violates our restriction
on "no returned values". An easy fix would be to have the
run() loop of the Patient module
"push" the current blood pressure to each of the sensors periodically;
for example on each simulated heartbeat.)
The pulse wave velocity sensor is a "beat-to-beat" blood pressure sensor that infers blood pressure from propagation characteristics of the pressure wave resulting from each heartbeat.
In our model, a blood pressure reading is obtained from the
pulse wave velocity sensor when the PulseWaveBP module
invokes the PulseWaveBP.setBP() method.
This currently occurs every 15 seconds, though it might be more
realistic if it occurred in synchrony with a simulated patient
heartbeat.
The run() method of the PulseWaveSource
contains a loop that obtains the current blood pressure from the
Patient module every 15 seconds.
The pressure supplied by the PulseWaveSourcemodule
consists of this "true" blood pressure, plus some Gaussian
noise whose standard deviation is specified as a percentage of the
true pressure.
Also incorporated into the PulseWaveSource module is a
simple two-state Markov failure model.
At any time, the module is either in the "failed" or "not failed"
state.
When the module is in the "not failed" state, it supplies blood
pressure readings periodically as described above.
When the module is in the "failed" state, it does not supply any
readings.
Every 15 seconds, there is a possible transition between states.
Transitions from "not failed" to "failed" occur with a probability
chosen to produce a specified mean time to failure (MTTF).
Transitions from "failed" to "not failed" occur with a probability
chosen to produce a specified mean time to repair (MTTR).
The Pump module models the M100 infusion pump.
The pump provides logic-level outputs CONT
(continuity), OCC (occlusion), and AirOK
(air in line).
When these lines change state, the pump generates interrupts,
which are simulated by calls to the
PumpMonitor.setCont(),
PumpMonitor.setOcc(), and
PumpMonitor.setAirOK() methods.
There are also analog outputs
EMF (back EMF) and IMP (impedance).
These lines have to be polled by calls to the
pollEMF() and pollIMP() methods,
which usually result in immediate callbacks to the
PumpMonitor.setEMF() and PumpMonitor.setIMP()
methods, respectively.
There is a certain probability that the corresponding callback
will not occur for a particular polling request; this models
a failure of the A/D converter that samples the EMF and IMP lines.
The pump is controlled by an analog control voltage that determines the
infusion rate.
The setting of this control voltage is simulated by calling the
setControlVoltage() method.
In addition, there is a logic-level input to the pump that determines
whether the pump will pay attention to the analog control voltage.
Changes to the state of this line are simulated by calling the
setAnalogControl() method.
The run() method of the pump contains a loop that
runs once per second and sends a certain amount of infused fluid
volume to the Patient module.
In addition, the value of the EMF output is updated
based on the current infusion rate.
This is not currently modeled realistically: in the real M100 pump,
the EMF value varies constantly as the rocker arm contacts the pump
tubing.
This constantly varying signal as to be processed in order to
estimate the infusion rate.
We simply treat the EMF line as giving a direct indication of the
current instantaneous infusion rate.
Also incorporated into the Pump module are
simple two-state Markov failure models associated with each
of the logic-level status lines CONT,
OCC, and AirOK.
At any time, each of these lines is either in the
"failed" or "not failed" state.
In the "not failed" state, the line has the logic value "true",
indicating normal operational status.
In the "failed" state, the line has the logic value "false",
which indicates an error condition.
Once per second, there is a possible transition between states.
Transitions from "not failed" to "failed" occur with a probability
chosen to produce a specified mean time to failure (MTTF).
Transitions from "failed" to "not failed" occur with a probability
chosen to produce a specified mean time to repair (MTTR).
AlarmControl
The AlarmControl module handles the raising and
clearing of the various alarms.
Each alarm is designated either a "level 1" (L1) or "level 2" (L2)
alarm.
As I understand it, L1 alarms are such that they will reset themselves
without user intervention if the alarm condition disappears.
On the other hand, L2 alarms require manual resetting by the user
after the alarm condition is corrected.
Resetting an alarm is achieved by the user pressing an
"Reset Alarm" button that becomes enabled when there is an active
alarm. A reset alarm will be triggered again immediately or
almost immediately if the alarm condition has not been corrected.
AlarmControl maintains a notion of the "current" alarm,
and displays the name of the current alarm in an alarm message field
when any alarm is active. Pressing the alarm reset button resets
only the current alarm; if there are other active alarms, another
one becomes the current alarm.
When there are active alarms, a "Silence Alarm" button is also available. Pressing this button silences the audible alarm signal for all alarms for a period of time that is different for each alarm. When the silence time for an alarm has expired, the audible alarm signal again becomes active for that alarm. Pressing the silence button again will again silence that alarm, but will not affect the remaining silence times associated with other already-silenced alarms.
Separate methods are provided by AlarmControl
for the activating and deactivating of each different alarm.
Most of these methods take a single boolean parameter that
is true if the alarm is to be activated, and false if the
alarm is to be deactivated.
There is also a cancelAllAlarms() method which is
used by Mode to deactivate any active alarms
when leaving auto-control mode.
ArterialBP:
The ArterialBP module handles the obtaining of
blood pressure readings from ArterialSource,
checking them for validity, and delivering them on demand to
Corroborate.
Each time ArterialBP receives a blood pressure
reading from ArterialSource, it checks this reading
for validity and saves it.
When Corroborate requires a blood pressure reading,
it calls ArterialBP.pollBP(), which results in
an immediate callback to Corroborate.setArterialBP().
The ArterialBP.intrT15() method is called by
Timer every 15 seconds, to enable ArterialBP
to detect the lost of BP data from ArterialSource.
If 15 seconds go by without a new reading, then the current reading
is flagged as "stale" and invalidated.
Corroborate:
The Corroborate module is responsible for combining
information from the various blood pressure sources to determine
the control BP supplied to PumpControl.
This function involves the manipulation of the cuff blood pressure
sensor to obtain readings at appropriate times to
corroborate the readings provided by the sources used for control,
to monitor for lost blood pressure sources and issue alarms,
and to cause auto-control mode to be terminated in case no
suitable control source is available.
Corroborate is provided with blood pressure data
via calls to the setArterialBP(),
setPulseWaveBP(), and setCuffBP()
methods. Calls to these occur in response to calls made
by Corroborate to the pollBP()
methods supplied by the ArterialBP,
PulseWaveBP, and CuffBP modules.
Corroborate supplies the current control BP to
PumpControl every 15 seconds.
The control BP is only updated upon receipt of new valid BP
data from the current control source, or upon the occurrence of a
timeout indicating that the current source has been lost.
If no new data arrives from the current BP source, then the
most recent control BP is what is supplied to PumpControl.
If no blood pressure source other than the cuff is available,
Corroborate exhibits some special behavior.
It calls CuffBP.setBPInterval() to initiate periodic
automatic readings of the cuff. The period of cuff readings
ranges from 1 minute apart to 10 minutes apart, depending on
the current blood pressure.
In addition, a failure of the cuff BP source to provide data
in a timely fashion will cause more serious alarms when the
cuff is the only source available than it would otherwise.
The core function of the Corroborate module
is "corroboration", which involves comparing readings obtained
from the so-called "beat-to-beat" BP sensors,
the arterial line sensor and the pulse wave velocity sensor,
to the readings obtained from the cuff sensor.
This corroboration function is modeled as a state machine,
which becomes active every 30 minutes during auto-control
or whenever a change in the available BP sources makes
re-corroboration necessary.
The transitions of this state machine are driven by the responses
from the cuff BP source and from caregiver interaction via
"override dialogs".
A list of the possible corroboration states and their meanings is as follows:
At most one corroboration cycle can be in progress at any given time. A new corroboration cycle becomes active in the following circumstances:
Corroborate also interprets the responses from
override dialogs issued for the user.
An override dialog is issued when a beat-to-beat source being
used for control does not corroborate with, or match,
the current cuff reading.
The user is asked to respond "YES" or "NO" as to whether the
current control source should be used anyway.
In case of a "YES" response, Corroborate checks
to make sure that the selected source is still valid
(there are scenarios under which the source can become invalid
while the dialog is pending), then remembers that the current
source was selected by override.
In case of a "NO" response, Corroborate either
terminates auto-control or tries to corroborate a lower-priority
source, depending on the circumstances.
CuffBP:
The CuffBP module handles the obtaining of
blood pressure readings from CuffSource
and delivering them on demand to Corroborate
Corroborate requests a reading by calling the
pollBP() method provided by CuffBP,
which responds by calling the setCuffBP)()
method provided by Corroborate.
CuffBP in turn requests a reading
from CuffSource by calling its
pollBP() method, and CuffSource
responds by calling the setBP() method
of CuffBP. The CuffBP
module checks each reading it obtains for validity
before passing it along to Corroborate.
The blood pressure cuff differs from the "beat-to-beat"
sensors in that it may take much longer (a minute or more)
to deliver a reading in response to a request, and in some
cases it might not deliver a reading at all.
To bound the time that it might take to deliver a response
when a cuff reading is requested by Corroborate,
the CuffBP module sets a timer when a reading
is first requested. If no response is forthcoming
by the time the timer expires, an invalid blood pressure
is given as a response to Corroborate.
In the current version of the model, the cuff timeout
is set to 55 seconds.
CuffBP also handles the taking of periodic
readings that are required when the cuff is the only BP source
available for controlling the infusion pump.
Corroborate sets the time interval between
periodic readings by calling the setBPInterval()
method of CuffBP.
Dialog:
The Dialog module handles user input via buttons
and dialog boxes. The following buttons are independent of
any dialog boxes:
There are two kinds of dialogs:
The override dialog contains a "YES" and a "NO" button. The change set point dialog contains "OK", "Cancel", "Default", "Up", and "Down" buttons, plus a text field indicating the selected set point.
There are somewhat complicated requirements on which things
hide or disable which other things on the display.
Each time the state of anything changes, Dialog
recomputes the display appropriately.
Display:
The Display module handles the display of various
kinds of indicators and data.
It provides various methods to the other modules by which the state
of the items to be displayed can be changed when necessary.
Logging:
The Logging module handles the writing of
messages to the resuscitation log.
A number of methods are provided for making log entries
in situations. These methods are invoked by other modules
when necessary.
Mode:
The Mode module keeps track of the current operating
mode and manages transitions between modes.
The possible modes are "waiting", which is the mode just after
the CARA system has been initialized but before the pump has been
detected for the first time, "manual", in which the pump ignores
the analog control voltage and pumps at its hardware setting
of 0.2 lph, and "auto-control", in which the pump is under active
control of the CARA software.
A transition from "waiting" to "manual" mode occurs the first
time the pump is detected. Mode is informed
about the status of the pump via calls made by
PumpMonitor to the setPumpStatus()
method.
A transition from "manual" to "auto-control" mode occurs
when the caregiver presses the "start auto-control" button,
resulting in a call by the Dialog module
to the startAutoControl() method.
One way a transition from "auto-control" to manual mode can occur is
when the caregiver issues a YES response to a "terminate auto-control"
dialog, which results in a call by the Dialog
module to the stopAutoControl() method.
Another way auto-control can be terminated is when
a call by PumpMonitor to the setPumpStatus()
method indicates that the pump is no longer "OK".
A third way auto-control can be terminated is when
a call by Corroborate to the setBPStatus()
method indicates that there is no longer any valid control BP.
When auto-control initiates or terminates,
Mode calls methods of various other modules,
including Corroborate to orchestrate the change.
PulseWaveBP:
The PulseWaveBP module handles the obtaining of
blood pressure readings from PulseWaveSource,
checking them for validity, and delivering them on demand to
Corroborate
Each time PulseWaveBP receives a blood pressure
reading from PulseWaveSource, it checks this
reading for validity and saves it.
When Corroborate requires a blood pressure reading,
it calls PulseWaveBP.pollBP(), which results in
an immediate callback to Corroborate.setPulseWaveBP)().
method provided by Corroborate.
The PulseWaveBP.intrT15() method is called by
Timer every 15 seconds, to enable PulseWaveBP
to detect the lost of BP data from PulseWaveSource.
If 15 seconds go by without a new reading, then the current reading
is flagged as "stale" and invalidated.
PumpControl:
The PumpControl module uses blood pressure and
set point information to calculate the control voltage to be
supplied to the pump. It also monitors for falling blood pressure.
The PumpControl module is informed by Mode
via the startAutoControl() method as to when
auto-control begins.
The set point is initialized to the default of 70mmHg, and
the pump control voltage is set so as to yield the initial
flow rate of 4 lph.
Information about the controlling blood pressure updated when the
Corroborate module calls the setControlBP()
method supplied by PumpControl.
Each time this occurs, PumpControl calculates a
new control voltage and supplies it to Pump by calling
the Pump.setControlVoltage() method.
Periodically (every 15 seconds) PumpControl checks
whether the set point has been reached and whether it appears that
the blood pressure is falling.
If the caregiver modifies the set point via the change set point
dialog, the Dialog module supplies the new set point
to PumpControl via the setSetPoint()
method.
When auto-control terminates, Mode uses the
stopAutoControl() method provided by Pump
to convey this information.
We currently use a very simplistic control algorithm that applies the maximum control voltage to the pump when the control blood pressure is below 60mmHg, applies the "keep vein open" (KVO) control voltage to the pump when the control blood pressure is at or above the set point, and which varies the control voltage linearly with the control blood pressure when the latter is between 60mmHg and the set point.
PumpMonitor:
The PumpMonitor module tracks the status information
supplied by the pump. It also estimates the infusion rate,
which it integrates over time to determine the total infused
fluid volume. The PumpControl module uses the
setTimeAtSetPoint() to keep the PumpMonitor
informed about how long it has been since the desired blood pressure
set point was attained. After the set point has been held for
ten minutes, the PumpMonitor calculates a baseline
"steady-state" infusion rate and monitors for increases in the
infusion rate that signficantly exceed this level.
The
setAirOK(),
setCONT(), and
setOCC()
methods provided by PumpMonitor are called by the
Pump module to simulate interrupts that occur when
the corresponding logic outputs of the pump change their state.
The
setEMF() and
setIMP()
methods are called by the Pump module in response
to calls made periodically (once every five seconds) by
PumpMonitor to the Pump.pollEMF() and
Pump.pollIMP() methods.
The information obtained in this way is combined to infer some
derived status information, such as whether the pump is plugged in
(we currently equate this with the CONT line being
at a logic "true" level) whether the pump is "OK", and to estimate
the current infusion rate.
Each time there is a possible change in pump status,
the Mode module is informed by a call to the
Mode.setPumpStatus() method.
Timer:
The Timer module provides timing services required
by the other modules. We decided not to have each of the individual
modules perform their own timing, because the overal model was
simpler and easier to understand if we localize all timing
into a single module.
There are two types of timing services provided by Timer.
One kind of service is settable "countdown" timers which other
modules use to implement timeouts. These timers are set by
supplying Timer with the amount of time to wait.
When this amount of time expires, Timer performs a
callback to the appropriate method of the invoking module.
The other type of timing service provided by Timer
is periodic interrupts at various frequencies.
Currently, the other modules require interrupts at one-second,
five-second, fifteen-second, and one-minute intervals.
Not all modules require all frequencies, and some modules do
not require any.
Timer itself contains a run() method
with a loop that executes once per second. Counters are used
to generate all the other timings from this basic one-second
period.
BACK to Cara Infusion Pump Project.