dapper.mods

Contains models included with DAPPER.

See the README section on test cases (models) for a table overview of the included models.

Defining your own model

Below is a sugested structuring followed by most models already within DAPPER. However, you are free to organize your model as you see fit, as long as it culminates in the definition of one or more HiddenMarkovModel. For the sake of modularity, try not to import stuff from DAPPER outside of dapper.mods and liveplotting.

  • Make a directory: my_model. It does not have to reside within the dapper/mods folder, but make sure to look into some of the other dirs thereunder as examples, for example dapper/mods/DoublePendulum.

  • Make a file: my_model/__init__.py to hold the core workings of the model. Further details are given below, but the main work lies in defining a step(x, t, dt) function (you can name it however you like, but step is the convention), to implement the dynamical model/system mapping the state x from one time t to another t + dt.

  • Make a file: my_model/demo.py to run step and visually showcase a simulation of the model without any DA, and verify it's working.

  • Make a file: my_model/my_settings_1.py that defines (or "configures", since there is usually little programming logic and flow taking place) a complete HiddenMarkovModel ready for a synthetic experiment (also called "twin experiment" or OSSE).

  • Once you've made some experiments you believe are noteworthy you should add a "suggested settings/tunings" section in comments at the bottom of my_model/my_settings_1.py, listing some of the relevant DA method configurations that you tested, along with the RMSE (or other stats) that you obtained for those methods. You will find plenty of examples already in DAPPER, used for cross-referenced with literature to verify the workings of DAPPER (and the reproducibility of publications).

