ashokkrish / episim

episim is an R Shiny app for mathematical modelling of infectious diseases
GNU General Public License v3.0
3 stars 3 forks source link

Model generality #25

Closed bryce-carson closed 1 month ago

bryce-carson commented 2 months ago

Just curious about something. If in the future I want to add a Vaccinated (V) compartment and want to run a SIRV model or a SVEIRD model is this going be a massive code change? Sorry to spring this on you out of nowhere.

Originally posted by @ashokkrish in https://github.com/ashokkrish/episim/issues/23#issuecomment-2105309555

Hi Ashok, let's discuss the issue here.

For now, the SEIR(S) models are one file, SEIRD is another file, and SIR and SIRS will be one or two files. This is due to a short-coming in my communication and delegation of tasks for implementing the models.

In the differential equations you sent, those are clear that with certain inputs ($\xi = 0$) we can enable or disable movement from or to another compartment, or disable things like vital dynamics ($\mu_B = \mu_D = 0$), or use partial mass action instead of total mass action ($q = 0$).

I will need to consider the formulation a little further, and for now the changes tracked by #1 are simpler than they are general. I think we can integrate death easily into the SEIR(S) model.

One thing I wanted to be sure of was that Tobias and Khanh had some tasks in R to work on to exercise their use of the language; at the time I was also working on finishing the refactor of ui.R, so I was unprepared to closely examine the models and decide how things could go.

I think after #1 is resolved, next week I will ask Tobias or Khanh if they can make a prototype model function that will accept the full range of variables and parameters and thereby enable or disable optional compartments like vaccination or death, or have temporary immunity.

Given a new set of differential equations, we can implement them now. It doesn't take a lot of fiddling.

With a more general framework, I'd like to have a function or a macro which takes a list of compartments, and each compartment is simply defined in-line as a list of expressions. There might be some checks and balances that happen, but it's a larger task than we've taken on for now, and with the time constraint for episim may not be tenable.

E.g.:

sveird <- refined_factory(...)  # https://www.behindthename.com/name/sei/submitted
svird <- victory_factory(...) # https://www.behindthename.com/name/si/submitted

Each factory function would generate a function to be consumed by lsoda(). The ... argument to the factory functions would take the mentioned lists. These lists would be named collections of terms, symbols, operators, etc, and also the names of other compartments.

The computer science ideas used in the above discussion are symbolic programming. It's a set of notions where both metaprogramming (programming the program) and the manipulation of logical and mathematical symbols is done directly in the language itself. R supports this to a relatively high degree due to it's lingual history deriving from Scheme.

I don't think there's time to implement it fully, but it's a nice idea we could discuss further next week.

bryce-carson commented 2 months ago

We could look for existing libraries that use symbolic programming to define the edges of a graph, or directly compute on networks/graphs.

Here is an article about graph analysis in Shiny using tidygraph.

Another way to think about this, using really simple symbolic manipulations, is to define a compartment as a summation of terms, and when an edge exists between two nodes (compartments) of the graph, there is a single term which is negated in one node and not negated in the other.

## The first component of COMPARTMENTS is expected to be the differential equation in which the term is negated. That component is a list.
addEdgeTerm <- function(compartments, term) {
  compartments[[1]][length(compartments[[1]] + 1)] <- quote(term * -1)
  compartments[[2]][length(compartments[[2]] + 1)] <- quote(term)
  return(compartments)
}

addTerm <- function(compartment, term) {
  compartment <- quote(term)
}

compartmentalize <- function(termList) {
  function() sum(lapply(eval, termlist))
}

In this way, a compartment is defined incrementally by adding terms to it which are not present as negated terms in other compartments, and adding an "edge term" to two compartments at a time, which is negated in one compartment and not in the other.

bryce-carson commented 2 months ago

@ashokkrish There is also the possibility of using $\tiny{ODIN}$: https://mrc-ide.github.io/odin/. Have you read about this domain-specific language before? I wonder whether a future project should implement a Shiny interface to $\tiny{ODIN}$ with the drag-and-drop feature you thought would be useful.