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

Improve interface around model specification #10

Merged
merged 11 commits into from
Mar 1, 2024
Merged

Improve interface around model specification #10

merged 11 commits into from
Mar 1, 2024

Conversation

richfitz
Copy link
Member

@richfitz richfitz commented Feb 26, 2024

To support having an observer on a stochastic model that can save things like trajectories, and to support adding lots of weird things to support nested models, I think we need a slightly more flexible approach to specifying models.

The motivation for this is a little hard to see, but starts to appear when considering models that run a time series as part of the likelihood calculation. For these, we're going to want to record trajectories over time (either deterministic or some sampled stochastic trajectories). We're likely also to want to store things like the final state so that onward simulation can be continued.

The mcstate_model interface does not need to know about any of this - and we don't want to have an ever increasing set of things to add there as people add new functionality to their models. Instead I'm imagining that we'll have some sort of "observer" that will be added to the call to mcstate_sample that we'll run at each step and which will receive the mcstate model to prod at it however it wants. So the observer and the model need to agree on the interface but the sampler does not really need to know about this detail at all.

Two ways we could do this are to allow mcstate_model to accept ... with additional properties, or we can compose the final mcstate_model out of whatever we're given; I've gone with the latter.

I was not totally sure where to put parameters (and then as a result domain) so for now I've put them both in the model and in the call to mcstate_model but I'm open to removing one of these and requiring that we have these in one place or the other. I'd wondered too about allowing model to be a density function (in which case we just do model <- list(density = density) to allow a really simple interface here; this is much of where allowing parameters/domain through has come from. For stochastic models (#9, currently draft) having this format is useful because it allows a model to provide functions that can support a stochastic model and then also gives us something to use to say "but actually this one is deterministic", which is what we do when creating deterministic models from stochastic ones

Edit: @M-Kusumgar and I had a chat about this and we're gone for something slightly different here that ends up being a little easier to think about I think. There's now an mcstate_model_properties function which can be passed in to mcstate_model which let's you assert what your model can and cannot do. We then look at the model and pull out all the functions that we need into a list but we also keep the original model which allows us to have the user do whatever they want at each mcmc step (in a later PR)

Copy link

codecov bot commented Feb 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (4a92bdf) to head (bd29340).

❗ Current head bd29340 differs from pull request most recent head 28d1658. Consider uploading reports for the commit 28d1658 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##              main       #10    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           38        39     +1     
  Lines         1456      1616   +160     
==========================================
+ Hits          1456      1616   +160     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@richfitz richfitz marked this pull request as ready for review February 27, 2024 09:54
Copy link
Contributor

@M-Kusumgar M-Kusumgar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! lets see how this propagates through the other PRs but i like it, couple tiny documentation comments

R/model.R Outdated Show resolved Hide resolved
R/model.R Outdated Show resolved Hide resolved
Comment on lines +84 to +85
##' @param model A list or environment with elements as described in
##' Details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is "Details"? is this a roxygen2 thing that will reference the text above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything after the first linebreak is Details in the help rendering (or should be)

richfitz added a commit that referenced this pull request Feb 29, 2024
R/model.R Outdated Show resolved Hide resolved
R/model.R Outdated
Comment on lines 184 to 221
validate_model_gradient <- function(model, properties, call) {
if (isFALSE(properties$has_gradient)) {
return(NULL)
}
gradient <- model$gradient
if (isTRUE(properties$has_gradient) && !is.function(gradient)) {
cli::cli_abort(
paste("Did not find a function 'gradient' within your model, but",
"your properties say that it should do"),
arg = "model", call = call)
}
if (!is.null(gradient) && !is.function(gradient)) {
cli::cli_abort(
"Expected 'model$gradient' to be a function if non-NULL",
arg = "model", call = call)
}
gradient
}


validate_model_direct_sample <- function(model, properties, call) {
if (isFALSE(properties$has_direct_sample)) {
return(NULL)
}
direct_sample <- model$direct_sample
if (isTRUE(properties$has_direct_sample) && !is.function(direct_sample)) {
cli::cli_abort(
paste("Did not find a function 'direct_sample' within your model, but",
"your properties say that it should do"),
arg = "model", call = call)
}
if (!is.null(direct_sample) && !is.function(direct_sample)) {
cli::cli_abort(
"Expected 'model$direct_sample' to be a function if non-NULL",
arg = "model", call = call)
}
direct_sample
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this repeated logic be implemented once and take expected property name etc as argument?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I wondered about that. Will do

richfitz and others added 3 commits February 29, 2024 17:20
Co-authored-by: Emma Russell <44669576+EmmaLRussell@users.noreply.github.com>
richfitz added a commit that referenced this pull request Feb 29, 2024
@richfitz richfitz mentioned this pull request Mar 1, 2024
@richfitz richfitz merged commit b078f65 into main Mar 1, 2024
16 checks passed
richfitz added a commit that referenced this pull request Mar 1, 2024
richfitz added a commit that referenced this pull request Mar 5, 2024
richfitz added a commit that referenced this pull request Mar 5, 2024
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

Successfully merging this pull request may close these issues.

4 participants