Back to trackLesson 3 of 7

Vital Signs 3: Simplifying With Parameters

Problem statement

Lessons 1–2 built a vitals model, but a real “vitals” reading is more than heart rate and blood pressure — temperature (at least) belongs in there too. Your real-world source of data likely has those readings and more, so you’ll probably want a library object to model vitals that can be used as a component in multiple simulations.

Lesson 2’s model also has a quiet, unresolved problem (carried over from Lesson 1’s model): NormalReading and AnomalousReading are the same distribution, copy-pasted, with one number changed. Add a field to both and you’re now keeping two copies in sync by hand.

Why split heart rate out?

The obvious move is one MultivariateNormal — heart rate, systolic, diastolic, temperature — with <declare> names for each mean. It doesn’t work well because a <declare> substitutes a value for a parameter’s entire attribute, not just a piece of one. means="$hrMean,120,80,98.6" isn’t a thing — $hrMean has to be the whole value of some attribute, not one number spliced into a larger list.

So the model has to be shaped around what actually needs to vary independently. Systolic and diastolic stay together; they’re correlated so they stay one MultivariateNormal with a bpMeans declare covering the pair. Heart rate and temperature each get pulled out as their own Normal, each with its own declare, so that each of them can be independently overridden. That’s a good design rule to keep in mind: group what moves together, split what has to move alone.

We could make the covariance matrix for blood pressure a parameter as well, if we wanted to. For this lesson, we’ve kept that a constant for simplicity.

The model

vitals-lib.xml — the reusable business object:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <root node="Vitals"/>
  <definitions>
    <declare name="hrMean" value="74"/>
    <declare name="hrStdDev" value="8"/>
    <declare name="bpMeans" value="120,80"/>
    <declare name="tempMean" value="98.6"/>
    <declare name="tempStdDev" value="0.5"/>
  </definitions>
  <nodes>
    <Normal name="HR" mean="$hrMean" stddev="$hrStdDev"/>
    <MultivariateNormal name="BP" means="$bpMeans" covariance="144,67.2; 67.2,64"/>
    <Normal name="Temp" mean="$tempMean" stddev="$tempStdDev"/>
    <Append name="Vitals">
      <input name="HR"/>
      <input name="BP"/>
      <input name="Temp"/>
    </Append>
  </nodes>
</dataset>

Updating Lesson 2’s Perturb model, we can now simply import vitals-lib.xml twice, overriding the heart rate mean paramter for the anomaly case, instead of hand-duplicating the structure:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <root node="Vitals"/>
  <nodes>
    <Perturb name="Vitals" repeat="true">
      <input name="NormalVitals" role="normal"/>
      <input name="AnomalousVitals" role="disturbance"/>
      <input name="WaitGen" role="wait"/>
      <input name="DurGen" role="duration"/>
    </Perturb>
    <Import name="NormalVitals" file="vitals-lib.xml"/>
    <Import name="AnomalousVitals" file="vitals-lib.xml" hrMean="195"/>
    <Constant name="WaitGen" value="4995" type="double"/>
    <Constant name="DurGen" value="5" type="double"/>
  </nodes>
</dataset>

This makes reuse both simpler and more powerful: being able to duplicate a single structure multiple times, in multiple models, with just a single Import line each time. It’s the difference between editing a number in one file versus having to find and update every place you copy-pasted it. If you added a fifth vital sign tomorrow (O2 level, for example), it exists in exactly one place and all of your models automatically pick up the change.

We have kept blood pressure and temperature separate from heart rate changes to simplify these examples, but if you want to vary those as well, all you would need to do would be to include additional parameters in the appropriate Import nodes.