Skip to content

Commit b404953

Browse files
authored
Merge pull request #8 from atsa-es/Uonly
v0.0.7 finished adding U, A, x0, C and tinits
2 parents cada0df + 37d4da7 commit b404953

File tree

8 files changed

+92
-53
lines changed

8 files changed

+92
-53
lines changed

NEWS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# marssTMB 0.0.7
22

3-
* updated `marxss.hpp` to be in MARSS format with X and Y as mxT and nxT
4-
* added Q estimation to `marxss.hpp` (note `MARSS_tmb()` will block this until this is tested)
5-
* added C estimation to `marxss.hpp` (note `MARSS_tmb()` will block this until this is tested)
3+
* updated `marxss.hpp` to be in MARSS format with X and Y as mxT and nxT.
4+
* added Q, C, U, x0 and A estimation to `marxss.hpp`. Minimal testing so far.
5+
* V0 = 0 is allowed.
6+
* tinitx=1 or tinitx=0 allowed.
67

78
# marssTMB 0.0.6
89

R/MARSS-TMB.R

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ MARSS_tmb <- function(y,
3434
inits = NULL,
3535
miss.value = as.numeric(NA),
3636
method = "TMB",
37-
form = "dfa",
37+
form = c("marxss", "dfa"),
3838
fit = TRUE,
3939
silent = FALSE,
4040
control = list(fun.opt="nlminb"),
4141
...) {
4242
pkg <- "marssTMB"
43+
form <- match.arg(form)
4344
method <- match.arg(method)
4445
# This section is temporary
4546
fun.opt <- ifelse(is.null(control$fun.opt), "nlminb", control$fun.opt )
@@ -97,46 +98,42 @@ MARSS_tmb <- function(y,
9798
x[["method"]] <- paste0(method, " with optimization function ", fun.opt, if(fun.opt=="optim") paste0(" with method ", optim.method))
9899

99100
# Error check for the DFA model
100-
# This section is temporary. Currently only DFA model with tinitx=1 is allowed.
101-
if(x[["model"]][["tinitx"]] != 1)
102-
stop(paste0(pkg, ": tinitx must be 1. Set tinitx=1 in model list."))
103101
model.descrip <- MARSS:::describe.marssMODEL(x[["model"]])
104-
if(form == "dfa"){
102+
105103
is.unconstrained <- function(elem) substr(model.descrip[[elem]], 1, 5) == "uncon"
106-
is.diagonal <- function(elem) substr(model.descrip[[elem]], 1, 8) == "diagonal"
104+
is.diagonal <- function(elem){
105+
substr(model.descrip[[elem]], 1, 8) == "diagonal" | model.descrip[[elem]] == "scalar (1 x 1)" }
107106
is.fixed <- function(elem) substr(model.descrip[[elem]], 1, 5) == "fixed"
108107
is.identity <- function(elem){
109108
val <- model.descrip[[elem]]
110109
substr(val, 1, 8) == "identity" | val == "fixed and all one (1 x 1)"
111110
}
112111
is.zero <- function(elem) substr(model.descrip[[elem]], 1, 14) == "fixed and zero"
113-
114-
# Check that R is allowed
115-
elem <- "R"
116-
ok <- is.diagonal(elem) | is.fixed(elem) | is.unconstrained(elem)
117-
if(!ok){
118-
stop(paste0(pkg, ": R must be diagonal, fixed or unconstrained"))
112+
113+
# Check that R and Q are allowed
114+
for(elem in c("R", "Q")){
115+
ok <- is.diagonal(elem) | is.fixed(elem) | is.unconstrained(elem) | is.identity(elem)
116+
if(!ok) stop(paste0(pkg, ": ", elem, " must be diagonal, fixed or unconstrained"))
119117
}
120118

121-
# Check that Q and B are identity
122-
for(elem in c("B", "Q")){
119+
# Check that B is identity
120+
for(elem in c("B")){
123121
ok <- is.identity(elem)
124122
if(!ok) stop(paste0(pkg, ": ", elem, " must be identity"))
125123
}
126124

127-
# Check that u, a, and C are zero
128-
for(elem in c("U", "A", "C")){
129-
ok <- is.zero(elem)
130-
if(!ok) stop(paste0(pkg, ": ", elem, " must be zero"))
131-
}
132-
133-
# Check that x0 and V0 are fixed
134-
for(elem in c("x0", "V0")){
135-
ok <- is.fixed(elem)
125+
# Check that a, and C are zero
126+
# for(elem in c("A", "C")){
127+
# ok <- is.zero(elem)
128+
# if(!ok) stop(paste0(pkg, ": ", elem, " must be zero"))
129+
# }
130+
131+
# Check that V0 is fixed
132+
for(elem in c("V0")){
133+
ok <- is.fixed(elem) | is.identity(elem)
136134
if(!ok) stop(paste0(pkg, ": ", elem, " must be fixed"))
137135
}
138-
}
139-
136+
140137
if(fit) return(MARSStmb(x))
141138
return(x)
142139
}

R/MARSStmb.R

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,22 @@ MARSStmb <- function(MLEobj) {
4444
# Expand out to full covariate matrix
4545
d_Covars <- matrix(d_Covars[,1,], nrow = nrow(d_Covars))
4646
c_Covars <- matrix(c_Covars[,1,], nrow = nrow(c_Covars))
47-
# if user did not pass in c_Covars then c must have T columns
48-
no_c_covars <- ncol(c_Covars) == 1
4947

5048
# Set up the initial matrices
5149
eleminits <- list()
52-
for (elem in c("Z", "D", "R", "C", "Q", "V0", "x0")) {
50+
model.elem <- attr(MODELobj, "par.names")
51+
for (elem in model.elem[!(model.elem %in% c("c", "d"))]) {
5352
eleminits[[elem]] <- coef(MLEobj, type = "matrix", what = "start")[[elem]]
5453
}
54+
# Check that no 0s on diagonal of V0 unless V0 is all zero
55+
# Note V0 is fixed by definition
56+
V0_is_zero <- all(eleminits[["V0"]]==0)
57+
if(!V0_is_zero && any(diag(eleminits[[elem]])==0))
58+
stop(paste0(pkg, ": V0 can only have 0s on the diagonal if it is all zero"))
59+
5560
# Set up the maps
5661
elemmaps <- list()
57-
for (elem in c("Z", "D", "C", "x0")) {
62+
for (elem in model.elem) {
5863
elemmaps[[elem]] <- create.elem.maps(MLEobj, elem = elem)[["map"]]
5964
}
6065
# maps for var-cov matrices have diagonal separate from off-diagonal
@@ -72,7 +77,10 @@ MARSStmb <- function(MLEobj) {
7277
Y = y,
7378
d_Covar = d_Covars,
7479
c_Covar = c_Covars,
75-
no_c_covars = as.numeric(no_c_covars)
80+
has_c_covars = as.numeric(ncol(c_Covars) != 1),
81+
has_d_covars = as.numeric(ncol(d_Covars) != 1),
82+
V0_is_zero = as.numeric(V0_is_zero),
83+
tinitx = MODELobj[["tinitx"]]
7684
)
7785

7886
# Note x0 and V0 are fixed (stochastic prior) for DFA
@@ -81,18 +89,20 @@ MARSStmb <- function(MLEobj) {
8189
# Creates the list of initial (start) values of parameter list
8290
R <- eleminits[["R"]]
8391
sdR <- sqrt(diag(R))
84-
corrR <- diag(1 / sdR) %*% R %*% diag(1 / sdR) # correlation matrix
92+
corrR <- diag(1/sdR, n) %*% R %*% diag(1/sdR, n) # correlation matrix
8593
Q <- eleminits[["Q"]]
8694
sdQ <- sqrt(diag(Q))
87-
corrQ <- diag(1 / sdQ) %*% Q %*% diag(1 / sdQ) # correlation matrix
95+
corrQ <- diag(1/sdQ, m) %*% Q %*% diag(1/sdQ, m) # correlation matrix
8896
parameters <- list(
8997
X = matrix(0, ncol = TT, nrow = m), # states
9098
x0 = eleminits[["x0"]],
9199
V0 = eleminits[["V0"]],
92100
logsdQ = log(sdQ), # log of sqrt of diagonal of Q
93101
cholCorrQ = chol(corrQ)[upper.tri(Q)], # off-diagonal of chol of corr Q
102+
U = eleminits[["U"]],
94103
C = eleminits[["C"]],
95104
Z = eleminits[["Z"]],
105+
A = eleminits[["A"]],
96106
D = eleminits[["D"]],
97107
logsdR = log(sdR), # log of sqrt of diagonal of R
98108
cholCorrR = chol(corrR)[upper.tri(R)] # off-diagonal of chol of corr R
@@ -106,12 +116,19 @@ MARSStmb <- function(MLEobj) {
106116
V0 = factor(matrix(NA, nrow = m, ncol = m)),
107117
logsdQ = elemmaps[["Q"]][["diag"]],
108118
cholCorrQ = elemmaps[["Q"]][["offdiag"]],
119+
U = elemmaps[["U"]],
109120
C = elemmaps[["C"]],
110121
Z = elemmaps[["Z"]],
122+
A = elemmaps[["A"]],
111123
D = elemmaps[["D"]],
112124
logsdR = elemmaps[["R"]][["diag"]],
113125
cholCorrR = elemmaps[["R"]][["offdiag"]]
114126
)
127+
if(MODELobj[["tinitx"]]==1 & V0_is_zero){
128+
mat <- matrix(1:(m*TT), m, TT)
129+
mat[,1] <- NA
130+
maplist$X <- mat |> unlist() |> as.factor()
131+
}
115132

116133
# Creates the model object and runs the optimization
117134
obj1 <- TMB::MakeADFun(

R/utils.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ create.varcov.maps <- function(x, elem){
3131
create.elem.maps <- function(x, elem="Z"){
3232
fixed <- x$model$fixed[[elem]]
3333
free <- x$model$free[[elem]]
34+
par.names <- colnames(free)
3435
var.dim <- attr(x$model, "model.dims")[[elem]][1:2]
3536
par.as.list <- MARSS:::fixed.free.to.formula(fixed[, , 1, drop = FALSE], free[, , 1, drop = FALSE], var.dim)
3637
mat <- lapply(par.as.list, function(x){ifelse(!is.character(x), NA, x)}) |>
3738
matrix(var.dim)
38-
return(list(map = mat |> unlist() |> as.factor(), map.matrix=mat, raw.matrix=par.as.list))
39+
return(list(map = mat |> unlist() |> factor(levels=par.names), map.matrix=mat, raw.matrix=par.as.list))
3940
}

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# marssTMB
22

3-
The beginning of a companion R package to MARSS that fits MARSS models with TMB. The package is in very active development. See [changelog](news/index.html) for current status.
3+
## To use
4+
5+
Fit MARSS models as usual, but use `MARSS_tmb()` instead of `MARSS()`. Not R and Q can only be diagonal, unconstrained or fixed. `equalvarcov` is not available nor are other custom Q matrices available with the EM algorithm in `MARSS()`. But TMB is very fast.
46

57
See the documentation at [marssTMB](https://atsa-es.github.io/marssTMB/)
68

79
## install
810

911
```
10-
remotes::install_github("atsa-es/marssTMB")
12+
# Install marssTMB in R:
13+
install.packages('marssTMB', repos = c('https://atsa-es.r-universe.dev', 'https://cloud.r-project.org'))
1114
```
1215

1316
## Notes

man/MARSS_tmb.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/TMB/marxss.hpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ Type marxss(objective_function<Type>* obj) {
1414
DATA_MATRIX(Y); /* n x T */
1515
DATA_MATRIX(d_Covar);
1616
DATA_MATRIX(c_Covar);
17-
DATA_INTEGER(no_c_covars);
17+
DATA_INTEGER(has_c_covars);
18+
DATA_INTEGER(has_d_covars);
19+
DATA_INTEGER(V0_is_zero);
20+
DATA_INTEGER(tinitx);
1821
PARAMETER_MATRIX(X); /* State m x T */
1922
PARAMETER_MATRIX(x0);
2023
PARAMETER_MATRIX(V0); /* x[1] */
2124
// PARAMETER_MATRIX(Q); /* x[t] - x[t-1] */
25+
PARAMETER_MATRIX(U);
2226
PARAMETER_MATRIX(C);
2327
PARAMETER_VECTOR(logsdQ); /* log of the sqrt of diag Q*/
2428
PARAMETER_VECTOR(cholCorrQ);
2529
PARAMETER_MATRIX(Z);
30+
PARAMETER_MATRIX(A);
2631
PARAMETER_MATRIX(D);
2732
PARAMETER_VECTOR(logsdR); /* log of the sqrt of diag R*/
2833
PARAMETER_VECTOR(cholCorrR);
@@ -43,30 +48,45 @@ Type marxss(objective_function<Type>* obj) {
4348

4449
matrix<Type> predX(nX,1); /* m x 1 */
4550

46-
MVNORM_t<Type> initialState(V0);
47-
//MVNORM_t<Type> neg_log_density_process(Q);
48-
/* Define likelihood */
49-
Type ans=0;
51+
52+
Type ans=0; /* Define likelihood */
5053
//ans -= dnorm(vector<Type>(u.row(0)),Type(0),Type(1),1).sum();
51-
ans += initialState(X.col(0)); /* tinitx=1 */
54+
if(V0_is_zero){
55+
if(tinitx){
56+
X.col(0) = x0;
57+
}else{
58+
predX = x0 + U;
59+
if(has_c_covars){
60+
predX = predX + C * c_Covar.col(0);
61+
}
62+
vector<Type> differ = X.col(0)-predX;
63+
ans += VECSCALE(corMatGenQ,sdQ)(differ);
64+
}
65+
}else{
66+
MVNORM_t<Type> initialState(V0);
67+
ans += initialState(X.col(0)-x0); /* tinitx=1 */
68+
}
5269
for(int i=1;i<timeSteps;i++){
5370
//ans+= neg_log_density_process(u.row(i)-u.row(i-1)); // Process likelihood
5471
//vector<Type> differ = u.row(i)-u.row(i-1);
55-
// predX is m x 1 so u must be transposed
5672
// if statement is temporary until I can figure how create a
5773
// a diagonal matrix with 1 on the -1 diagonal
5874
// diag(1:(timeSteps+1))[1:timeSteps, 2:(timeSteps+1)]
59-
if(no_c_covars){
60-
predX = X.col(i-1) + C * c_Covar;
61-
}else{
62-
predX = X.col(i-1) + C * c_Covar.col(i);
75+
predX = X.col(i-1) + U;
76+
if(has_c_covars){
77+
predX = predX + C * c_Covar.col(i);
6378
}
6479
vector<Type> differ = X.col(i)-predX;
6580
ans += VECSCALE(corMatGenQ,sdQ)(differ);
6681
}
6782

6883
matrix<Type> predY(nY, timeSteps);
69-
predY = Z * X + D * d_Covar;
84+
matrix<Type> rowOne(1, timeSteps);
85+
rowOne.setOnes();
86+
predY = Z * X + A * rowOne;
87+
if(has_d_covars){
88+
predY = predY + D * d_Covar;
89+
}
7090

7191
for(int i=0;i<timeSteps;i++){ //move one time step at a time
7292
int nonNAcount = 0; //start at zero NA values

vignettes/MARSS_TMB.Rmd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ Fit with TMB.
5858
```{r}
5959
library(marssTMB)
6060
m3 <- dfaTMB(dat, model=list(m=1, R='unconstrained'))
61-
m4 <- MARSS_tmb(dat, model=mod.list)
62-
m5 <- MARSS_tmb(dat, model=mod.list, control=list(fun.opt="optim"))
61+
m4 <- MARSS_tmb(dat, model=mod.list, form='dfa')
62+
m5 <- MARSS_tmb(dat, model=mod.list, control=list(fun.opt="optim"), form='dfa')
6363
```
6464

6565
### Log likelihoods

0 commit comments

Comments
 (0)