CUQIpy 1.2.0.post0.dev380__py3-none-any.whl → 1.2.0.post0.dev409__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of CUQIpy might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: CUQIpy
3
- Version: 1.2.0.post0.dev380
3
+ Version: 1.2.0.post0.dev409
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  cuqi/__init__.py,sha256=LsGilhl-hBLEn6Glt8S_l0OJzAA1sKit_rui8h-D-p0,488
2
2
  cuqi/_messages.py,sha256=fzEBrZT2kbmfecBBPm7spVu7yHdxGARQB4QzXhJbCJ0,415
3
- cuqi/_version.py,sha256=4Rygq2rO32tD5TYtalGmZY_AqICH80eNKpSu2m7Zj5A,510
3
+ cuqi/_version.py,sha256=RYvEybWqRsq9GwSGi6oR2Z_p9IMo81rXzeZ27cuK2ts,510
4
4
  cuqi/config.py,sha256=wcYvz19wkeKW2EKCGIKJiTpWt5kdaxyt4imyRkvtTRA,526
5
5
  cuqi/diagnostics.py,sha256=5OrbJeqpynqRXOe5MtOKKhe7EAVdOEpHIqHnlMW9G_c,3029
6
6
  cuqi/array/__init__.py,sha256=-EeiaiWGNsE3twRS4dD814BIlfxEsNkTCZUc5gjOXb0,30
@@ -34,11 +34,13 @@ cuqi/distribution/_posterior.py,sha256=zAfL0GECxekZ2lBt1W6_LN0U_xskMwK4VNce5xAF7
34
34
  cuqi/distribution/_smoothed_laplace.py,sha256=p-1Y23mYA9omwiHGkEuv3T2mwcPAAoNlCr7T8osNkjE,2925
35
35
  cuqi/distribution/_truncated_normal.py,sha256=sZkLYgnkGOyS_3ZxY7iw6L62t-Jh6shzsweRsRepN2k,4240
36
36
  cuqi/distribution/_uniform.py,sha256=KA8yQ6ZS3nQGS4PYJ4hpDg6Eq8EQKQvPsIpYfR8fj2w,1967
37
- cuqi/experimental/__init__.py,sha256=iStrmEy4ZMnGpEyd6QNlC6RK83lrS9iRkxQS0u-s8cU,105
37
+ cuqi/experimental/__init__.py,sha256=6PFlmAkWuxWhzVrZz2g10tBBDuH5542G02nIRQfQCNg,128
38
38
  cuqi/experimental/algebra/__init__.py,sha256=btRAWG58ZfdtK0afXKOg60AX7d76KMBjlZa4AWBCCgU,81
39
- cuqi/experimental/algebra/_ast.py,sha256=iJ_umDzTct4O9tZM-ep2NNkrdxR4_PTIEOrxZiwvlc0,9257
40
- cuqi/experimental/algebra/_orderedset.py,sha256=8SxktP1333ByTldtqzU9xLQ5SAFU0V9B-i6U1prVBYk,2019
41
- cuqi/experimental/algebra/_randomvariable.py,sha256=lwOTy9KApqXwJ57VBYUBMrCwpbA7cR91OlT0ZUtiIaE,15841
39
+ cuqi/experimental/algebra/_ast.py,sha256=PdPz19cJMjvnMx4KEzhn4gvxIZX_UViE33Mbttj_5Xw,9873
40
+ cuqi/experimental/algebra/_orderedset.py,sha256=fKysh4pmI4xF7Y5Z6O86ABzg20o4uBs-v8jmLBMrdpo,2849
41
+ cuqi/experimental/algebra/_randomvariable.py,sha256=1VwJjsF5PPmkchGa7mbNCcAgnt19olkvMeHCRAvEVtk,18911
42
+ cuqi/experimental/geometry/__init__.py,sha256=kgoKegfz3Jhr7fpORB_l55z9zLZRtloTLyXFDh1oF2o,47
43
+ cuqi/experimental/geometry/_productgeometry.py,sha256=G-hIYnfLiRS5IWD2EPXORNBKNP2zSaCCHAeBlDC_R3I,7177
42
44
  cuqi/experimental/mcmc/__init__.py,sha256=zSqLZmxOqQ-F94C9-gPv7g89TX1XxlrlNm071Eb167I,4487
