Skip to content

Commit

Permalink
add <infectionOrigin> to the DecisionTree (#402)
Browse files Browse the repository at this point in the history
* add <infectionOrigin> with <imported>, <introduced> and <indigenous> branches to the DecisionTree

* make the detection of imported cases dynamic
  • Loading branch information
acavelan authored Jan 16, 2025
1 parent 4c2308f commit 60ae7d4
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 76 deletions.
53 changes: 53 additions & 0 deletions model/Clinical/CMDecisionTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,44 @@ class CMDTCaseType : public CMDecisionTree {
const CMDecisionTree& secondLine;
};

/**
* Should the patient recieve a different treatment based on its infection type?
*/
class CMDTInfectionOrigin : public CMDecisionTree {
public:
static const CMDecisionTree& create( const ::scnXml::DTInfectionOrigin& node, bool isUC );

protected:
virtual bool operator==( const CMDecisionTree& that ) const{
if( this == &that ) return true; // short cut: same object thus equivalent
const CMDTInfectionOrigin* p = dynamic_cast<const CMDTInfectionOrigin*>( &that );
if( p == 0 ) return false; // different type of node
if( imported != p->imported ) return false;
if( introduced != p->introduced ) return false;
if( indigenous != p->indigenous ) return false;
return true; // no tests failed; must be the same
}

virtual CMDTOut exec( CMHostData hostData ) const{
WithinHost::InfectionOrigin InfectionOrigin = hostData.withinHost().getInfectionOrigin();
if(InfectionOrigin == WithinHost::InfectionOrigin::Imported)
return imported.exec( hostData );
else if (InfectionOrigin == WithinHost::InfectionOrigin::Introduced)
return introduced.exec( hostData );
else
return indigenous.exec( hostData );
}

private:
CMDTInfectionOrigin( const CMDecisionTree& imported,
const CMDecisionTree& introduced,
const CMDecisionTree& indigenous ) :
imported(imported), introduced(introduced), indigenous(indigenous)
{}

const CMDecisionTree& imported, &introduced, &indigenous;
};

/**
* Use a diagnostic, and call another decision tree based on the outcome.
*/
Expand Down Expand Up @@ -603,6 +641,7 @@ const CMDecisionTree& CMDecisionTree::create( const scnXml::DecisionTree& node,
if( node.getMultiple().present() ) return CMDTMultiple::create( node.getMultiple().get(), isUC);
// branching nodes
if( node.getCaseType().present() ) return CMDTCaseType::create( node.getCaseType().get(), isUC);
if( node.getInfectionOrigin().present() ) return CMDTInfectionOrigin::create( node.getInfectionOrigin().get(), isUC);
if( node.getDiagnostic().present() ) return CMDTDiagnostic::create( node.getDiagnostic().get(), isUC);
if( node.getUncomplicated().present() ) return CMDTUncomplicated::create( node.getUncomplicated().get(), isUC);
if( node.getSevere().present() ) return CMDTSevere::create( node.getSevere().get(), true);
Expand All @@ -628,6 +667,9 @@ const CMDecisionTree& CMDTMultiple::create( const scnXml::DTMultiple& node, bool
for( const scnXml::DTCaseType& sn : node.getCaseType() ){
self->children.push_back( &CMDTCaseType::create(sn, isUC) );
}
for( const scnXml::DTInfectionOrigin& sn : node.getInfectionOrigin() ){
self->children.push_back( &CMDTInfectionOrigin::create(sn, isUC) );
}
for( const scnXml::DTDiagnostic& sn : node.getDiagnostic() ){
self->children.push_back( &CMDTDiagnostic::create(sn, isUC) );
}
Expand Down Expand Up @@ -671,6 +713,17 @@ const CMDecisionTree& CMDTCaseType::create( const scnXml::DTCaseType& node, bool
) );
}

const CMDecisionTree& CMDTInfectionOrigin::create( const scnXml::DTInfectionOrigin& node, bool isUC ){
if( !isUC ){
throw util::xml_scenario_error( "decision tree: caseType can only be used for uncomplicated cases" );
}
return save_decision( new CMDTInfectionOrigin(
CMDecisionTree::create( node.getImported(), isUC ),
CMDecisionTree::create( node.getIntroduced(), isUC ),
CMDecisionTree::create( node.getIndigenous(), isUC )
) );
}

const CMDecisionTree& CMDTDiagnostic::create( const scnXml::DTDiagnostic& node, bool isUC ){
return save_decision( new CMDTDiagnostic(
diagnostics::get( node.getDiagnostic() ),
Expand Down
2 changes: 1 addition & 1 deletion model/Clinical/Episode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void Episode::update (const Host::Human& human, Episode::State newState)
toReport = true;

if(toReport) {
infectionType = human.withinHostModel->getInfectionType();
infectionType = human.withinHostModel->getInfectionOrigin();

report ();

Expand Down
22 changes: 6 additions & 16 deletions model/Host/WithinHost/CommonWithinHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,30 +260,14 @@ void CommonWithinHost::update(Host::Human &human, LocalRng& rng, int &nNewInfs_i
m_y_lag_l[y_lag_i * Genotypes::N() + g] = 0.0;
}

int nImported = 0, nIntroduced = 0, nIndigenous = 0;
for( auto inf = infections.begin(); inf != infections.end(); ++inf )
{
if((*inf)->origin() == InfectionOrigin::Imported)
m_y_lag_i[y_lag_i * Genotypes::N() + (*inf)->genotype()] += (*inf)->getDensity();
else
m_y_lag_l[y_lag_i * Genotypes::N() + (*inf)->genotype()] += (*inf)->getDensity();

if((*inf)->origin() == InfectionOrigin::Indigenous) nIndigenous++;
else if((*inf)->origin() == InfectionOrigin::Introduced) nIntroduced++;
else nImported++;
}

/* The rules are:
- Imported only if all infections are imported
- Introduced if at least one Introduced
- Indigenous otherwise (Imported + Indigenous or just Indigenous infections) */
if(nIntroduced > 0)
infectionType = InfectionOrigin::Introduced;
else if(nIndigenous > 0)
infectionType = InfectionOrigin::Indigenous;
else
infectionType = InfectionOrigin::Imported;

// This is a bug, we keep it this way to be consistent with old simulations
if(nNewInfsIgnored > 0)
nNewInfs_l += nNewInfsIgnored;
Expand All @@ -294,6 +278,10 @@ void CommonWithinHost::addProphylacticEffects(const vector<double>& pClearanceBy
throw util::unimplemented_exception( "prophylactic effects on 1-day time step" );
}

InfectionOrigin CommonWithinHost::getInfectionOrigin()const
{
return get_infection_origin(infections);
}

// ----- Summarize -----

Expand All @@ -309,6 +297,8 @@ bool CommonWithinHost::summarize( Host::Human& human )const{
pathogenesisModel->summarize( human );
pkpdModel.summarize( human );

InfectionOrigin infectionType = get_infection_origin(infections);

// If the number of infections is 0 and parasite density is positive we default to Indigenous
if( infections.size() > 0 ){
mon::reportStatMHI( mon::MHR_INFECTED_HOSTS, human, 1 );
Expand Down
2 changes: 2 additions & 0 deletions model/Host/WithinHost/CommonWithinHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class CommonWithinHost : public WHFalciparum
static CommonInfection* (* createInfection) (LocalRng& rng, uint32_t protID, int origin);
static CommonInfection* (* checkpointedInfection) (istream& stream);
//@}

virtual InfectionOrigin getInfectionOrigin() const;

virtual bool summarize( Host::Human& human )const;

Expand Down
93 changes: 46 additions & 47 deletions model/Host/WithinHost/DescriptiveWithinHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "util/ModelOptions.h"
#include "util/StreamValidator.h"
#include "util/errors.h"

#include <cassert>

using namespace std;
Expand All @@ -50,21 +51,27 @@ DescriptiveWithinHostModel::DescriptiveWithinHostModel( LocalRng& rng, double co
opt_vaccine_genotype = util::ModelOptions::option (util::VACCINE_GENOTYPE);
}

DescriptiveWithinHostModel::~DescriptiveWithinHostModel() {}
DescriptiveWithinHostModel::~DescriptiveWithinHostModel() {
for( auto inf = infections.begin(); inf != infections.end(); ++inf ){
delete *inf;
}
infections.clear();
}


// ----- Simple infection adders/removers -----

void DescriptiveWithinHostModel::loadInfection(istream& stream) {
infections.push_back(DescriptiveInfection(stream));
infections.push_back(new DescriptiveInfection(stream));
}

void DescriptiveWithinHostModel::clearInfections( Treatments::Stages stage ){
for(auto inf = infections.begin(); inf != infections.end();) {
if( stage == Treatments::BOTH ||
(stage == Treatments::LIVER && !inf->bloodStage()) ||
(stage == Treatments::BLOOD && inf->bloodStage())
(stage == Treatments::LIVER && !(*inf)->bloodStage()) ||
(stage == Treatments::BLOOD && (*inf)->bloodStage())
){
delete *inf;
inf = infections.erase( inf );
}else{
++inf;
Expand All @@ -77,11 +84,12 @@ void DescriptiveWithinHostModel::clearInfections( Treatments::Stages stage ){

void DescriptiveWithinHostModel::clearImmunity() {
for(auto inf = infections.begin(); inf != infections.end(); ++inf) {
inf->clearImmunity();
(*inf)->clearImmunity();
}
m_cumulative_h = 0.0;
m_cumulative_Y_lag = 0.0;
}

void DescriptiveWithinHostModel::importInfection(LocalRng& rng, int origin){
if( numInfs < MAX_INFECTIONS ){
m_cumulative_h += 1;
Expand All @@ -90,7 +98,8 @@ void DescriptiveWithinHostModel::importInfection(LocalRng& rng, int origin){
// should use initial frequencies to select genotypes.
vector<double> weights( 0 ); // zero length: signal to use initial frequencies
uint32_t genotype = Genotypes::sampleGenotype(rng, weights);
infections.push_back(DescriptiveInfection(rng, genotype, origin));
infections.push_back(new DescriptiveInfection(rng, genotype, origin));

}
assert( numInfs == static_cast<int>(infections.size()) );
}
Expand Down Expand Up @@ -119,10 +128,10 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
{
double vaccineFactor = human.vaccine.getFactor( interventions::Vaccine::PEV, genotype );
if(vaccineFactor == 1.0 || human.rng.bernoulli(vaccineFactor))
infections.push_back(DescriptiveInfection (rng, genotype, InfectionOrigin::Introduced));
infections.push_back(new DescriptiveInfection (rng, genotype, InfectionOrigin::Introduced));
}
else if (opt_vaccine_genotype == false)
infections.push_back(DescriptiveInfection (rng, genotype, InfectionOrigin::Introduced));
infections.push_back(new DescriptiveInfection (rng, genotype, InfectionOrigin::Introduced));
}
assert( numInfs == static_cast<int>(infections.size()) );

Expand All @@ -136,10 +145,10 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
{
double vaccineFactor = human.vaccine.getFactor( interventions::Vaccine::PEV, genotype );
if(vaccineFactor == 1.0 || human.rng.bernoulli(vaccineFactor))
infections.push_back(DescriptiveInfection (rng, genotype, InfectionOrigin::Indigenous));
infections.push_back(new DescriptiveInfection (rng, genotype, InfectionOrigin::Indigenous));
}
else if (opt_vaccine_genotype == false)
infections.push_back(DescriptiveInfection (rng, genotype, InfectionOrigin::Indigenous));
infections.push_back(new DescriptiveInfection (rng, genotype, InfectionOrigin::Indigenous));
}
assert( numInfs == static_cast<int>(infections.size()) );

Expand All @@ -160,9 +169,10 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
// any more).
// SP drug action and the PK/PD model would need to be abstracted
// behind a common interface.
if ( inf->expired() /* infection has self-terminated */ ||
(inf->bloodStage() ? treatmentBlood : treatmentLiver) )
if ( (*inf)->expired() /* infection has self-terminated */ ||
((*inf)->bloodStage() ? treatmentBlood : treatmentLiver) )
{
delete *inf;
inf=infections.erase(inf);
numInfs--;
continue;
Expand All @@ -171,18 +181,18 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
// Should be: infStepMaxDens = 0.0, but has some history.
// See MAX_DENS_CORRECTION in DescriptiveInfection.cpp.
double infStepMaxDens = timeStepMaxDensity;
double immSurvFact = immunitySurvivalFactor(ageInYears, inf->cumulativeExposureJ());
double bsvFactor = human.vaccine.getFactor(interventions::Vaccine::BSV, opt_vaccine_genotype? inf->genotype() : 0);
double immSurvFact = immunitySurvivalFactor(ageInYears, (*inf)->cumulativeExposureJ());
double bsvFactor = human.vaccine.getFactor(interventions::Vaccine::BSV, opt_vaccine_genotype? (*inf)->genotype() : 0);

inf->determineDensities(rng, m_cumulative_h, infStepMaxDens, immSurvFact, _innateImmSurvFact, bsvFactor);
(*inf)->determineDensities(rng, m_cumulative_h, infStepMaxDens, immSurvFact, _innateImmSurvFact, bsvFactor);

if (bugfix_max_dens)
infStepMaxDens = std::max(infStepMaxDens, timeStepMaxDensity);
timeStepMaxDensity = infStepMaxDens;

double density = inf->getDensity();
double density = (*inf)->getDensity();
totalDensity += density;
if( !inf->isHrp2Deficient() ){
if( !(*inf)->isHrp2Deficient() ){
hrp2Density += density;
}

Expand All @@ -207,30 +217,14 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
m_y_lag_l[y_lag_i * Genotypes::N() + g] = 0.0;
}

int nImported = 0, nIntroduced = 0, nIndigenous = 0;
for( auto inf = infections.begin(); inf != infections.end(); ++inf )
{
if(inf->origin() == InfectionOrigin::Imported)
m_y_lag_i[y_lag_i * Genotypes::N() + inf->genotype()] += inf->getDensity();
if((*inf)->origin() == InfectionOrigin::Imported)
m_y_lag_i[y_lag_i * Genotypes::N() + (*inf)->genotype()] += (*inf)->getDensity();
else
m_y_lag_l[y_lag_i * Genotypes::N() + inf->genotype()] += inf->getDensity();

if(inf->origin() == InfectionOrigin::Indigenous) nIndigenous++;
else if(inf->origin() == InfectionOrigin::Introduced) nIntroduced++;
else nImported++;
m_y_lag_l[y_lag_i * Genotypes::N() + (*inf)->genotype()] += (*inf)->getDensity();
}

/* The rules are:
- Imported only if all infections are imported
- Introduced if at least one Introduced
- Indigenous otherwise (Imported + Indigenous or just Indigenous infections) */
if(nIntroduced > 0)
infectionType = InfectionOrigin::Introduced;
else if(nIndigenous > 0)
infectionType = InfectionOrigin::Indigenous;
else
infectionType = InfectionOrigin::Imported;

// This is a bug, we keep it this way to be consistent with old simulations
if(opt_vaccine_genotype == false)
{
Expand All @@ -239,12 +233,18 @@ void DescriptiveWithinHostModel::update(Host::Human &human, LocalRng& rng, int &
}
}

InfectionOrigin DescriptiveWithinHostModel::getInfectionOrigin()const
{
return get_infection_origin(infections);
}

// ----- Summarize -----

bool DescriptiveWithinHostModel::summarize( Host::Human& human )const{
pathogenesisModel->summarize( human );

InfectionOrigin infectionType = get_infection_origin(infections);

// If the number of infections is 0 and parasite density is positive we default to Indigenous
if( infections.size() > 0 ){
mon::reportStatMHI( mon::MHR_INFECTED_HOSTS, human, 1 );
Expand All @@ -258,8 +258,8 @@ bool DescriptiveWithinHostModel::summarize( Host::Human& human )const{
int nImported = 0, nIntroduced = 0, nIndigenous = 0;
for( auto inf = infections.begin(); inf != infections.end(); ++inf )
{
if(inf->origin() == InfectionOrigin::Indigenous) nIndigenous++;
else if(inf->origin() == InfectionOrigin::Introduced) nIntroduced++;
if((*inf)->origin() == InfectionOrigin::Indigenous) nIndigenous++;
else if((*inf)->origin() == InfectionOrigin::Introduced) nIntroduced++;
else nImported++;
}

Expand All @@ -271,14 +271,14 @@ bool DescriptiveWithinHostModel::summarize( Host::Human& human )const{
mon::reportStatMHGI( mon::MHR_INFECTIONS_INDIGENOUS, human, 0, nIndigenous );

if( reportPatentInfected ){
for(std::list<DescriptiveInfection>::const_iterator inf = infections.begin(); inf != infections.end(); ++inf)
for(auto inf = infections.begin(); inf != infections.end(); ++inf)
{
if( diagnostics::monitoringDiagnostic().isPositive( human.rng, inf->getDensity(), std::numeric_limits<double>::quiet_NaN() ) )
if( diagnostics::monitoringDiagnostic().isPositive( human.rng, (*inf)->getDensity(), std::numeric_limits<double>::quiet_NaN() ) )
{
mon::reportStatMHGI( mon::MHR_PATENT_INFECTIONS, human, 0, 1 );
if(inf->origin() == InfectionOrigin::Indigenous)
if((*inf)->origin() == InfectionOrigin::Indigenous)
mon::reportStatMHGI( mon::MHR_PATENT_INFECTIONS_INDIGENOUS, human, 0, 1 );
else if(inf->origin() == InfectionOrigin::Introduced)
else if((*inf)->origin() == InfectionOrigin::Introduced)
mon::reportStatMHGI( mon::MHR_PATENT_INFECTIONS_INTRODUCED, human, 0, 1 );
else
mon::reportStatMHGI( mon::MHR_PATENT_INFECTIONS_IMPORTED, human, 0, 1 );
Expand All @@ -288,9 +288,8 @@ bool DescriptiveWithinHostModel::summarize( Host::Human& human )const{
if( reportInfectionsByGenotype ){
// accumulate total density by genotype
map<uint32_t, double> dens_by_gtype;
for( const DescriptiveInfection& inf: infections ){
dens_by_gtype[inf.genotype()] += inf.getDensity();
}
for(auto inf = infections.begin(); inf != infections.end(); ++inf)
dens_by_gtype[(*inf)->genotype()] += (*inf)->getDensity();

for( auto gtype: dens_by_gtype ){
// we had at least one infection of this genotype
Expand Down Expand Up @@ -334,8 +333,8 @@ void DescriptiveWithinHostModel::checkpoint (istream& stream) {
}
void DescriptiveWithinHostModel::checkpoint (ostream& stream) {
WHFalciparum::checkpoint (stream);
for(DescriptiveInfection& inf : infections) {
inf & stream;
for(auto inf = infections.begin(); inf != infections.end(); ++inf){
*inf & stream;
}
}

Expand Down
Loading

0 comments on commit 60ae7d4

Please sign in to comment.