Details on my_model/__init__.py

  • The step function must support 2D-array (i.e. ensemble) and 1D-array (single realization) input, and return output of the same number of dimensions (as the input). See

    To begin with, test whether the model works on 1 realization, before running it with several (simultaneously). Also, start with a small integration time step, before using more efficient/adventurous time steps. Note that the time step might need to be shorter in assimilation, because it may cause instabilities.

    Most models are defined using simple procedural style. However, dapper.mods.LorenzUV and dapper.mods.QG use OOP, which is perhaps more robust when different control-variable settings are to be investigated. The choice is yours.

    In parameter estimation problems, the parameters are treated as input variables to the "forward model". This does not necessarily require OOP. See examples/param_estim.py.

  • Optional: define a suggested/example initial state, x0. This facilitates the specification of initial conditions for different synthetic experiments, as random variables centred on x0. It is also a convenient way just to specify the system size as len(x0). In many experiments, the specific value of x0 does not matter, because most systems are chaotic, and the average of the stats are computed only for time > BurnIn > 0, which will not depend on x0 if the experiment is long enough. Nevertheless, it's often convenient to pre-define a point on the attractor, or basin, or at least ensure "physicality", for quicker spin-up (burn-in).

  • Optional: define a number called Tplot which defines the (sliding) time window used by the liveplotting of diagnostics.

  • Optional: To use the (extended) Kalman filter, or 4D-Var, you will need to define the model linearization, typically called dstep_dx. Note: this only needs to support 1D input (single realization).

  1"""Contains models included with DAPPER.
  2
  3.. include:: ./README.md
  4"""
  5
  6import copy as cp
  7import inspect
  8from pathlib import Path
  9
 10import numpy as np
 11import struct_tools
 12
 13# Imports used to set up HMMs
 14import dapper.tools.progressbar as pb
 15from dapper.dpr_config import rc
 16from dapper.mods.utils import Id_op
 17from dapper.tools.chronos import Chronology
 18from dapper.tools.localization import no_localization
 19from dapper.tools.matrices import CovMat
 20from dapper.tools.randvars import RV, GaussRV
 21from dapper.tools.seeding import set_seed
 22
 23from .integration import with_recursion, with_rk4
 24from .utils import Id_Obs, ens_compatible, linspace_int, partial_Id_Obs
 25
 26
 27class HiddenMarkovModel(struct_tools.NicePrint):
 28    """Container class (with some embellishments) for a Hidden Markov Model (HMM).
 29
 30    Should contain the details necessary to run synthetic DA experiments,
 31    also known as "twin experiment", or OSSE (observing system simulation experiment).
 32    The synthetic truth and observations may then be obtained by running
 33    `HiddenMarkovModel.simulate`.
 34
 35    .. note::
 36      Each model included with DAPPER comes with several examples
 37      of model settings from the literature.
 38      See, for example, `dapper.mods.Lorenz63.sakov2012`.
 39
 40    .. warning::
 41      These example configurations do not necessarily hold a high programming standard,
 42      as they may have been whipped up at short notice to replicate some experiments,
 43      and are not intended for re-use.
 44      Nevertheless, sometimes they are re-used by another configuration script,
 45      leading to a major gotcha/pitfall: changes made to the imported `HMM` (or
 46      the model's module itself) also impact the original object (since they
 47      are mutable and thereby referenced).  This *usually* isn't an issue, since
 48      one rarely imports two/more separate configurations. However, the test suite
 49      imports all configurations, which might then unintentionally interact.
 50      To avoid this, you should use the `copy` method of the `HMM`
 51      before making any changes to it.
 52
 53
 54    Parameters
 55    ----------
 56    Dyn: `Operator` or dict
 57        Operator for the dynamics.
 58    Obs: `Operator` or dict
 59        Operator for the observations
 60        Can also be time-dependent, ref `TimeDependentOperator`.
 61    tseq: `dapper.tools.chronos.Chronology`
 62        Time sequence of the HMM process.
 63    X0: `dapper.tools.randvars.RV`
 64        Random distribution of initial condition
 65    liveplotters: `list`, optional
 66        A list of tuples. See example use in function `LPs` of `dapper.mods.Lorenz63`.
 67        - The first element of the tuple determines if the liveplotter
 68        is shown by default. If `False`, the liveplotter is only shown when
 69        included among the `liveplots` argument of `assimilate`
 70        - The second element in the tuple gives the corresponding liveplotter
 71        function/class.
 72    sectors: `dict`, optional
 73        Labelled indices referring to parts of the state vector.
 74        When defined, field-mean statistics are computed for each sector.
 75        Example use can be found in  `examples/param_estim.py`
 76        and `dapper/mods/Lorenz96/miyoshi2011.py`
 77    name: str, optional
 78        Label for the `HMM`.
 79    """
 80
 81    def __init__(self, *args, **kwargs):
 82        # Expected args/kwargs, along with type and default.
 83        attrs = dict(Dyn=(Operator, None),
 84                     Obs=(TimeDependentOperator, None),
 85                     tseq=(Chronology, None),
 86                     X0=(RV, None),
 87                     liveplotters=(list, []),
 88                     sectors=(dict, {}),
 89                     name=(str, self._default_name()))
 90
 91        # Transfer args to kwargs
 92        for arg, kw in zip(args, attrs):
 93            assert (kw not in kwargs), "Could not sort out arguments."
 94            kwargs[kw] = arg
 95
 96        # Un-abbreviate
 97        abbrevs = {"LP": "liveplotters",
 98                   "loc": "localizer"}
 99        for key in list(kwargs):
100            try:
101                full = abbrevs[key]
102            except KeyError:
103                pass
104            else:
105                assert (full not in kwargs), "Could not sort out arguments."
106                kwargs[full] = kwargs.pop(key)
107
108        # Convert dict to object
109        for key, (type_, default) in attrs.items():
110            val = kwargs.get(key, default)
111            if not isinstance(val, type_) and val is not None:
112                val = type_(**val)
113            kwargs[key] = val
114
115        # Transfer kwargs to self
116        for key in attrs:
117            setattr(self, key, kwargs.pop(key))
118        assert not kwargs, (
119            f"Arguments {list(kwargs)} not recognized. "
120            "If you want, you can still write them to the HMM, "
121            "but this must be done after initialisation.")
122
123        # Further defaults
124        # if not hasattr(self.Obs, "localizer"):
125        #     self.Obs.localizer = no_localization(self.Nx, self.Ny)
126
127        # Validation
128        # if self.Obs.noise.C == 0 or self.Obs.noise.C.rk != self.Obs.noise.C.M:
129        #     raise ValueError("Rank-deficient R not supported.")
130
131    # ndim shortcuts
132    @property
133    def Nx(self): return self.Dyn.M
134
135    printopts = {'ordering': ['Dyn', 'Obs', 'tseq', 'X0'], "indent": 4}
136
137    def simulate(self, desc='Truth & Obs'):
138        """Generate synthetic truth and observations."""
139        Dyn, Obs, tseq, X0 = self.Dyn, self.Obs, self.tseq, self.X0
140
141        # Init
142        xx = np.zeros((tseq.K + 1, Dyn.M))
143        yy = np.empty(tseq.Ko + 1, dtype=object)
144
145        x = X0.sample(1).squeeze()
146        xx[0] = x
147
148        # Loop
149        for k, ko, t, dt in pb.progbar(tseq.ticker, desc):
150            x = Dyn(x, t-dt, dt)
151            x = x + np.sqrt(dt)*Dyn.noise.sample(1).squeeze()
152            if ko is not None:
153                yy[ko] = Obs(ko)(x) + Obs(ko).noise.sample(1).squeeze()
154            xx[k] = x
155
156        return xx, yy
157
158    def copy(self):
159        return cp.deepcopy(self)
160
161    @staticmethod
162    def _default_name():
163        name = inspect.getfile(inspect.stack()[2][0])
164        try:
165            name = str(Path(name).relative_to(rc.dirs.dapper/'mods'))
166        except ValueError:
167            name = str(Path(name))
168        return name
169
170
171class TimeDependentOperator:
172    """Wrapper for `Operator` that enables time dependence.
173
174    The time instance should be specified by `ko`,
175    i.e. the index of an observation time.
176
177    Examples: `examples/time-dep-obs-operator.py` and `dapper/mods/QG/sakov2008.py`.
178    """
179    def __init__(self, **kwargs):
180        """Can be initialized like `Operator`, in which case the resulting
181        object will always return the same `Operator` nomatter the input time.
182
183        If initialized with 1 argument: `dict(time_dependent=func)`
184        then `func` must return an `Operator` object.
185        """
186        try:
187            fun = kwargs['time_dependent']
188            assert len(kwargs) == 1
189            assert callable(fun)
190            self.Ops = fun
191        except KeyError:
192            self.Op1 = Operator(**kwargs)
193
194    def __repr__(self):
195        return "<" + type(self).__name__ + '> ' + str(self)
196
197    def __str__(self):
198        if hasattr(self, 'Op1'):
199            return 'CONSTANT operator sepcified by .Op1:\n' + repr(self.Op1)
200        else:
201            return '.Ops: ' + repr(self.Ops)
202
203    def __call__(self, ko):
204        try:
205            return self.Ops(ko)
206        except AttributeError:
207            return self.Op1
208
209
210class Operator(struct_tools.NicePrint):
211    """Container for the dynamical and the observational maps.
212
213    Parameters
214    ----------
215    M: int
216        Length of output vectors.
217    model: function
218        The actual operator.
219    noise: RV, optional
220        The associated additive noise. The noise can also be a scalar or an
221        array, producing `GaussRV(C=noise)`.
222
223    Any remaining keyword arguments are written to the object as attributes.
224    """
225
226    def __init__(self, M, model=None, noise=None, **kwargs):
227        self.M = M
228
229        # Default to the Identity operator
230        if model is None:
231            model = Id_op()
232            kwargs['linear'] = lambda *args: np.eye(M)
233        # Assign
234        self.model = model
235
236        # None/0 => No noise
237        if isinstance(noise, RV):
238            self.noise = noise
239        else:
240            if noise is None:
241                noise = 0
242            if np.isscalar(noise):
243                self.noise = GaussRV(C=noise, M=M)
244            else:
245                self.noise = GaussRV(C=noise)
246
247        # Write attributes
248        for key, value in kwargs.items():
249            setattr(self, key, value)
250
251    def __call__(self, *args, **kwargs):
252        return self.model(*args, **kwargs)
253
254    printopts = {'ordering': ['M', 'model', 'noise'], "indent": 4}
class HiddenMarkovModel(struct_tools.NicePrint):
 28class HiddenMarkovModel(struct_tools.NicePrint):
 29    """Container class (with some embellishments) for a Hidden Markov Model (HMM).
 30
 31    Should contain the details necessary to run synthetic DA experiments,
 32    also known as "twin experiment", or OSSE (observing system simulation experiment).
 33    The synthetic truth and observations may then be obtained by running
 34    `HiddenMarkovModel.simulate`.
 35
 36    .. note::
 37      Each model included with DAPPER comes with several examples
 38      of model settings from the literature.
 39      See, for example, `dapper.mods.Lorenz63.sakov2012`.
 40
 41    .. warning::
 42      These example configurations do not necessarily hold a high programming standard,
 43      as they may have been whipped up at short notice to replicate some experiments,
 44      and are not intended for re-use.
 45      Nevertheless, sometimes they are re-used by another configuration script,
 46      leading to a major gotcha/pitfall: changes made to the imported `HMM` (or
 47      the model's module itself) also impact the original object (since they
 48      are mutable and thereby referenced).  This *usually* isn't an issue, since
 49      one rarely imports two/more separate configurations. However, the test suite
 50      imports all configurations, which might then unintentionally interact.
 51      To avoid this, you should use the `copy` method of the `HMM`
 52      before making any changes to it.
 53
 54
 55    Parameters
 56    ----------
 57    Dyn: `Operator` or dict
 58        Operator for the dynamics.
 59    Obs: `Operator` or dict
 60        Operator for the observations
 61        Can also be time-dependent, ref `TimeDependentOperator`.
 62    tseq: `dapper.tools.chronos.Chronology`
 63        Time sequence of the HMM process.
 64    X0: `dapper.tools.randvars.RV`
 65        Random distribution of initial condition
 66    liveplotters: `list`, optional
 67        A list of tuples. See example use in function `LPs` of `dapper.mods.Lorenz63`.
 68        - The first element of the tuple determines if the liveplotter
 69        is shown by default. If `False`, the liveplotter is only shown when
 70        included among the `liveplots` argument of `assimilate`
 71        - The second element in the tuple gives the corresponding liveplotter
 72        function/class.
 73    sectors: `dict`, optional
 74        Labelled indices referring to parts of the state vector.
 75        When defined, field-mean statistics are computed for each sector.
 76        Example use can be found in  `examples/param_estim.py`
 77        and `dapper/mods/Lorenz96/miyoshi2011.py`
 78    name: str, optional
 79        Label for the `HMM`.
 80    """
 81
 82    def __init__(self, *args, **kwargs):
 83        # Expected args/kwargs, along with type and default.
 84        attrs = dict(Dyn=(Operator, None),
 85                     Obs=(TimeDependentOperator, None),
 86                     tseq=(Chronology, None),
 87                     X0=(RV, None),
 88                     liveplotters=(list, []),
 89                     sectors=(dict, {}),
 90                     name=(str, self._default_name()))
 91
 92        # Transfer args to kwargs
 93        for arg, kw in zip(args, attrs):
 94            assert (kw not in kwargs), "Could not sort out arguments."
 95            kwargs[kw] = arg
 96
 97        # Un-abbreviate
 98        abbrevs = {"LP": "liveplotters",
 99                   "loc": "localizer"}
100        for key in list(kwargs):
101            try:
102                full = abbrevs[key]
103            except KeyError:
104                pass
105            else:
106                assert (full not in kwargs), "Could not sort out arguments."
107                kwargs[full] = kwargs.pop(key)
108
109        # Convert dict to object
110        for key, (type_, default) in attrs.items():
111            val = kwargs.get(key, default)
112            if not isinstance(val, type_) and val is not None:
113                val = type_(**val)
114            kwargs[key] = val
115
116        # Transfer kwargs to self
117        for key in attrs:
118            setattr(self, key, kwargs.pop(key))
119        assert not kwargs, (
120            f"Arguments {list(kwargs)} not recognized. "
121            "If you want, you can still write them to the HMM, "
122            "but this must be done after initialisation.")
123
124        # Further defaults
125        # if not hasattr(self.Obs, "localizer"):
126        #     self.Obs.localizer = no_localization(self.Nx, self.Ny)
127
128        # Validation
129        # if self.Obs.noise.C == 0 or self.Obs.noise.C.rk != self.Obs.noise.C.M:
130        #     raise ValueError("Rank-deficient R not supported.")
131
132    # ndim shortcuts
133    @property
134    def Nx(self): return self.Dyn.M
135
136    printopts = {'ordering': ['Dyn', 'Obs', 'tseq', 'X0'], "indent": 4}
137
138    def simulate(self, desc='Truth & Obs'):
139        """Generate synthetic truth and observations."""
140        Dyn, Obs, tseq, X0 = self.Dyn, self.Obs, self.tseq, self.X0
141
142        # Init
143        xx = np.zeros((tseq.K + 1, Dyn.M))
144        yy = np.empty(tseq.Ko + 1, dtype=object)
145
146        x = X0.sample(1).squeeze()
147        xx[0] = x
148
149        # Loop
150        for k, ko, t, dt in pb.progbar(tseq.ticker, desc):
151            x = Dyn(x, t-dt, dt)
152            x = x + np.sqrt(dt)*Dyn.noise.sample(1).squeeze()
153            if ko is not None:
154                yy[ko] = Obs(ko)(x) + Obs(ko).noise.sample(1).squeeze()
155            xx[k] = x
156
157        return xx, yy
158
159    def copy(self):
160        return cp.deepcopy(self)
161
162    @staticmethod
163    def _default_name():
164        name = inspect.getfile(inspect.stack()[2][0])
165        try:
166            name = str(Path(name).relative_to(rc.dirs.dapper/'mods'))
167        except ValueError:
168            name = str(Path(name))
169        return name

Container class (with some embellishments) for a Hidden Markov Model (HMM).

Should contain the details necessary to run synthetic DA experiments, also known as "twin experiment", or OSSE (observing system simulation experiment). The synthetic truth and observations may then be obtained by running HiddenMarkovModel.simulate.

Each model included with DAPPER comes with several examples of model settings from the literature. See, for example, dapper.mods.Lorenz63.sakov2012.

These example configurations do not necessarily hold a high programming standard, as they may have been whipped up at short notice to replicate some experiments, and are not intended for re-use. Nevertheless, sometimes they are re-used by another configuration script, leading to a major gotcha/pitfall: changes made to the imported HMM (or the model's module itself) also impact the original object (since they are mutable and thereby referenced). This usually isn't an issue, since one rarely imports two/more separate configurations. However, the test suite imports all configurations, which might then unintentionally interact. To avoid this, you should use the copy method of the HMM before making any changes to it.

Parameters
  • Dyn (Operator or dict): Operator for the dynamics.
  • Obs (Operator or dict): Operator for the observations Can also be time-dependent, ref TimeDependentOperator.
  • tseq (dapper.tools.chronos.Chronology): Time sequence of the HMM process.
  • X0 (dapper.tools.randvars.RV): Random distribution of initial condition
  • liveplotters (list, optional): A list of tuples. See example use in function LPs of dapper.mods.Lorenz63.
    • The first element of the tuple determines if the liveplotter is shown by default. If False, the liveplotter is only shown when included among the liveplots argument of assimilate
    • The second element in the tuple gives the corresponding liveplotter function/class.
  • sectors (dict, optional): Labelled indices referring to parts of the state vector. When defined, field-mean statistics are computed for each sector. Example use can be found in examples/param_estim.py and dapper/mods/Lorenz96/miyoshi2011.py
  • name (str, optional): Label for the HMM.
HiddenMarkovModel(*args, **kwargs)
 82    def __init__(self, *args, **kwargs):
 83        # Expected args/kwargs, along with type and default.
 84        attrs = dict(Dyn=(Operator, None),
 85                     Obs=(TimeDependentOperator, None),
 86                     tseq=(Chronology, None),
 87                     X0=(RV, None),
 88                     liveplotters=(list, []),
 89                     sectors=(dict, {}),
 90                     name=(str, self._default_name()))
 91
 92        # Transfer args to kwargs
 93        for arg, kw in zip(args, attrs):
 94            assert (kw not in kwargs), "Could not sort out arguments."
 95            kwargs[kw] = arg
 96
 97        # Un-abbreviate
 98        abbrevs = {"LP": "liveplotters",
 99                   "loc": "localizer"}
100        for key in list(kwargs):
101            try:
102                full = abbrevs[key]
103            except KeyError:
104                pass
105            else:
106                assert (full not in kwargs), "Could not sort out arguments."
107                kwargs[full] = kwargs.pop(key)
108
109        # Convert dict to object
110        for key, (type_, default) in attrs.items():
111            val = kwargs.get(key, default)
112            if not isinstance(val, type_) and val is not None:
113                val = type_(**val)
114            kwargs[key] = val
115
116        # Transfer kwargs to self
117        for key in attrs:
118            setattr(self, key, kwargs.pop(key))
119        assert not kwargs, (
120            f"Arguments {list(kwargs)} not recognized. "
121            "If you want, you can still write them to the HMM, "
122            "but this must be done after initialisation.")
123
124        # Further defaults
125        # if not hasattr(self.Obs, "localizer"):
126        #     self.Obs.localizer = no_localization(self.Nx, self.Ny)
127
128        # Validation
129        # if self.Obs.noise.C == 0 or self.Obs.noise.C.rk != self.Obs.noise.C.M:
130        #     raise ValueError("Rank-deficient R not supported.")
Nx
133    @property
134    def Nx(self): return self.Dyn.M
printopts = {'ordering': ['Dyn', 'Obs', 'tseq', 'X0'], 'indent': 4}
def simulate(self, desc='Truth & Obs'):
138    def simulate(self, desc='Truth & Obs'):
139        """Generate synthetic truth and observations."""
140        Dyn, Obs, tseq, X0 = self.Dyn, self.Obs, self.tseq, self.X0
141
142        # Init
143        xx = np.zeros((tseq.K + 1, Dyn.M))
144        yy = np.empty(tseq.Ko + 1, dtype=object)
145
146        x = X0.sample(1).squeeze()
147        xx[0] = x
148
149        # Loop
150        for k, ko, t, dt in pb.progbar(tseq.ticker, desc):
151            x = Dyn(x, t-dt, dt)
152            x = x + np.sqrt(dt)*Dyn.noise.sample(1).squeeze()
153            if ko is not None:
154                yy[ko] = Obs(ko)(x) + Obs(ko).noise.sample(1).squeeze()
155            xx[k] = x
156
157        return xx, yy

Generate synthetic truth and observations.

def copy(self):
159    def copy(self):
160        return cp.deepcopy(self)
class TimeDependentOperator:
172class TimeDependentOperator:
173    """Wrapper for `Operator` that enables time dependence.
174
175    The time instance should be specified by `ko`,
176    i.e. the index of an observation time.
177
178    Examples: `examples/time-dep-obs-operator.py` and `dapper/mods/QG/sakov2008.py`.
179    """
180    def __init__(self, **kwargs):
181        """Can be initialized like `Operator`, in which case the resulting
182        object will always return the same `Operator` nomatter the input time.
183
184        If initialized with 1 argument: `dict(time_dependent=func)`
185        then `func` must return an `Operator` object.
186        """
187        try:
188            fun = kwargs['time_dependent']
189            assert len(kwargs) == 1
190            assert callable(fun)
191            self.Ops = fun
192        except KeyError:
193            self.Op1 = Operator(**kwargs)
194
195    def __repr__(self):
196        return "<" + type(self).__name__ + '> ' + str(self)
197
198    def __str__(self):
199        if hasattr(self, 'Op1'):
200            return 'CONSTANT operator sepcified by .Op1:\n' + repr(self.Op1)
201        else:
202            return '.Ops: ' + repr(self.Ops)
203
204    def __call__(self, ko):
205        try:
206            return self.Ops(ko)
207        except AttributeError:
208            return self.Op1

Wrapper for Operator that enables time dependence.

The time instance should be specified by ko, i.e. the index of an observation time.

Examples: examples/time-dep-obs-operator.py and dapper/mods/QG/sakov2008.py.

TimeDependentOperator(**kwargs)
180    def __init__(self, **kwargs):
181        """Can be initialized like `Operator`, in which case the resulting
182        object will always return the same `Operator` nomatter the input time.
183
184        If initialized with 1 argument: `dict(time_dependent=func)`
185        then `func` must return an `Operator` object.
186        """
187        try:
188            fun = kwargs['time_dependent']
189            assert len(kwargs) == 1
190            assert callable(fun)
191            self.Ops = fun
192        except KeyError:
193            self.Op1 = Operator(**kwargs)

Can be initialized like Operator, in which case the resulting object will always return the same Operator nomatter the input time.

If initialized with 1 argument: dict(time_dependent=func) then func must return an Operator object.

class Operator(struct_tools.NicePrint):
211class Operator(struct_tools.NicePrint):
212    """Container for the dynamical and the observational maps.
213
214    Parameters
215    ----------
216    M: int
217        Length of output vectors.
218    model: function
219        The actual operator.
220    noise: RV, optional
221        The associated additive noise. The noise can also be a scalar or an
222        array, producing `GaussRV(C=noise)`.
223
224    Any remaining keyword arguments are written to the object as attributes.
225    """
226
227    def __init__(self, M, model=None, noise=None, **kwargs):
228        self.M = M
229
230        # Default to the Identity operator
231        if model is None:
232            model = Id_op()
233            kwargs['linear'] = lambda *args: np.eye(M)
234        # Assign
235        self.model = model
236
237        # None/0 => No noise
238        if isinstance(noise, RV):
239            self.noise = noise
240        else:
241            if noise is None:
242                noise = 0
243            if np.isscalar(noise):
244                self.noise = GaussRV(C=noise, M=M)
245            else:
246                self.noise = GaussRV(C=noise)
247
248        # Write attributes
249        for key, value in kwargs.items():
250            setattr(self, key, value)
251
252    def __call__(self, *args, **kwargs):
253        return self.model(*args, **kwargs)
254
255    printopts = {'ordering': ['M', 'model', 'noise'], "indent": 4}

Container for the dynamical and the observational maps.

Parameters
  • M (int): Length of output vectors.
  • model (function): The actual operator.
  • noise (RV, optional): The associated additive noise. The noise can also be a scalar or an array, producing GaussRV(C=noise).
  • Any remaining keyword arguments are written to the object as attributes.
Operator(M, model=None, noise=None, **kwargs)
227    def __init__(self, M, model=None, noise=None, **kwargs):
228        self.M = M
229
230        # Default to the Identity operator
231        if model is None:
232            model = Id_op()
233            kwargs['linear'] = lambda *args: np.eye(M)
234        # Assign
235        self.model = model
236
237        # None/0 => No noise
238        if isinstance(noise, RV):
239            self.noise = noise
240        else:
241            if noise is None:
242                noise = 0
243            if np.isscalar(noise):
244                self.noise = GaussRV(C=noise, M=M)
245            else:
246                self.noise = GaussRV(C=noise)
247
248        # Write attributes
249        for key, value in kwargs.items():
250            setattr(self, key, value)
M
model
printopts = {'ordering': ['M', 'model', 'noise'], 'indent': 4}