Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filling in an element of a matrix? #194

Open
bbolker opened this issue Jun 28, 2020 · 2 comments
Open

filling in an element of a matrix? #194

bbolker opened this issue Jun 28, 2020 · 2 comments

Comments

@bbolker
Copy link

bbolker commented Jun 28, 2020

I have an epidemiological model where I pre-compute the per capita flows from compartment i to j and store them in a matrix M: this way, the only term that needs to be updated over the course of the epidemic is the S -> E per capita flows: all the rest stay constant (eventually could relax this, e.g. due to time-varying contact-tracing effort which would move infected people to quarantine faster). This formulation also makes it easier to go back and forth between deterministic and stochastic formulations (e.g. the "flow matrix" M is set up nicely for pomp's reulermultinom function for stochastically moving discrete individuals on the basis of multiple competing stochastic processes).

I may be completely missing something, but I'm having trouble compiling the gradient function when I try to set a particular component of M. Is there a way to do this? (As a workaround, is there a way to generate the C code with the M[2,1] <- ... line commented out and then hack the code before compiling?)

Happy to submit this to some more appropriate forum if appropriate ...

remotes::install_github("bbolker/McMasterPandemic")
library(McMasterPandemic)
## setup, via McMasterPandemic
pars <- read_params("ICU1.csv")
state <- make_state(param=pars)
M <- make_ratemat(state, pars)
odin_gradfun <- odin::odin({
    s_init[] <- user()
    initial(s) <- s_init
    M[,] <- user()
    beta0 <- user()
    Ca <- user()
    Cp <- user()
    iso_m <- user()
    Cm <- user()
    iso_s <- user()
    Cs <- user()
    N <- user()
    M[2,1] <- beta0/N*(Ca*s[3]+Cp*s[4]+(1-iso_m)*Cm*s[5]+(1-iso_s)*Cs*s[6])
    flows <- M[i,j]*s[j]
    deriv(s) <- sum(M[i,])-sum(M[,j])
})
@richfitz
Copy link
Member

richfitz commented Jul 3, 2020

Hi @bbolker - thanks for the message, and sorry for missing it (I declared github notification bankruptcy a long time ago but you can ping me with a notification or on the rOpenSci slack if you run into trouble).

I think you just need to disambiguate this a bit for odin. Here's a slightly smaller case, as the one above won't compile without the dim() calls. This fails with the same error as yours:

odin::odin({
  M[, ] <- user()
  M[1,2] <- 1
  dim(M) <- c(4, 4)
  initial(x) <- 0
  deriv(x) <- sum(M)
})
#> Error: user() may only be used on a single-line array
#> 	 M[, ] <- user() # (line 1)
#>   M[1, 2] <- 1 # (line 2)

however, this one will compile

gen <- odin::odin({
  m0 <- user()
  M[, ] <- m0
  M[1,2] <- 1
  dim(M) <- c(4, 4)
  initial(x) <- 0
  deriv(x) <- sum(M)
})

if your initial value for M is a matrix (sounds like it is) then that would be:

gen <- odin::odin({
  M0[, ] <- user()
  M[, ] <- M0[i, j]
  M[1,2] <- 1
  dim(M) <- c(4, 4)
  dim(M0) <- c(4, 4)
  initial(x) <- 0
  deriv(x) <- sum(M)
})

Creating the model and showing its internal variables:

mod <- gen(M0 = matrix(-seq_len(16), 4, 4))
mod$contents()
#> > mod <- gen(M0 = matrix(-seq_len(16), 4, 4))
#> > # This shows both M0 and M:
#> > mod$contents()
#> $dim_M
#> [1] 16
#> 
#> $dim_M_1
#> [1] 4
#> 
#> $dim_M_2
#> [1] 4
#> 
#> $dim_M0
#> [1] 16
#> 
#> $dim_M0_1
#> [1] 4
#> 
#> $dim_M0_2
#> [1] 4
#> 
#> $initial_x
#> [1] 0
#> 
#> $M
#>      [,1] [,2] [,3] [,4]
#> [1,]   -1    1   -9  -13
#> [2,]   -2   -6  -10  -14
#> [3,]   -3   -7  -11  -15
#> [4,]   -4   -8  -12  -16
#> 
#> $M0
#>      [,1] [,2] [,3] [,4]
#> [1,]   -1   -5   -9  -13
#> [2,]   -2   -6  -10  -14
#> [3,]   -3   -7  -11  -15
#> [4,]   -4   -8  -12  -16

here, because M can be computed without reference to anything time-varying M has been set before any integration happens. But if your value you insert into M does depend on time there will be a copy each time unfortunately:

gen <- odin::odin({
  M0[, ] <- user()
  M[, ] <- M0[i, j]
  M[1,2] <- 1 + (t * 0)
  dim(M) <- c(4, 4)
  dim(M0) <- c(4, 4)
  initial(x) <- 0
  deriv(x) <- sum(M)
})
mod <- gen(M0 = matrix(-seq_len(16), 4, 4))
mod$contents()$M
#>      [,1] [,2] [,3] [,4]
#> [1,]    0    0    0    0
#> [2,]    0    0    0    0
#> [3,]    0    0    0    0
#> [4,]    0    0    0    0
mod$deriv(0, 0)
mod$contents()$M
#>      [,1] [,2] [,3] [,4]
#> [1,]   -1    1   -9  -13
#> [2,]   -2   -6  -10  -14
#> [3,]   -3   -7  -11  -15
#> [4,]   -4   -8  -12  -16

@richfitz
Copy link
Member

richfitz commented Jul 3, 2020

I've opened a new ticket (#195) which would allow this to behave more efficiently as currently the generated code looks like:

  for (int i = 1; i <= internal->dim_M_1; ++i) {
    for (int j = 1; j <= internal->dim_M_2; ++j) {
      internal->M[i - 1 + internal->dim_M_1 * (j - 1)] = internal->M0[internal->dim_M0_1 * (j - 1) + i - 1];
    }
  }
  {
     int i = 1;
     int j = 2;
     internal->M[i - 1 + internal->dim_M_1 * (j - 1)] = 1 + (t * 0);
  }

with that whole top section being a waste of time. I doubt this will be a huge time sink though?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants