CDCgov / ixa

Apache License 2.0
10 stars 0 forks source link

How to handle multiple pathogens/serotypes? #76

Open confunguido opened 3 weeks ago

confunguido commented 3 weeks ago

I think we need an example that handles multiple serotypes, variants, pathogens, etc. Particularly, I am thinking of person properties. For instance, if I want to write a model for the four serotypes of dengue (DENV1, DENV2, DENV3, DENV4), should I set the person property for infection status, heatlh status, and even maybe last time infected as:

define_person_property!(InfectionStatus, [InfectionStatusType, 4])
define_person_property!(LastTimeInfected, [f64, 4])
define_person_property!(HealthStatus, [HealthStatusType, 4])

Another complication is if we don't know the number of circulating pathogens or variants in the population. For instance, if we specify the number of variants from an input file that is unknown at compile-time, I would like to define the person properties in a similar mamner.

define_person_property!(InfectionStatus, [InfectionStatusType, parameters.number_variants])
ekr-cfa commented 1 week ago

define_person_property!(InfectionStatus, [InfectionStatusType, parameters.number_variants])

I don't believe that this is possible with a Rust macro, because the macro is evaluated at compile time, and parameters.number_variants is known at runtime. If you truly don't know until runtime, then I think you need to have a fixed upper bound that is known at compile time and a runtime error if you try to exceed that.

With that said, I tend to think that it's inadvisable to have each of these be different person properties, in part because it doesn't enforce correctness. For instance, you can have InfectionStatus=Never but LastTimeInfected=<something>. Similarly, I don't know what HealthStatus is, but I imagine it's not independent of InfectionStatus. Restricting ourselves to InfectionStatus and LastInfected, I would instead use the type system to enforce only valid configurations, e.g.,

enum InfectionStatus {
   Susceptible,
   Infected {
      time: f64   // infected time
   }, 
   Recovered { 
     infected: f64,  // infected time
     recovered: f64  // recovered time
   }
}

impl Default for InfectionStatus {
    fn default() -> Self {
       InfectionStatus::Susceptible
    }
}

This guarantees that if someone is Infected you have an infection time but not a recovery time, but if you are Recovered, you have both, etc.

And then you would do:

#[derive(Default)]
struct InfectionStatusAllType{
   variants: [InfectionStatus; 4]
}

Followed by:

define_person_property_with_default!(InfectionStatusAll, InfectionStatusAllType, InfectionStatusAllType::default())
jasonasher commented 1 week ago

For future consideration, one challenge with this approach is that event notifications for these kinds of vector person properties are not as useful as one might want. For example people may want to subscribe to people becoming newly infected and they will have to compare across the arrays to see if this kind of status change has occurred. I think we could at some point consider the concept of a vectorized/array person property - one where there is a vec/array of values for each person and where updates occur at specific index entries and updating the ith entry in that array releases an event than the ith entry has been changed with the old and new values at that index.