Repository: gammapy/gammapy
Pull request #5336 (2024-06-20 09:33:17)

Backport PR #5320 on branch v1.0.x (Fix parameters unique names)
'Backport PR #5320: Fix parameters unique names'


Patch:
diff --git a/gammapy/modeling/models/core.py b/gammapy/modeling/models/core.py
index f675bf01f..8782dd9d0 100644
--- a/gammapy/modeling/models/core.py
+++ b/gammapy/modeling/models/core.py
@@ -163,6 +163,10 @@ class ModelBase:
             [getattr(self, name) for name in self.default_parameters.names]
         )
 
+    @property
+    def parameters_unique_names(self):
+        return self.parameters.unique_parameters.names
+
     @copy_covariance
     def copy(self, **kwargs):
         """A deep copy."""
@@ -384,12 +388,11 @@ class DatasetModels(collections.abc.Sequence):
     def parameters_unique_names(self):
         """List of unique parameter names as model_name.par_type.par_name"""
         names = []
-        for model in self:
-            for par in model.parameters:
-                components = [model.name, par.type, par.name]
+        for model in self._models:
+            for par_name in model.parameters_unique_names:
+                components = [model.name, par_name]
                 name = ".".join(components)
                 names.append(name)
-
         return names
 
     @property
diff --git a/gammapy/modeling/models/cube.py b/gammapy/modeling/models/cube.py
index a00c956e5..6913aa780 100644
--- a/gammapy/modeling/models/cube.py
+++ b/gammapy/modeling/models/cube.py
@@ -154,6 +154,17 @@ class SkyModel(ModelBase):
 
         return Parameters.from_stack(parameters)
 
+    @property
+    def parameters_unique_names(self):
+        """List of unique parameter names. Return formatted as par_type.par_name."""
+        names = []
+        for model in self._models:
+            for par_name in model.parameters_unique_names:
+                components = [model.type, par_name]
+                name = ".".join(components)
+                names.append(name)
+        return names
+
     @property
     def spatial_model(self):
         """`~gammapy.modeling.models.SpatialModel`"""
@@ -620,6 +631,10 @@ class FoVBackgroundModel(ModelBase):
         """Spectral norm model"""
         return self._spectral_model
 
+    @property
+    def _models(self):
+        return [self.spectral_model]
+
     @property
     def name(self):
         """Model name"""
@@ -632,6 +647,17 @@ class FoVBackgroundModel(ModelBase):
         parameters.append(self.spectral_model.parameters)
         return Parameters.from_stack(parameters)
 
+    @property
+    def parameters_unique_names(self):
+        """List of unique parameter names. Return formatted as par_type.par_name."""
+        names = []
+        for model in self._models:
+            for par_name in model.parameters_unique_names:
+                components = [model.type, par_name]
+                name = ".".join(components)
+                names.append(name)
+        return names
+
     def __str__(self):
         str_ = f"{self.__class__.__name__}\n\n"
 
@@ -843,12 +869,28 @@ class TemplateNPredModel(ModelBase):
             raise TypeError(f"Invalid type: {model!r}")
         self._spectral_model = model
 
+    @property
+    def _models(self):
+        models = [self.spectral_model]
+        return [model for model in models if model is not None]
+
     @property
     def parameters(self):
         parameters = []
         parameters.append(self.spectral_model.parameters)
         return Parameters.from_stack(parameters)
 
