Skip to content
","upvoteCount":1,"answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"

@Chaofan13, your example very well demonstrates the inconvenience of having to treat every state variable, parameter, observable, and covariate by name when you supply basic model components to pomp as R functions. This illustrates one more advantage of the C snippet interface.

\n

You've given us a lot to work with here, but it's a bit too much really. I'll illustrate how to write a C snippet to accomplish something similar, but simpler. Let's suppose we want to translate the following basic model components—which are written as R functions—into C snippets.

\n
rinit_fn <- function (s1, s2, s3, e1, e2, e3, ...) {\n  population <- 1e4\n  S <- round(c(s1,s2,s3)*population)\n  E <- round(c(e1,e2,e3)*population)\n  c(S=S,E=E)\n}\n\nrinit_fn(s1=0.1,s2=0.1,s3=0.2,e1=0.1,e2=0.2,e3=0.3)
\n
##   S1   S2   S3   E1   E2   E3 \n## 1000 1000 2000 1000 2000 3000\n
\n
step_fn <- function (S1, S2, S3, E1, E2, E3, r, delta.t, ...) {\n  dE <- rpois(n=3,lambda=r*delta.t*c(S1,S2,S3))\n  S <- c(S1,S2,S3)-dE\n  E <- c(E1,E2,E3)+dE\n  c(S=S,E=E)\n}\n\nstep_fn(S1=100,S2=200,S3=300,E1=400,E2=500,E3=600,r=0.1,delta.t=1)
\n
##  S1  S2  S3  E1  E2  E3 \n##  88 184 272 412 516 628\n
\n
library(pomp)\nsimulate(\n  t0=0, times=seq(0,5),\n  rinit=rinit_fn,\n  rprocess=discrete_time(step_fn,delta.t=1),\n  params=c(r=0.3,s1=0.1,s2=0.1,s3=0.2,e1=0.1,e2=0.2,e3=0.3),\n  format=\"data.frame\"\n)
\n
## Warning: 'rmeasure' unspecified: NAs generated.\n
\n
##   time .id   S1   S2   S3   E1   E2   E3\n## 1    0   1 1000 1000 2000 1000 2000 3000\n## 2    1   1  708  668 1400 1292 2332 3600\n## 3    2   1  505  483 1004 1495 2517 3996\n## 4    3   1  363  338  720 1637 2662 4280\n## 5    4   1  266  246  514 1734 2754 4486\n## 6    5   1  195  170  355 1805 2830 4645\n
\n

This is a very simple stochastic dynamical system. It's not very interesting, but hopefully you can see that it illustrates the issue you ask about.

\n

In C, one can easily work with arrays, provided their entries are stored in contiguous memory locations. This is accomplished using pointers and pointer arithmetic. We need to know what data type is stored in the array and we need a pointer to the first element in the array. Both of these things are furnished by each of the declarations

\n
double *x;\nint *i;\n
\n

In these declarations, x and y are declared to be pointers to arrays of floating-point and integer numbers, respectively.

\n

The C language defines pointer arithmetic in such a way that one can easily access any given element. In addition to the usual arithmetic operations, pointer arithmetic has the \"address\" and \"dereference\" operators. The address operator (&) extracts a pointer to the given variable. Thus, if x is a variable, &x is the address of x in the computer's memory. The dereference operator (*) is the inverse of &: if p is a pointer to some memory (i.e., an address), then *p is the number stored at that memory location. Moreover, if p is a pointer to some memory location, then for any integer k, p+k points to the location k units farther along in the memory. In particular, in C, the following expressions are equivalent, and each returns the kth element of the array that begins at memory location p:

\n
p[k]\n*(p+k)\n
\n

You can read more about pointers here, for example. Now, when a pomp C snippet is executed, the named parameters, state variables, etc. are provided by name. Assuming that you have stored variables belonging to an array contiguously and in order, you can access them from the C snippet using pointers. In our simplified example, we might do something like the following.

\n
rinit_snip <- \"\ndouble pop = 10000;\ndouble *s = &s1;\ndouble *e = &e1;\ndouble *S = &S1;\ndouble *E = &E1;\nint i;\nfor (i = 0; i < 3; i++) {\n  S[i] = nearbyint(s[i]*pop);\n  E[i] = nearbyint(e[i]*pop);\n}\n\"
\n

More generally, we can use multi-dimensional arrays. Suppose for instance that we wish to treat the S and E variables, respectively, as the first and second columns of a 2-D array. Then the following would work for an rinit C snippet:

\n
rinit_snip2 <- \"\ndouble pop = 10000;\ndouble *x = &s1;\ndouble *X = &S1;\nint i, j;\nfor (i = 0; i < 2; i++) {\n  for (j = 0; j < 3; j++) {\n    X[3*i+j] = nearbyint(x[3*i+j]*pop);\n  }\n}\n\"
\n

Note that here I have assumed that the 2-D arrays X and x are in column-major order. This is the usual convention in scientific computing. When this C snippet is executed, it will place the S and I variables in their proper places, but, very importantly, it assumes that the parameters s1,e1,... are stored in a very particular order. It would be your responsibility, as the user, to make sure that you put the parameters in their proper locations at all times!

\n

A corresponding rprocess snippet might look like this:

\n
step_snip <- \"\ndouble *S = &S1;\ndouble *E = &E1;\ndouble dE[3];\nint i;\nfor (i = 0; i < 3; i++) {\n  dE[i] = rpois(r*dt*S[i]);\n}\nfor (i = 0; i < 3; i++) {\n  S[i] -= dE[i];\n  E[i] += dE[i];\n}\n\"
\n

Putting these together to generate a simulation,

\n
simulate(\n  t0=0, times=seq(0,5),\n  rinit=Csnippet(rinit_snip2),\n  rprocess=discrete_time(Csnippet(step_snip),delta.t=1),\n  params=c(r=0.3,s1=0.1,s2=0.1,s3=0.2,e1=0.1,e2=0.2,e3=0.3),\n  format=\"data.frame\",\n  statenames=c(sprintf(\"S%d\",1:3),sprintf(\"E%d\",1:3)),\n  paramnames=c(\"r\",sprintf(\"s%d\",1:3),sprintf(\"e%d\",1:3))\n)
\n
## Warning: 'rmeasure' unspecified: NAs generated.\n
\n
##   time .id   S1   S2   S3   E1   E2   E3\n## 1    0   1 1000 1000 2000 1000 2000 3000\n## 2    1   1  693  702 1411 1307 2298 3589\n## 3    2   1  493  476  993 1507 2524 4007\n## 4    3   1  337  315  673 1663 2685 4327\n## 5    4   1  238  213  476 1762 2787 4524\n## 6    5   1  163  146  343 1837 2854 4657\n
\n

Note that, in the params argument, we have been careful to put the parameters in the order that the C snippets expect! Having done that, however, our rinit snippet ensures that the state variables are in the order that the rprocess snippet expects.

","upvoteCount":2,"url":"https://github.com/kingaa/pomp/discussions/213#discussioncomment-9224800"}}}

How to use arrays when constructing an age-structured model #213

Answered by kingaa
Chaofan13 asked this question in Q&A
Discussion options

You must be logged in to vote

@Chaofan13, your example very well demonstrates the inconvenience of having to treat every state variable, parameter, observable, and covariate by name when you supply basic model components to pomp as R functions. This illustrates one more advantage of the C snippet interface.

You've given us a lot to work with here, but it's a bit too much really. I'll illustrate how to write a C snippet to accomplish something similar, but simpler. Let's suppose we want to translate the following basic model components—which are written as R functions—into C snippets.

rinit_fn <- function (s1, s2, s3, e1, e2, e3, ...) {
  population <- 1e4
  S <- round(c(s1,s2,s3)*population)
  E <- round(c(e1,e2,e3)*p…

Replies: 1 comment 1 reply

Comment options

You must be logged in to vote
1 reply
@Chaofan13
Comment options

Answer selected by Chaofan13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
2 participants