43
45
  cuqi/experimental/mcmc/_conjugate.py,sha256=VNPQkGity0mposcqxrx4UIeXm35EvJvZED4p2stffvA,9924
44
46
  cuqi/experimental/mcmc/_conjugate_approx.py,sha256=uEnY2ea9su5ivcNagyRAwpQP2gBY98sXU7N0y5hTADo,3653
@@ -61,7 +63,7 @@ cuqi/implicitprior/_regularizedGaussian.py,sha256=mzaAHq0yz73FZo-OB2iqFMd2i2NNzV
61
63
  cuqi/implicitprior/_regularizedUnboundedUniform.py,sha256=H2fTOSqYTlDiLxQ7Ya6wnpCUIkpO4qKrkTOsOPnBBeU,3483
62
64
  cuqi/implicitprior/_restorator.py,sha256=Z350XUJEt7N59Qw-SIUaBljQNDJk4Zb0i_KRFrt2DCg,10087
63
65
  cuqi/likelihood/__init__.py,sha256=QXif382iwZ5bT3ZUqmMs_n70JVbbjxbqMrlQYbMn4Zo,1776
64
- cuqi/likelihood/_likelihood.py,sha256=z3AXAbIrv_DjOYh4jy3iDHemuIFUUJu6wdvJ5e2dgW0,6913
66
+ cuqi/likelihood/_likelihood.py,sha256=PuW8ufRefLt6w40JQWqNnEh3YCLxu4pz0h0PcpT8inc,7075
65
67
  cuqi/model/__init__.py,sha256=jgY2-jyxEMC79vkyH9BpfowW7_DbMRjqedOtO5fykXQ,62
66
68
  cuqi/model/_model.py,sha256=LqeMwOSb1oIGpT7g1cmItP_2Q4dmgg8eNPNo0joPUyg,32905
67
69
  cuqi/operator/__init__.py,sha256=0pc9p-KPyl7KtPV0noB0ddI0CP2iYEHw5rbw49D8Njk,136
@@ -91,8 +93,8 @@ cuqi/testproblem/_testproblem.py,sha256=x769LwwRdJdzIiZkcQUGb_5-vynNTNALXWKato7s
91
93
  cuqi/utilities/__init__.py,sha256=H7xpJe2UinjZftKvE2JuXtTi4DqtkR6uIezStAXwfGg,428
92
94
  cuqi/utilities/_get_python_variable_name.py,sha256=wxpCaj9f3ZtBNqlGmmuGiITgBaTsY-r94lUIlK6UAU4,2043
93
95
  cuqi/utilities/_utilities.py,sha256=Jc4knn80vLoA7kgw9FzXwKVFGaNBOXiA9kgvltZU3Ao,11777
94
- CUQIpy-1.2.0.post0.dev380.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
95
- CUQIpy-1.2.0.post0.dev380.dist-info/METADATA,sha256=pio8eeEPi2sbNpoalD1ZWlftxpKJMt3vin-Y0LRsJLo,18529
96
- CUQIpy-1.2.0.post0.dev380.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
97
- CUQIpy-1.2.0.post0.dev380.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
98
- CUQIpy-1.2.0.post0.dev380.dist-info/RECORD,,
96
+ CUQIpy-1.2.0.post0.dev409.dist-info/LICENSE,sha256=kJWRPrtRoQoZGXyyvu50Uc91X6_0XRaVfT0YZssicys,10799
97
+ CUQIpy-1.2.0.post0.dev409.dist-info/METADATA,sha256=VJRAgO9AwDhgUSbg2IV38biOj-znvhQVvT8lmr_bePo,18529
98
+ CUQIpy-1.2.0.post0.dev409.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
99
+ CUQIpy-1.2.0.post0.dev409.dist-info/top_level.txt,sha256=AgmgMc6TKfPPqbjV0kvAoCBN334i_Lwwojc7HE3ZwD0,5
100
+ CUQIpy-1.2.0.post0.dev409.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
cuqi/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-12-20T09:13:49+0100",
11
+ "date": "2025-01-14T23:39:18+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "7405af566d2a50ba59fc861865d41c08f8dcf160",
15
- "version": "1.2.0.post0.dev380"
14
+ "full-revisionid": "4d0949ea5c777b95d3c216f22c17e267e5cf15ff",
15
+ "version": "1.2.0.post0.dev409"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,3 +1,4 @@
1
1
  """ Experimental module for testing new features and ideas. """
