Skip to content

Add support for time-varying linear systems#250

Merged
DanWaxman merged 1 commit into
mainfrom
dw-time-varying-linear-gaussian
Jun 11, 2026
Merged

Add support for time-varying linear systems#250
DanWaxman merged 1 commit into
mainfrom
dw-time-varying-linear-gaussian

Conversation

@DanWaxman

Copy link
Copy Markdown
Collaborator

Previously, Kalman filters/smoothers only support time-invariant systems; to this end, both LinearGaussianStateEvolution and LinearGaussianObservation were time-invariant. This is a problem when trying to include proper discretization schemes for CD linear systems, where time-varying linear systems naturally arise with non-uniform sampling (cf. #226). This PR adds time-varying support for linear Gaussian models.

Main changes are to (i) the LinearGaussianStateEvolution interface, (ii) the LinearGaussianObservation interface, (iii) the KF/RTS Smoothing cuthbert integrations.

Changes (i) and (ii) are quite similar; they both allow for parameters to depend on (t_now,t_next)/t or be constant, in a potentially mixed way. They are resolved via a params_at function, so that data about the system may still be extracted for KF/RTS use.

This makes the changes for filtering and smoothing integrations somewhat minimal. They just go from accessing the info to using evo.params_at(t_now, t_next).

For dynamax (which I recall being somewhat fragile regarding LTV systems), we supply a is_time_invariant flag, and throw errors accordingly. We make additional guards at the integration stage later on as well.

Previously, Kalman filters/smoothers only support time-invariant systems; to this end, both LinearGaussianStateEvolution and LinearGaussianObservation were time-invariant. This is a problem when trying to include proper discretization schemes for CD linear systems, where time-varying linear systems naturally arise with non-uniform sampling.

Main changes are to (i) the LinearGaussianStateEvolution interface, (ii) the LinearGaussianObservation interface, (iii) the KF/RTS Smoothing cuthbert integrations.

Changes (i) and (ii) are quite similar; they both allow for parameters to depend on `(t_now,t_next)`/`t` or be constant, in a potentially mixed way. They are resolved via a `params_at` function, so that data about the system may still be extracted for KF/RTS use.

This makes the changes for filtering and smoothing integrations somewhat minimal. They just go from accessing the info to using `evo.params_at(t_now, t_next)`.

For `dynamax` (which I recall being somewhat fragile regarding LTV systems), we supply a `is_time_invariant` flag, and throw errors accordingly. We make additional guards at the integration stage later on as well.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Dynestyx’s linear-Gaussian model components to support time-varying (callable) parameters, enabling Kalman filtering/smoothing on irregular time grids (e.g., for better CT discretizations as in #226). It introduces a params_at(...) resolution layer so Kalman/RTS integrations can consume per-step resolved matrices while preserving the existing constant-parameter workflow, and it explicitly guards the cd_dynamax backend against unsupported callable parameters.

Changes:

  • Add callable-parameter support to LinearGaussianStateEvolution ((t_now, t_next) -> ...) and LinearGaussianObservation (t -> ...), plus params_at and is_time_invariant.
  • Update cuthbert discrete Kalman filter/smoother integrations to resolve parameters per time step via params_at.
  • Add cd_dynamax backend guards that raise TypeError when callable parameters are supplied; expand tests and docs accordingly.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_time_varying_linear_gaussian.py New end-to-end tests covering constant-equivalence, true time-variation, mixed callable/constant cases, plates, gradients, simulator behavior, and cd_dynamax rejection.
tests/test_models_core.py Unit tests for callable-field equivalence, time dependence, and dimension inference/shape validation with callable fields.
dynestyx/models/state_evolution.py Allow callable transition parameters; add LinearGaussianParams, params_at(t_now,t_next), and is_time_invariant.
dynestyx/models/observations.py Allow callable observation parameters; add LinearGaussianObservationParams, params_at(t), and is_time_invariant.
dynestyx/models/init.py Export the new *Params named tuples in the public models namespace.
dynestyx/inference/filter_configs.py Document backend support constraints for time-varying linear-Gaussian models.
dynestyx/inference/smoother_configs.py Document backend support constraints for time-varying linear-Gaussian models.
dynestyx/inference/integrations/cuthbert/discrete_filter.py Resolve observation params via params_at; add per-step Kalman parameter builders for time-varying support.
dynestyx/inference/integrations/cuthbert/discrete_smoother.py Reuse the new Kalman dynamics parameter builder for smoother integration.
dynestyx/inference/integrations/cd_dynamax/utils.py Add centralized guard to reject callable linear-Gaussian fields for cd_dynamax paths.
dynestyx/inference/integrations/cd_dynamax/discrete_filter.py Enforce constant-parameter requirement for cd_dynamax discrete KF conversion.
dynestyx/init.py Re-export new LinearGaussianParams / LinearGaussianObservationParams at top-level API.
docs/api_reference/public/models/specialized/linear_gaussian_state_evolution.md Document callable parameter usage and params_at for time-varying transitions.
docs/api_reference/public/models/specialized/linear_gaussian_observation.md Document callable parameter usage and params_at for time-varying observations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread dynestyx/inference/integrations/cd_dynamax/utils.py
obs = dsx_model.observation_model
if not isinstance(obs, LinearGaussianObservation):
raise TypeError("dsx_to_cdlgssm_params requires LinearGaussianObservation.")
_require_constant_linear_gaussian_fields(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm fine with this---we could probably support Dynamax's time-varying KF. It expects A.shape = (times, state, state), so would just have to pre-build the extra dimension. Doesn't seem worth the hassle here.

Comment thread dynestyx/inference/integrations/cd_dynamax/utils.py
Comment thread dynestyx/inference/integrations/cuthbert/discrete_smoother.py
from dynestyx.models.core import DiscreteTimeStateEvolution


class LinearGaussianParams(NamedTuple):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is basically the same as the Observation version of this class, other than names. Makes me wonder if our LinearGaussian classes should not end in Observation or StateEvolution...they can all just have something like state2out_matrix, control2out_matrix, bias, cov

@mattlevine22 mattlevine22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like things work here and the PR accomplishes its mission, so approving!

One question I'll let you think about and/or leave for later.

  • Should we unify LinearGaussianObservation and LinearGaussianStateEvolution into a single class? or at least just have them shadow a single underlying class? ...same question w.r.t. the new Params version of these classes. I realized these are all literally the same other than field names.

@DanWaxman

Copy link
Copy Markdown
Collaborator Author

Should we unify LinearGaussianObservation and LinearGaussianStateEvolution into a single class? or at least just have them shadow a single underlying class? ...same question w.r.t. the new Params version of these classes. I realized these are all literally the same other than field names.

They're not with callables -- state evolution has signature (x, t_now, t_next) -> ... while observation has (x, t) -> ....

@DanWaxman DanWaxman merged commit f1ed608 into main Jun 11, 2026
4 checks passed
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.

3 participants