Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
engine
engine
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 5
    • Issues 5
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge Requests 0
    • Merge Requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Pulse Physiology Suite
  • engineengine
  • Wiki
  • Creating an Action

Last edited by Aaron Bray Oct 25, 2018
Page history

Creating an Action

Actions are the method used to get instructions into Pulse and direct a physiology model to change. The physiology model is dependent on the model implementation, while an action is generic and usually associated with a general procedure done to the patient. An engine is free to interpret and modify its models according to the intent of the action and the data it provides.

In this post, we will discuss how to create the infrastructure to design and implement a new action in the Common Data Model so it can be included in a scenario and used by Pulse integrators. We will also look at how a physiology modeler can use the action in their methodology implementation.

Create the Common Data Model Action Proto buffer

You will need to define the data structure for your action. The data structures are defined in google protocol buffers. The actions structures are organized by the object the action takes place on. The following files are where we define actions associated with various objects.

  • Anesthesia Machine Actions
  • Environment Actions
  • Inhaler Actions
  • Patient Actions

For this discussion, we will look at adding a patient action, but the same principles can be applied to other object related actions.

Open this PatientActions.proto file in your favorite editor and add the following. (Typically we strive to keep the actions in alphabetical order) Note the naming convention is to end each data structure with Data

message MyNewActionData
{
// Set the first field to be the encapsulation of the base class
// protobuf does support inheritance..
  PatientActionData  PatientAction = 1;
// Next add the properties associated with your action definition
  Scalar0To1Data     Severity      = 2;
}

Next, add your new action object to the list of available patient actions.

message AnyPatientActionData
{
  oneof Action
  {
    PatientAssessmentRequestData            Assessment                      = 1;
    AcuteStressData                         AcuteStress                     = 2;
    AirwayObstructionData                   AirwayObstruction               = 3;
    ApneaData                               Apnea                           = 4;
    AsthmaAttackData                        AsthmaAttack                    = 5;
    BrainInjuryData                         BrainInjury                     = 6;
    BronchoconstrictionData                 Bronchoconstriction             = 7;
    CardiacArrestData                       CardiacArrest                   = 8;
    ChestCompressionForceData               ChestCompressionForce           = 9;
    ChestCompressionForceScaleData          ChestCompressionForceScale      = 10;
    ChestOcclusiveDressingData              ChestOcclusiveDressing          = 11;
    ConsciousRespirationData                ConsciousRespiration            = 12;
    ConsumeNutrientsData                    ConsumeNutrients                = 13;
    ExerciseData                            Exercise                        = 14;
    HemorrhageData                          Hemorrhage                      = 15;
    IntubationData                          Intubation                      = 16;
    MechanicalVentilationData               MechanicalVentilation           = 17;
    NeedleDecompressionData                 NeedleDecompression             = 18;
    PericardialEffusionData                 PericardialEffusion             = 19;
    TensionPneumothoraxData                 TensionPneumothorax             = 20;
    SubstanceBolusData                      SubstanceBolus                  = 21;
    SubstanceCompoundInfusionData           SubstanceCompoundInfusion       = 22;
    SubstanceInfusionData                   SubstanceInfusion               = 23;
    UrinateData                             Urinate                         = 24;

    // Set this property to a large number, if you do submit this new action
    // As a merge request into the master branch, we will assign it a new id
    // And properly integrate it into the system
    MyNewActionData                         MyNewAction                     = 1001;
  }
}

With our changes in place, we will need to generate the appropriate bindings classes. From the <path/to/pulse/build>/install/bin directory run the following command:

cmake -DTYPE:STRING=protoc -P run.cmake 
# There is also .bat and .sh scripts to make this a little easier
# On windows, you can type
run protoc
# or, on Linux
./run.sh protoc

Create the Common Data Model C++ Action

Now we are ready to create the C++ files specific to this action class. The easiest thing to do is to create copies of engine/cdm/cpp/patient/actions/SEAcuteStress.h and engine/cdm/cpp/patient/actions/SEAcuteStress.cpp files and rename them to SEyour_action_name.h/.cpp Then replace the string AcuteStress with the name of your action, then create the properties to match your action protobuf structure. Note the naming convention is to start each file and class name with SE

Add the Serialization Support for your Action Class

All CDM classes support serialization to and from a binding class. You will need to update the appropriate binding class for your action type.

Headers

  • Anesthesia Machine Action
  • Environment Action
  • Inhaler Action
  • Patient Action

In our example, for adding a patient action we will add something like the following code to the appropriate header

// Forward declare
CDM_BIND_DECL2(MyNewAction)
// Add the following methods to the class
static void Load(const cdm::MyNewActionData& src, SEMyNewAction& dst);
static cdm::MyNewActionData* Unload(const SEMyNewAction& src);
static void Serialize(const cdm::MyNewActionData& src, SEMyNewAction& dst);
static void Serialize(const SEMyNewAction& src, cdm::MyNewActionData& dst);
static void Copy(const SEMyNewAction& src, SEMyNewAction& dst);

Code

  • Anesthesia Machine Action
  • Environment Action
  • Inhaler Action
  • Patient Action

Implement the newly added methods. I suggest you just copy another action class method set and use the correct class names.

The Serialization class is also responsible for translating the action object to and from the AnyAction object. You will need to update the following methods to ensure generic action support.