2
2
  from . import mcmc
3
3
  from . import algebra
4
+ from . import geometry
@@ -51,6 +51,11 @@ class Node(ABC):
51
51
  """Evaluate node at a given parameter value. This will traverse the sub-tree originated at this node and evaluate it given the recorded operations."""
52
52
  pass
53
53
 
54
+ @abstractmethod
55
+ def condition(self, **kwargs):
56
+ """ Conditions the tree by replacing any VariableNode with a ValueNode if the variable is in the kwargs dictionary. """
57
+ pass
58
+
54
59
  @abstractmethod
55
60
  def __repr__(self):
56
61
  """String representation of the node. Used for printing the AST."""
@@ -129,6 +134,9 @@ class UnaryNode(Node, ABC):
129
134
  def __init__(self, child: Node):
130
135
  self.child = child
131
136
 
137
+ def condition(self, **kwargs):
138
+ return self.__class__(self.child.condition(**kwargs))
139
+
132
140
 
133
141
  class BinaryNode(Node, ABC):
134
142
  """Base class for all binary nodes in the abstract syntax tree.
@@ -155,6 +163,9 @@ class BinaryNode(Node, ABC):
155
163
  self.left = left
156
164
  self.right = right
157
165
 
166
+ def condition(self, **kwargs):
167
+ return self.__class__(self.left.condition(**kwargs), self.right.condition(**kwargs))
168
+
158
169
  def __repr__(self):
159
170
  return f"{self.left} {self.op_symbol} {self.right}"
160
171
 
@@ -205,6 +216,11 @@ class VariableNode(Node):
205
216
  )
206
217
  return kwargs[self.name]
207
218
 
219
+ def condition(self, **kwargs):
220
+ if self.name in kwargs:
221
+ return ValueNode(kwargs[self.name])
222
+ return self
223
+
208
224
  def __repr__(self):
209
225
  return self.name
210
226
 
@@ -226,6 +242,9 @@ class ValueNode(Node):
226
242
  """Returns the value of the node."""
227
243
  return self.value
228
244
 
245
+ def condition(self, **kwargs):
246
+ return self
247
+
229
248
  def __repr__(self):
230
249
  return str(self.value)
231
250
 
@@ -19,6 +19,13 @@ class _OrderedSet:
19
19
  """
20
20
  self.dict[item] = None
21
21
 
22
+ def remove(self, item):
23
+ """Remove an item from the set.
24
+
25
+ If the item is not in the set, it raises a KeyError.
26
+ """
27
+ del self.dict[item]
28
+
22
29
  def __contains__(self, item):
23
30
  """Check if an item is in the set.
24
31
 
@@ -47,6 +54,18 @@ class _OrderedSet:
47
54
  for item in other:
48
55
  self.add(item)
49
56
 
57
+ def replace(self, old_item, new_item):
58
+ """Replace old_item with new_item at the same position, preserving order."""
59
+ if old_item not in self.dict:
60
+ raise KeyError(f"{old_item} not in set")
61
+
62
+ items = list(self.dict.keys()) # Preserve order
63
+ index = items.index(old_item) # Find position
64
+ items[index] = new_item # Replace at the same position
65
+
66
+ # Reconstruct the ordered set with the new item in place
67
+ self.dict = dict.fromkeys(items)
68
+
50
69
  def __or__(self, other):
51
70
  """Return a new set that is the union of this set and another set.
52
71
 
@@ -57,3 +76,7 @@ class _OrderedSet:
57
76
  new_set = _OrderedSet(self.dict.keys())
58
77
  new_set.extend(other)
59
78
  return new_set
79
+
80
+ def __repr__(self):
81
+ """Return a string representation of the set."""
82
+ return "_OrderedSet({})".format(list(self.dict.keys()))
@@ -5,8 +5,7 @@ from ._orderedset import _OrderedSet
5
5
  import operator
6
6
  import cuqi
7
7
  from cuqi.distribution import Distribution
8
- from copy import copy
9
-
8
+ from copy import copy, deepcopy
10
9
 
11
10
  class RandomVariable:
12
11
  """ Random variable defined by a distribution with the option to apply algebraic operations on it.
@@ -210,7 +209,7 @@ class RandomVariable:
210
209
  def parameter_names(self) -> str:
211
210
  """ Name of the parameter that the random variable can be evaluated at. """
212
211
  self._inject_name_into_distribution()
213
- return [distribution.name for distribution in self.distributions] # Consider renaming .name to .par_name for distributions
212
+ return [distribution._name for distribution in self.distributions] # Consider renaming .name to .par_name for distributions
214
213
 
215
214
  @property
216
215
  def dim(self):
@@ -239,7 +238,57 @@ class RandomVariable:
239
238
  def is_transformed(self):
240
239
  """ Returns True if the random variable is transformed. """
241
240
  return not isinstance(self.tree, VariableNode)
242
-
241
+
242
+ @property
243
+ def is_cond(self):
244
+ """ Returns True if the random variable is a conditional random variable. """
245
+ return any(dist.is_cond for dist in self.distributions)
246
+
247
+ def condition(self, *args, **kwargs):
248
+ """Condition the random variable on a given value. Only one of either positional or keyword arguments can be passed.
249
+
250
+ Parameters
251
+ ----------
252
+ *args : Any
253
+ Positional arguments to condition the random variable on. The order of the arguments must match the order of the parameter names.
254
+
255
+ **kwargs : Any
256
+ Keyword arguments to condition the random variable on. The keys must match the parameter names.
257
+
258
+ """
259
+
260
+ # Before conditioning, capture repr to ensure all variable names are injected
261
+ self.__repr__()
262
+
263
+ if args and kwargs:
264
+ raise ValueError("Cannot pass both positional and keyword arguments to RandomVariable")
265
+
266
+ if args:
267
+ kwargs = self._parse_args_add_to_kwargs(args, kwargs)
268
+
269
+ # Create a deep copy of the random variable to ensure the original tree is not modified
270
+ new_variable = self._make_copy(deep=True)
271
+
272
+ for kwargs_name in list(kwargs.keys()):
273
+ value = kwargs.pop(kwargs_name)
274
+
275
+ # Condition the tree turning the variable into a constant
276
+ if kwargs_name in self.parameter_names:
277
+ new_variable._tree = new_variable.tree.condition(**{kwargs_name: value})
278
+
279
+ # Condition the random variable on both the distribution parameter name and distribution conditioning variables
280
+ for dist in self.distributions:
281
+ if kwargs_name == dist.name:
282
+ new_variable._remove_distribution(dist.name)
283
+ elif kwargs_name in dist.get_conditioning_variables():
284
+ new_variable._replace_distribution(dist.name, dist(**{kwargs_name: value}))
285
+
286
+ # Check if any kwargs are left unprocessed
287
+ if kwargs:
288
+ raise ValueError(f"Conditioning variables {list(kwargs.keys())} not found in the random variable {self}")
289
+
290
+ return new_variable
291
+
243
292
  @property
244
293
  def _non_default_args(self) -> List[str]:
245
294
  """List of non-default arguments to distribution. This is used to return the correct