+    @property
+    def parameters_unique_names(self):
+        """List of unique parameter names. Return formatted as par_type.par_name."""
+        names = []
+        for model in self._models:
+            for par_name in model.parameters_unique_names:
+                components = [model.type, par_name]
+                name = ".".join(components)
+                names.append(name)
+        return names
+
     def evaluate(self):
         """Evaluate background model.
 
diff --git a/gammapy/modeling/models/spectral.py b/gammapy/modeling/models/spectral.py
index 37bd4dc9e..320dd6b68 100644
--- a/gammapy/modeling/models/spectral.py
+++ b/gammapy/modeling/models/spectral.py
@@ -3,6 +3,7 @@
 import logging
 import operator
 import os
+from pathlib import Path
 import numpy as np
 import scipy.optimize
 import scipy.special
@@ -12,7 +13,6 @@ from astropy.table import Table
 from astropy.utils.decorators import classproperty
 from astropy.visualization import quantity_support
 import matplotlib.pyplot as plt
-from pathlib import Path
 from gammapy.maps import MapAxis, RegionNDMap
 from gammapy.modeling import Parameter, Parameters
 from gammapy.utils.integrate import trapz_loglog
@@ -628,10 +628,24 @@ class CompoundSpectralModel(SpectralModel):
         self.operator = operator
         super().__init__()
 
+    @property
+    def _models(self):
+        return [self.model1, self.model2]
+
     @property
     def parameters(self):
         return self.model1.parameters + self.model2.parameters
 
+    @property
+    def parameters_unique_names(self):
+        names = []
+        for idx, model in enumerate(self._models):
+            for par_name in model.parameters_unique_names:
+                components = [f"model{idx+1}", par_name]
+                name = ".".join(components)
+                names.append(name)
+        return names
+
     def __str__(self):
         return (
             f"{self.__class__.__name__}\n"


Test patch:
diff --git a/gammapy/modeling/models/tests/test_io.py b/gammapy/modeling/models/tests/test_io.py
index e874acf48..5d2c4e986 100644
--- a/gammapy/modeling/models/tests/test_io.py
+++ b/gammapy/modeling/models/tests/test_io.py
@@ -1,6 +1,5 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 import operator
-
 import pytest
 import numpy as np
 from numpy.testing import assert_allclose
@@ -9,19 +8,19 @@ from astropy.utils.data import get_pkg_data_filename
 from gammapy.maps import Map, MapAxis, RegionNDMap
 from gammapy.modeling.models import (
     MODEL_REGISTRY,
+    CompoundSpectralModel,
     ConstantTemporalModel,
     EBLAbsorptionNormSpectralModel,
     ExpDecayTemporalModel,
     FoVBackgroundModel,
     GaussianTemporalModel,
     LinearTemporalModel,
+    LogParabolaSpectralModel,
     Model,
     Models,
     PiecewiseNormSpectralModel,
     PowerLawSpectralModel,
-    LogParabolaSpectralModel,
     PowerLawTemporalModel,
-    CompoundSpectralModel,
     SineTemporalModel,
     SkyModel,
     TemplateNPredModel,
@@ -451,11 +450,11 @@ def test_compound_models_io(tmp_path):
     assert_allclose(sk1.covariance.data, sk.covariance.data, rtol=1e-3)
     assert_allclose(np.sum(sk1.covariance.data), 0.0)
     assert Models([sk]).parameters_unique_names == [
-        "model.spectral.index",
-        "model.spectral.amplitude",
-        "model.spectral.reference",
-        "model.spectral.amplitude",
-        "model.spectral.reference",
-        "model.spectral.alpha",
-        "model.spectral.beta",
+        "model.spectral.model1.index",
+        "model.spectral.model1.amplitude",
+        "model.spectral.model1.reference",
+        "model.spectral.model2.amplitude",
+        "model.spectral.model2.reference",
+        "model.spectral.model2.alpha",
+        "model.spectral.model2.beta",
     ]
diff --git a/gammapy/modeling/tests/test_fit.py b/gammapy/modeling/tests/test_fit.py
index f9838bb45..21e2a06ce 100644
--- a/gammapy/modeling/tests/test_fit.py
+++ b/gammapy/modeling/tests/test_fit.py
@@ -219,7 +219,7 @@ def test_stat_profile():
     dataset.models.parameters["x"].scan_n_values = 3
     result = fit.stat_profile(datasets=[dataset], parameter="x")
 
-    assert_allclose(result["test.model.x_scan"], [0, 2, 4], atol=1e-7)
+    assert_allclose(result["test.x_scan"], [0, 2, 4], atol=1e-7)
     assert_allclose(result["stat_scan"], [4, 0, 4], atol=1e-7)
     assert len(result["fit_results"]) == 0
 
@@ -236,7 +236,7 @@ def test_stat_profile_reoptimize():
     dataset.models.parameters["x"].scan_n_values = 3
     result = fit.stat_profile(datasets=[dataset], parameter="x", reoptimize=True)
 
-    assert_allclose(result["test.model.x_scan"], [0, 2, 4], atol=1e-7)
+    assert_allclose(result["test.x_scan"], [0, 2, 4], atol=1e-7)
     assert_allclose(result["stat_scan"], [4, 0, 4], atol=1e-7)
     assert_allclose(
         result["fit_results"][0].total_stat, result["stat_scan"][0], atol=1e-7
@@ -255,8 +255,8 @@ def test_stat_surface():
     dataset.models.parameters["y"].scan_values = y_values
     result = fit.stat_surface(datasets=[dataset], x="x", y="y")
 
-    assert_allclose(result["test.model.x_scan"], x_values, atol=1e-7)
-    assert_allclose(result["test.model.y_scan"], y_values, atol=1e-7)
+    assert_allclose(result["test.x_scan"], x_values, atol=1e-7)
+    assert_allclose(result["test.y_scan"], y_values, atol=1e-7)
     expected_stat = [
         [1.0001e04, 1.0000e00, 1.0001e04],
         [1.0000e04, 0.0000e00, 1.0000e04],
@@ -284,8 +284,8 @@ def test_stat_surface_reoptimize():
 
     result = fit.stat_surface(datasets=[dataset], x="x", y="y", reoptimize=True)
 
-    assert_allclose(result["test.model.x_scan"], x_values, atol=1e-7)
-    assert_allclose(result["test.model.y_scan"], y_values, atol=1e-7)
+    assert_allclose(result["test.x_scan"], x_values, atol=1e-7)
+    assert_allclose(result["test.y_scan"], y_values, atol=1e-7)
     expected_stat = [
         [1.0001e04, 1.0000e00, 1.0001e04],
         [1.0000e04, 0.0000e00, 1.0000e04],
@@ -307,11 +307,11 @@ def test_stat_contour():
 
     assert result["success"]
 
-    x = result["test.model.y"]
+    x = result["test.y"]
     assert len(x) in [10, 11]  # Behavior changed after iminuit>=2.13
     assert_allclose(x[0], 299, rtol=1e-5)
     assert_allclose(x[9], 299.133975, rtol=1e-5)
-    y = result["test.model.z"]
+    y = result["test.z"]
     assert len(x) == len(y)
     assert len(y) in [10, 11]
     assert_allclose(y[0], 0.04, rtol=1e-5)