Add gmrf and precision builder functions as precursors for eventually adding DSEM#1594
Add gmrf and precision builder functions as precursors for eventually adding DSEM#1594e-perl-NOAA wants to merge 12 commits into
Conversation
This file implements the GMRF distribution functor, which evaluates the log-likelihood of Gaussian Markov Random Fields using a sparse precision matrix.
🎨 Chore: code formatting workflowOur automated workflows cannot run on forks because of permission issues, and thus, we ask that you run the following code locally and push any changes that are created to your feature branch. You will only be reminded of this once per PR. Thank you! Format C++ code
Format R code
styler::style_pkg() # Style R code
roxygen2::roxygenise() # Update documentation
styler::style_pkg() # Style R code again
roxygen2::roxygenise() # Update documentation again
usethis::use_tidy_description() # Style DESCRIPTION filePush changes
|
Wow 😮 , you did it! I haven't taken a close look at the code in those files yet, but for For the I'm not sure what your timeline is for working on the tests, but I'll be setting aside dedicated time each week to work on testing-related issues. Feel free to join those sessions (starting July 9 and listed on the FIMS calendar) if you have any questions or would like to work through any testing challenges together! |
|
@Bai-Li-NOAA Yeah, that sounds great! I won't have time to work on tests this week, but I'll definitely have questions for you when I start working on it next week! Also, I love that you are leaning into me naming my AI/agents Fernando 🤣 |
|
@nathanvaughan-NOAA can you review this PR? |
|
@kellijohnson-NOAA I need to add tests, I just haven't had time to work on them 😞 |
|
No worries @e-perl-NOAA, Fernando is a busy guy I'm sure he'll get to them eventually 😁 I'll just go through the functions. |
| /** | ||
| * @brief Evaluates the GMRF log probability density function. | ||
| * @details The evaluation follows: | ||
| * \f[ \log(f(x)) = -\frac{1}{2} (x - \mu)^T Q (x - \mu) + \frac{1}{2} \log|Q| \f] |
There was a problem hiding this comment.
The formula is missing the constant term: -N\log(\sqrt(2\pi))
N is the size of the latent state.
|
@e-perl-NOAA, there are two issues:
I started working on a comment that included a bunch of code - I realized it might be easier to set up a co-working session to go over these changes together. I think Fernando has gotten you to a nice spot, but at this point, writing more instructions to AI would probably take more time than implementing the needed changes directly! |
| } | ||
|
|
||
| // Register builder in Information Map (to be accessed by GMRF distribution) | ||
| info->dsem_builders[this->id] = builder; |
There was a problem hiding this comment.
dsem_builders does not exist in information. More code is needed to link the precision matrix builder to the GMRF both here and in information.
| }; | ||
|
|
||
| /** | ||
| * @brief Rcpp interface for DSEM growth. |
There was a problem hiding this comment.
remove beta_z from the next four files (rcpp_growth, rcpp_maturity, rcpp_recruitment, and rcpp_selectivity)
| @@ -0,0 +1,75 @@ | |||
| /** | |||
There was a problem hiding this comment.
this file can be removed, beta_z will be linked in a different way
| } | ||
|
|
||
| // Centering: x - mu because TMB's GMRF expects input centered around a mean of 0. | ||
| vector<Type> x_centered(n_x); |
There was a problem hiding this comment.
I think this needs to be std::vector
There was a problem hiding this comment.
It would also be faster to have
vector x_centered;
x_centered.reserve(n_x);
for (size_t i = 0; i < n_x; ++i) {
x_centered.emplace_back(this->get_observed(i) - this->get_expected(i));
}
this avoids filling all the x_centered values with default first and overwriting them, emplace also avoids creating and then moving the centered value which push_back does.
|
|
||
| /** | ||
| * @brief Rcpp interface for DSEM growth. | ||
| */ |
There was a problem hiding this comment.
We should avoid setting this up to create module specific DSEM classes. Everything should be handled in the dsem interface so it can then point to arbitrary variables in any current/future modules without adding development overhead to them.
| int type = 0; /**< 1 = A path effect (Rho), 2 = Variance (Gamma) */ | ||
| int from = 0; /**< The variable the arrow starts from */ | ||
| int to = 0; /**< The variable the arrow points to */ | ||
| int beta_index = 0; /**< Which parameter in beta_z to use for this arrow */ |
There was a problem hiding this comment.
Am I right that using the index here is basically so you can map off multiple linkages to be estimated together? I'm not sure how common that would be but it may make more sense just to have the value in RAMPath and allow it to be estimable and/or mapped through TMB like other parameters? That basically replaces beta_index and start with beta_z that can be a fixed or estimated variable.
| } | ||
|
|
||
| // Poke the "strength" into the correct grid slot based on arrow type. | ||
| if (this->paths[r].type == 1) { |
There was a problem hiding this comment.
When I looked into SparseMatrix it looks like using .coeffRef is supposedly really slow due to memory moving issues and using Eigen::Triplet is much faster. Something like this apparently
int rows = 3;
int cols = 3;
Eigen::SparseMatrix<double> A(rows, cols);
std::vector<Eigen::Triplet<double>> tripletList;
tripletList.reserve(4);
tripletList.push_back(Eigen::Triplet<double>(0, 0, 1.5));
tripletList.push_back(Eigen::Triplet<double>(1, 2, 2.0));
tripletList.push_back(Eigen::Triplet<double>(2, 1, -3.5));
tripletList.push_back(Eigen::Triplet<double>(2, 2, 4.0));
A.setFromTriplets(tripletList.begin(), tripletList.end());
A.makeCompressed();
| } | ||
|
|
||
| // Centering: x - mu because TMB's GMRF expects input centered around a mean of 0. | ||
| vector<Type> x_centered(n_x); |
There was a problem hiding this comment.
It would also be faster to have
vector x_centered;
x_centered.reserve(n_x);
for (size_t i = 0; i < n_x; ++i) {
x_centered.emplace_back(this->get_observed(i) - this->get_expected(i));
}
this avoids filling all the x_centered values with default first and overwriting them, emplace also avoids creating and then moving the centered value which push_back does.
Disclaimer
I don't actually know what I'm doing here, but there was enthusiasm for progress being made on getting DSEM into FIMS so I embarked on this journey ⛵ with nothing but Copilot and a dream ☁️. Please keep that in mind as you make comments and I beg you to explain things in a way that you would see them in those "XXX for Dummies" books.
What is the feature?
Getting the behind the scenes functions of gmrf and a precision builder (that can be used with more than just dsem) that are precursors seemed like a good point to start merging things in since myself and copilot/notebookLM then all started to get confused on how to proceed from here (it started going a bit off the rails once we started getting to what needed to be added in the
information.hppfile) . I don't even think that these functions are "exposed" enough to write tests for? Maybe I'm wrong here and if so, @Bai-Li-NOAA I would DEFINITELY need your help.Once it seems like there is consensus that these are a good way forward and/or good to go, they can be merged in and I/we can incrementally work on getting DSEM into FIMS in subsequent PRs. Hopefully that method confuses copilot/notebookLM (and honestly, myself) less.
How have you implemented the solution?
Instructions for code reviewer
👋Hello reviewer👋, thank you for taking the time to review this PR!
nit:(for nitpicking) as the comment type. For example,nit:I prefer using adata.frame()instead of amatrixbecause ...This PR is now ready to be merged.Checklist