@@ -247,13 +296,31 @@ class RandomVariable:
247
296
  """
248
297
  return self.parameter_names
249
298
 
299
+ def _replace_distribution(self, name, new_distribution):
300
+ """ Replace distribution with a given name with a new distribution in the same position of the ordered set. """
301
+ for dist in self.distributions:
302
+ if dist._name == name:
303
+ self._distributions.replace(dist, new_distribution)
304
+ break
305
+
306
+ def _remove_distribution(self, name):
307
+ """ Remove distribution with a given name from the set of distributions. """
308
+ for dist in self.distributions:
309
+ if dist._name == name:
310
+ self._distributions.remove(dist)
311
+ break
312
+
250
313
  def _inject_name_into_distribution(self, name=None):
251
314
  if len(self._distributions) == 1:
252
315
  dist = next(iter(self._distributions))
316
+
317
+ if dist._is_copy:
318
+ dist = dist._original_density
319
+
253
320
  if dist._name is None:
254
321
  if name is None:
255
322
  name = self.name
256
- dist._name = name
323
+ dist.name = name # Inject using setter
257
324
 
258
325
  def _parse_args_add_to_kwargs(self, args, kwargs) -> dict:
259
326
  """ Parse args and add to kwargs if any. Arguments follow self.parameter_names order. """
@@ -293,8 +360,12 @@ class RandomVariable:
293
360
  """ Returns True if this is a copy of another random variable, e.g. by conditioning. """
294
361
  return hasattr(self, '_original_variable') and self._original_variable is not None
295
362
 
296
- def _make_copy(self):
297
- """ Returns a shallow copy of the density keeping a pointer to the original. """
363
+ def _make_copy(self, deep=False) -> 'RandomVariable':
364
+ """ Returns a copy of the density keeping a pointer to the original. """
365
+ if deep:
366
+ new_variable = deepcopy(self)
367
+ new_variable._original_variable = self
368
+ return new_variable
298
369
  new_variable = copy(self)
299
370
  new_variable._distributions = copy(self.distributions)
300
371
  new_variable._tree = copy(self._tree)
@@ -0,0 +1 @@
1
+ from ._productgeometry import _ProductGeometry
@@ -0,0 +1,181 @@
1
+ from cuqi.geometry import Geometry
2
+ import numpy as np
3
+
4
+ class _ProductGeometry(Geometry):
5
+ """ A class for representing a product geometry. A product geometry
6
+ represents the product space of multiple geometries of type :class:`Geometry`.
7
+ See the example below for a product geometry of two geometries.
8
+
9
+ Parameters
10
+ ----------
11
+ \*geometries : cuqi.geometry.Geometry
12
+ The geometries to be combined into a product geometry. Each geometry
13
+ is passed as a comma-separated argument.
14
+
15
+ Example
16
+ -------
17
+ .. code-block:: python
18
+ import numpy as np
19
+ from cuqi.geometry import Continuous1D, Discrete
20
+ from cuqi.experimental.geometry import _ProductGeometry
21
+ geometry1 = Continuous1D(np.linspace(0, 1, 100))
22
+ geometry2 = Discrete(["sound_speed"])
23
+ product_geometry = _ProductGeometry(geometry1, geometry2)
24
+ """
25
+
26
+ def __init__(self, *geometries):
27
+ self.geometries = geometries
28
+
29
+ @property
30
+ def geometries(self):
31
+ """List of geometries that are combined to form the product geometry."""
32
+ return self._geometries
33
+
34
+ @geometries.setter
35
+ def geometries(self, geometries):
36
+ # Check if all geometries are of type Geometry.
37
+ for g in geometries:
38
+ if not isinstance(g, Geometry):
39
+ raise TypeError(
40
+ "All geometries must be of type Geometry. "
41
+ "Received: {}".format(type(g))
42
+ )
43
+ self._geometries = geometries
44
+
45
+ @property
46
+ def fun_shape(self):
47
+ """Shape of the function representation. Returns a tuple, where
48
+ each element of the tuple is the shape of the function
49
+ representation of each geometry."""
50
+ return tuple([g.fun_shape for g in self.geometries])
51
+
52
+ @property
53
+ def fun_dim(self):
54
+ """Dimension of the function representation which is the sum of
55
+ the function representation dimensions of each geometry."""
56
+ return sum([g.fun_dim for g in self.geometries])
57
+
58
+ @property
59
+ def par_shape(self):
60
+ """Shape of the parameter representation. Returns a tuple, where
61
+ each element of the tuple is the shape of the parameter
62
+ representation of each geometry."""
63
+ return tuple([g.par_shape for g in self.geometries])
64
+
65
+ @property
66
+ def par_dim(self):
67
+ """Dimension of the parameter representation which is the sum of
68
+ the parameter representation dimensions of each geometry."""
69
+ return sum(self.par_dim_list)
70
+
71
+ @property
72
+ def par_dim_list(self):
73
+ """List of the parameter representation dimensions of each
74
+ geometry. This property is useful for indexing a stacked parameter
75
+ vector."""
76
+ return [g.par_dim for g in self.geometries]
77
+
78
+ @property
79
+ def stacked_par_split_indices(self):
80
+ """Indices at which the stacked parameter vector should be split
81
+ to obtain the parameter vectors for each geometry. For example, if
82
+ the stacked parameter vector is [1, 2, 3, 4, 5, 6] and the parameter
83
+ vectors for each geometry are [1, 2], [3, 4], and [5, 6], then the
84
+ split indices are [2, 4]"""
85
+ return np.cumsum(self.par_dim_list[:-1])
86
+
87
+ @property
88
+ def number_of_geometries(self):
89
+ """Number of geometries in the product geometry."""
90
+ return len(self.geometries)
91
+
92
+ def _split_par(self, par):
93
+ """Splits a stacked parameter vector into parameter vectors for each
94
+ geometry."""
95
+ return tuple(np.split(par, self.stacked_par_split_indices))
96
+
97
+ def _plot(self, values, **kwargs):
98
+ """Plotting function for the product geometry."""
99
+ raise NotImplementedError(
100
+ f"Plotting not implemented for {self.__class__.__name__}.")
101
+
102
+ def par2fun(self, *pars):
103
+ """Converts parameter vector(s) into function values for each
104
+ geometry. The parameter vector can be stacked (all parameters are
105
+ in one vector) or unstacked (one parameter vector corresponds to
106
+ each geometry). In all cases, the order of the parameter vectors
107
+ should follow the order of the geometries in the product, i.e., the
108
+ first parameter vector corresponds to the first geometry and so on."""
109
+
110
+ # If one argument is passed, then it is assumed that the parameter
111
+ # vector is stacked and split it.
112
+ # No effect if the parameter vector is already split and corresponds
113
+ # to one geometry.
114
+ if len(pars) == 1:
115
+ pars = self._split_par(pars[0])
116
+
117
+ # Convert parameter vectors to function values for each geometry.
118
+ funvals = []
119
+ for i, g in enumerate(self.geometries):
120
+ funval_i = g.par2fun(pars[i])
121
+ funvals.append(funval_i)
122
+ return tuple(funvals)
123
+
124
+ def fun2par(self, *funvals, stacked=False):
125
+ """Converts (multiple) function values into the corresponding
126
+ parameter vectors. If the flag stacked is set to True, then the
127
+ parameter vectors are stacked into one vector. Otherwise, the
128
+ parameter vectors are returned as a tuple. The order of function
129
+ values should follow the order of the geometries in the product,
130
+ i.e., the first function value corresponds to the first geometry
131
+ and so on."""
132
+
133
+ pars = []
134
+ for i, g in enumerate(self.geometries):
135
+ par_i = g.fun2par(funvals[i])
136
+ pars.append(par_i)
137
+
138
+ # stack parameters:
139
+ if stacked:
140
+ # if single sample
141
+ if len(pars[0].shape) == 1:
142
+ stacked_val = np.hstack(pars)
143
+ elif len(pars[0].shape) == 2:
144
+ stacked_val = np.vstack(pars)
145
+ else:
146
+ raise ValueError(
147
+ "Cannot stack parameter vectors with more than 2 dimensions."
148
+ )
149
+
150
+ return stacked_val if stacked else tuple(pars)
151
+
152
+ def vec2fun(self, *funvecs):
153
+ """Maps function vector representation, if available, to function
154
+ values. The order of the function vectors should follow the order of
155
+ the geometries in the product, i.e., the first function vector
156
+ corresponds to the first geometry and so on."""
157
+ funvals = []
158
+ for i, g in enumerate(self.geometries):
159
+ funvals.append(g.vec2fun(funvecs[i]))
160
+
161
+ return tuple(funvals)
162
+
163
+ def fun2vec(self, *funvals):
164
+ """Maps function values to a vector representation of the function
165
+ values, if available. The order of the function values should follow
166
+ the order of the geometries in the product, i.e., the first function
167
+ value corresponds to the first geometry and so on."""
168
+ funvecs = []
169
+ for i, g in enumerate(self.geometries):
170
+ funvecs.append(g.fun2vec(funvals[i]))
171
+
172
+ return tuple(funvecs)
173
+
174
+
175
+ def __repr__(self) -> str:
176
+ """Representation of the product geometry."""
177
+ string = "{}(".format(self.__class__.__name__) + "\n"
178
+ for g in self.geometries:
179
+ string += "\t{}\n".format(g.__repr__())
180
+ string += ")"
181
+ return string
@@ -43,6 +43,14 @@ class Likelihood(Density):
43
43
  def name(self, value):
44
44
  self.distribution.name = value
45
45
 
46
+ @property
47
+ def _name(self):
48
+ return self.distribution._name
49
+
50
+ @_name.setter
51
+ def _name(self, value):
52
+ self.distribution._name = value
53
+
46
54
  @property
47
55
  def FD_enabled(self):
48
56
  """ Return FD_enabled of the likelihood from the underlying distribution """