From 2538064070ba138f1172690705cf574b900f9b4a Mon Sep 17 00:00:00 2001 From: Jasper Everink Date: Fri, 20 Jun 2025 13:58:38 +0200 Subject: [PATCH 1/6] Set GMRF mean to a vector when scalar passed Makes it correctly work with LinearRTO now --- cuqi/distribution/_gmrf.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index b830551af6..14d801fd67 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -9,6 +9,8 @@ from cuqi.distribution import Distribution from cuqi.utilities import force_ndarray +import collections + class GMRF(Distribution): """ Gaussian Markov random field (GMRF). @@ -147,7 +149,12 @@ def mean(self): @mean.setter def mean(self, value): - self._mean = force_ndarray(value, flatten=True) + # Force the mean to be an array of proper length to be used in RegularizedLinearRTO sampler. + if isinstance(value, collections.abc.Iterable): + self._mean = force_ndarray(value, flatten=True) + else: + # Direct call to the private '_geometry' as the public 'geometry' tries to infer the dimension using the variable we currently setting. + self._mean = value*np.ones(self._geometry.par_dim) @property def prec(self): From 5f95a32c88af1d5353943f01a08b0fb6281f59f6 Mon Sep 17 00:00:00 2001 From: Jasper Everink Date: Fri, 20 Jun 2025 14:08:46 +0200 Subject: [PATCH 2/6] Fix conditioning on mean with GMRF --- cuqi/distribution/_gmrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index 14d801fd67..7fec634d8e 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -150,7 +150,7 @@ def mean(self): @mean.setter def mean(self, value): # Force the mean to be an array of proper length to be used in RegularizedLinearRTO sampler. - if isinstance(value, collections.abc.Iterable): + if isinstance(value, collections.abc.Iterable) or callable(value): self._mean = force_ndarray(value, flatten=True) else: # Direct call to the private '_geometry' as the public 'geometry' tries to infer the dimension using the variable we currently setting. From cbe34eab3acac74d764cdff1404f1d01309e82f7 Mon Sep 17 00:00:00 2001 From: Jasper Everink Date: Fri, 20 Jun 2025 14:16:01 +0200 Subject: [PATCH 3/6] Possible fix for initializing GMRF without geometry --- cuqi/distribution/_gmrf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index 7fec634d8e..9f8cccd095 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -99,14 +99,14 @@ def __init__(self, mean=None, prec=None, bc_type="zero", order=1, **kwargs): # Init from abstract distribution class super().__init__(**kwargs) + # Ensure geometry has shape + if not self._geometry.fun_shape or self._geometry.par_dim == 1: + raise ValueError(f"Distribution {self.__class__.__name__} must be initialized with supported geometry (geometry of which the fun_shape is not None) and has parameter dimension greater than 1.") + self.mean = mean self.prec = prec self._bc_type = bc_type - # Ensure geometry has shape - if not self.geometry.fun_shape or self.geometry.par_dim == 1: - raise ValueError(f"Distribution {self.__class__.__name__} must be initialized with supported geometry (geometry of which the fun_shape is not None) and has parameter dimension greater than 1.") - # Default physical_dim to geometry's dimension if not provided physical_dim = len(self.geometry.fun_shape) From 5ef38745703f921e8de2d2c630223be8e57fabd2 Mon Sep 17 00:00:00 2001 From: Jasper Everink Date: Fri, 20 Jun 2025 14:30:43 +0200 Subject: [PATCH 4/6] Undo --- cuqi/distribution/_gmrf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index 9f8cccd095..7fec634d8e 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -99,14 +99,14 @@ def __init__(self, mean=None, prec=None, bc_type="zero", order=1, **kwargs): # Init from abstract distribution class super().__init__(**kwargs) - # Ensure geometry has shape - if not self._geometry.fun_shape or self._geometry.par_dim == 1: - raise ValueError(f"Distribution {self.__class__.__name__} must be initialized with supported geometry (geometry of which the fun_shape is not None) and has parameter dimension greater than 1.") - self.mean = mean self.prec = prec self._bc_type = bc_type + # Ensure geometry has shape + if not self.geometry.fun_shape or self.geometry.par_dim == 1: + raise ValueError(f"Distribution {self.__class__.__name__} must be initialized with supported geometry (geometry of which the fun_shape is not None) and has parameter dimension greater than 1.") + # Default physical_dim to geometry's dimension if not provided physical_dim = len(self.geometry.fun_shape) From 1fc5b9ef3cd410e986aa949e9ca516b1a8568aa1 Mon Sep 17 00:00:00 2001 From: Jasper Everink Date: Fri, 20 Jun 2025 15:12:24 +0200 Subject: [PATCH 5/6] Error for GMRF edge case --- cuqi/distribution/_gmrf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index 7fec634d8e..672f1e70fb 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -96,6 +96,9 @@ class GMRF(Distribution): """ def __init__(self, mean=None, prec=None, bc_type="zero", order=1, **kwargs): + if not isinstance(mean, collections.abc.Iterable) and 'geometry' not in kwargs.keys(): + raise ValueError(f"Cannot infer dimension if scalar mean and no supported geometry is provided.") + # Init from abstract distribution class super().__init__(**kwargs) From f9c67bacc8ebad719972b9a82dc2ed2d56b78261 Mon Sep 17 00:00:00 2001 From: jeverink Date: Tue, 2 Sep 2025 10:13:57 +0200 Subject: [PATCH 6/6] Update cuqi/distribution/_gmrf.py Co-authored-by: amal-ghamdi --- cuqi/distribution/_gmrf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuqi/distribution/_gmrf.py b/cuqi/distribution/_gmrf.py index 672f1e70fb..f31560bbc2 100644 --- a/cuqi/distribution/_gmrf.py +++ b/cuqi/distribution/_gmrf.py @@ -152,7 +152,7 @@ def mean(self): @mean.setter def mean(self, value): - # Force the mean to be an array of proper length to be used in RegularizedLinearRTO sampler. + # Force the mean to be an array of proper length. if isinstance(value, collections.abc.Iterable) or callable(value): self._mean = force_ndarray(value, flatten=True) else: