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 thedapper/mods
folder, but make sure to look into some of the other dirs thereunder as examples, for exampledapper/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 astep(x, t, dt)
function (you can name it however you like, butstep
is the convention), to implement the dynamical model/system mapping the statex
from one timet
to anothert + dt
.Make a file:
my_model/demo.py
to runstep
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 completeHiddenMarkovModel
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). Seedapper.mods.Lorenz63
: use ofens_compatible
.dapper.mods.Lorenz96
: use of relatively clever slice notation.dapper.mods.LorenzUV
: use of cleverer slice notation:...
(ellipsis). Consider pre-defining the slices like so:iiX = (..., slice(None, Nx)) iiP = (..., slice(Nx, None))
to abbreviate the indexing elsewhere.
dapper.mods.QG
: use of parallelized for loop (map).
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
anddapper.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 onx0
. It is also a convenient way just to specify the system size aslen(x0)
. In many experiments, the specific value ofx0
does not matter, because most systems are chaotic, and the average of the stats are computed only fortime > BurnIn > 0
, which will not depend onx0
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}
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, refTimeDependentOperator
. - 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 functionLPs
ofdapper.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 theliveplots
argument ofassimilate
- The second element in the tuple gives the corresponding liveplotter function/class.
- The first element of the tuple determines if the liveplotter
is shown by default. If
- 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 inexamples/param_estim.py
anddapper/mods/Lorenz96/miyoshi2011.py
- name (str, optional):
Label for the
HMM
.
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.")
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.
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
.
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)
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.
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)