Handle configs via dependency injection#931
Conversation
… delegate config building and improve attribute access
…ration and participant/trial counts
…ant and trial counts
…te initialization parameters
…methods and adding a new method for building validated Config instances
…on parameters for model configuration
… model configuration handling
There was a problem hiding this comment.
Pull request overview
This PR refactors HSSM/RLSSM configuration handling to use typed config objects built via factory methods (dependency injection into HSSMBase), and updates RLSSM internals/tests to align with the new config and attribute patterns.
Changes:
- Refactors
HSSMBaseto accept a pre-builtBaseModelConfigand adds deprecated proxy properties for legacy attributes. - Adds
Config._build_model_config(...)and uses it inHSSM.__init__; RLSSM now builds a validatedConfigviaRLSSMConfig._build_model_config(...). - Updates RLSSM public attributes/tests (
n_participants,n_trials) and adjusts distribution construction to work with the new config flow.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_rlssm.py | Updates tests to use new RLSSM public attributes. |
| src/hssm/rl/rlssm.py | Removes config mixin inheritance; injects validated config into HSSMBase. |
| src/hssm/hssm.py | Adds HSSM.__init__ that builds a validated Config via factory then calls HSSMBase. |
| src/hssm/config.py | Introduces centralized config factory (Config._build_model_config) and RLSSM helper factory. |
| src/hssm/base.py | Changes base initializer to consume injected config and adds deprecated proxy properties + init-arg capture helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
src/hssm/base.py
Outdated
| warnings.warn( | ||
| f"`{name}` is deprecated; use `self.model_config.{name}` instead.", | ||
| DeprecationWarning, | ||
| stacklevel=2, | ||
| ) |
There was a problem hiding this comment.
The new deprecated proxy properties always emit DeprecationWarning via _deprecation_warn(). Since core internals (including HSSMBase.__init__) call self.list_params, self.choices, etc., this will trigger warnings during normal model construction even when users never touch deprecated attributes (and can break in environments that treat warnings as errors). Consider having internal code access self.model_config.* directly, or gating warnings so they only fire for external/user-level access.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…uments and explicit error handling for missing snapshot
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/hssm/base.py:276
- After introducing deprecated proxy properties (e.g.,
self.list_params,self.choices), this initializer still uses them internally (_validate_choices(),if self.list_params is None,len(self.choices)). That will emit DeprecationWarnings during normal model construction (especially under warnings-as-errors). Prefer direct access to the authoritativemodel_confighere (e.g.,model_config.list_params,model_config.choices).
self._validate_choices()
# region Avoid mypy error later (None.append). Should list_params be Optional?
if self.list_params is None:
raise ValueError(
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…s and avoid deprecated proxy properties
…et in subclasses and exclude additional internal names from locals() snapshots during re-instantiation.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…cify required fields and improve readability.
… names in parameter mapping for safe unpickling.
…BaseModelConfig instance and clarify usage of dict for configuration.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/hssm/config.py:227
update_choicesassignschoicesdirectly, but callers (e.g.Config._build_model_config) can pass alist[int]. This breaks the tuple-based immutability/type-consistency the rest of the module is moving toward and can leak mutable lists intoConfig.choices. Coerce totuple(choices)(and consider widening the parameter type to acceptSequence[int]).
def update_choices(self, choices: tuple[int, ...] | None) -> None:
"""Update the choices from user input.
Parameters
----------
choices : tuple[int, ...] | None
A tuple of choices.
"""
if choices is None:
return
self.choices = choices
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
digicosmos86
left a comment
There was a problem hiding this comment.
Thanks @cpaniaguam! Adding a few comments. Let me know what you think
src/hssm/rl/rlssm.py
Outdated
There was a problem hiding this comment.
This is OK for v1 where we pass all configs through rlssm_config, but we should discuss what the API looks like for RLSSM. @AlexanderFengler @krishnbera please let us know what you think. We talked about having separate arguments like decision_process to get likelihood functions from users, just like HSSM.
Also, in keeping the same API with HSSM, this argument should still be model_config but accepting a different type.
In the original Config hierarchy, Config is the object that the class internally keeps, and ModelConfig is the object that the user provides, and can parse the dictionary in place of a ModelConfig class. I think we can also inherit this hierarchy for RLSSMConfig
There was a problem hiding this comment.
Will rename rlssm_config as model_config.
src/hssm/rl/rlssm.py
Outdated
| # Build a typed Config instance via RLSSMConfig's own factory method. | ||
| # The differentiable Op is passed so Config.validate() is satisfied; | ||
| # loglik_kind="approx_differentiable" reflects that the Op has gradients. | ||
| config = rlssm_config._build_model_config(loglik_op) |
There was a problem hiding this comment.
The naming _build_model_config() can be a bit misleading here. As I mentioned above, ModelConfig is a class for accepting user-specified configs. How about combine_user_config or something like that?
Anyway, it seems that the config classes' naming is getting confusing. We should organize a little bit
| assert self.list_params is not None, "list_params should be set by HSSMBase" | ||
| params_is_trialwise = [name != "p_outlier" for name in self.list_params] | ||
| list_params = self.model_config.list_params | ||
| assert list_params is not None, "model_config.list_params must be set" |
There was a problem hiding this comment.
It's a good idea not to use assertions but throw actual informative error types (e.g. ValueError). Assertions can be disabled and these errors won't be caught
There was a problem hiding this comment.
I subscribe to that principle. This is for mypy purposes. I'll look into improving this.
| choices: list[int] | tuple[int, ...] | None, | ||
| loglik: Any = None, | ||
| ) -> Config: | ||
| """Build and return a validated Config for standard HSSM models. |
There was a problem hiding this comment.
This still feels weird. Rather than converting a RLSSMConfig into a Config for standard HSSM config, how about use a config field in the __init__ of HSSMBase class, which can be either a HSSMConfig or a RLSSMConfig. Then, in the __init__ of the derived classes, you build each config differently, and then pass the config to the config field in the derived classes? In other words, the __init__ can delegate building config to derived classes, and itself only focuses on storing the built config objects?
There was a problem hiding this comment.
This is addressed in this separate PR #936.
This pull request refactors the model configuration and initialization logic for the HSSM base class, moving the responsibility for constructing and validating model configurations out of
HSSMBaseand into the configuration classes themselves. This results in a cleaner, more modular design and simplifies subclassing and serialization. The most important changes are grouped below.Refactoring and Simplification of Model Configuration
HSSMBaseclass now requires a fully initializedBaseModelConfig(usually aConfig) to be passed in, rather than accepting a mix of model names, config dicts, and other parameters. All likelihood, parameter, and data information is now drawn from this object, and the constructor no longer builds the config itself. [1] [2] [3]ssms-simulators) has been moved to a new_build_model_configclassmethod onConfig. This method is now responsible for all config normalization and validation._build_model_configandget_config_classmethods have been removed fromHSSMBaseandBaseModelConfig, and the responsibility for config class resolution is now handled by the config classes themselves. [1] [2] [3] [4]Improved Initialization Argument Capture and Serialization
_store_init_argsprovides a robust way for subclasses to snapshot their initialization arguments, and a default empty snapshot is set in the base class to prevent serialization errors if subclasses forget to call it. [1] [2] [3]__getstate__method now raises a clear error if the initialization snapshot is missing, making the contract for serialization explicit and robust.API and Documentation Updates
HSSMBasehave been updated to reflect the new configuration workflow, clarifying that aBaseModelConfigis required and describing its role. [1] [2]BaseModelConfigand cleaned up inRLSSMConfig. [1] [2]Internal Imports and Logging
config.py. [1] [2] [3]