// Update the Load method
SEPatientAction* PBPatientAction::Load(const cdm::AnyPatientActionData& any, SESubstanceManager& subMgr)
{
  switch (any.Action_case())
  {
    // Note we will want this to be alphabetical in the switch
    case cdm::AnyPatientActionData::ActionCase::kMyNewAction:
    {
      SEMyNewAction* a = new SEMyNewAction();
      SEMyNewAction::Load(any.mynewaction(), *a);
      return a;
    }
    ...

// Update the Unload Method
// Note we will want the cast/if block to be alphabetical order
cdm::AnyPatientActionData* PBPatientAction::Unload(const SEPatientAction& action)
{
  cdm::AnyPatientActionData* any = new cdm::AnyPatientActionData();
  const SEMyNewAction* a= dynamic_cast<const SEMyNewAction*>(&action);
  if (a != nullptr)
  {
    any->set_allocated_mynewaction(SEMyNewAction::Unload(*a));
    return any;
  }
  ...

Add the Common Data Model Action to the Action Manager

The action manager contains different classes to organize the collection of actions associated with the object that the actions takes place on. The following files are those action collection managers:

  • Anesthesia Machine Action Collection
  • Environment Action Collection
  • Inhaler Action Collection
  • Patient Action Collection

Since we are adding a patient action, we will edit the SEPatientActionCollection.h/.cpp files. Note this example is for one instance of the action for the entire engine, there are patterns in this file if want to, for example, have an instance of the action be associated with a compartment or some other way to handle multiple instances of the action type. Also note the example of the Tension Pneumothorax actions. The one action can be of 2 different types, and associated with the two lungs. Hence the interface was designed to check if any pneumothorax actions are present, then method for each combination of type/location. This exposure is up to you and what makes the most sense for your action to present to model developers. In this example we are taking a simple approach to adding this new action. Just like we used the SEAcuteStress action files as a basis, you can go through the SEPatientActionCollection files and look for SEAcuteStress and copy the patterns in the file. (Again, we organize instructions alphabetically)

In the header file you will need to :

// Include the header to your action file
#include "patient/actions/SEMyNewAction.h"

// Create methods to expose your new action
bool HasMyNewAction() const;
SEMyNewAction* GetMyNewAction() const;
void RemoveMyNewAction();

// Add the member variable
SEMyNewAction*          m_MyNewAction;

In the cpp file you will need to :

// initialize the member variable in the constructor
m_AcuteStress = nullptr;
// Support the clearing the action
RemoveMyNewAction();
// Support serialization of the action
if (src.HasMyNewAction())
    dst.mutable_anyaction()->AddAllocated(SEAction::Unload(*src.m_MyNewAction));
// Support Processing the action
// Notice how we make a copy of any incoming action,
// so for a user to change an action they must call Process
const SEMyNewAction* mine = dynamic_cast<const SEMyNewAction*>(&action);
if (mine != nullptr)
{
  if (m_MyNewAction == nullptr)
    m_MyNewAction = new SEMyNewAction();
  any.set_allocated_mynewaction(SEMyNewAction::Unload(*mine));
  SEMyNewAction::Load(any.mynewaction(), *m_MyNewAction);
  if (!m_MyNewAction->IsActive())
    RemoveMyNewAction();
  return true;
}
// Fill out your exposed methods
bool SEPatientActionCollection::HasMyNewAction() const
{
  return m_MyNewAction == nullptr ? false : m_MyNewAction->IsActive();
}
SEMyNewAction* SEPatientActionCollection::GetMyNewAction() const
{
  return m_MyNewAction;
}
void SEPatientActionCollection::RemoveMyNewAction()
{
  SAFE_DELETE(m_MyNewAction);
}  

// Go to the appropriate action manager and add logic to the GetActiveActions method
void SEPatientActionCollection::GetActiveActions(std::vector<const SEAction*>& actions) const
{
  if(HasMyNewAction())
    actions.push_back(GetMyNewAction());
}

With this file complete, the action has been added to the Common Data Model and the engine is able to accept these actions and the engine may now implement logic based on this action.

Adding Support for an Action to the Engine

You will need to identify the system(s) you wish to check for this action and put the following code inside a method that gets called during PreProcess

if (m_data.GetActions().GetPatientActions().HasMyNewAction())
{
  SEMyNewAction* s = m_data.GetActions().GetPatientActions().GetMyNewAction();
  // Pull data from the action
  double severity = s->GetSeverity().GetValue();
  // Generate a multiplier based on the action data
  multiplier = GeneralMath::LinearInterpolator(0, 1, 0, 30, severity);
  // Apply the multiplier to something in the circuit
  double new_next = CircuitPath->GetNextResistance(FlowResistanceUnit::mmHg_s_Per_mL) * multiplier;
  CircuitPath->GetNextResistance().SetValue(new_next, FlowResistanceUnit::mmHg_s_Per_mL);
}

Note that this code will get executed every time-step. You should take into consideration that circuit elements (i.e, resistances, compliances, etc.) store three temporal parameters - the “next” value that will be used in the upcoming circuit calculation for the state of the next time-step, the “current” value that is value used for the last circuit calculation, and the “baseline” value that is the unmodified original homeostatic value. At the beginning of each time-step, the next value is replaced with the baseline value.

A model should create a multiplier value based on the action data. That multiplier should then be applied to the next value each time-step (The next value may or may not have already been already modified by a different action). Actions should not modify baseline values or current values, since they are merely used as input for transient calculations. Typically speaking, an action that remains active will call the same code to modify next values every time-step.

Clone repository
  • Adding a Parameter
  • Contributing to Pulse
  • Creating a Scenario
  • Creating a Unit Test
  • Creating a condition
  • Creating an Action
  • Extending a System
  • Generating Documentation
  • Modifying a System Circuit
  • Pulse Scenario Driver
  • Test Suite
  • Updating Baselines
  • Updating Documentation
  • Using C#
  • Using CLion
View All Pages