Repository: astropy/photutils

Issue:
Forced photometry with fixed position still seems to be fitting a position
Has anybody had this issue? 

photutils 1.10.0
astropy 6.0.0

I have a situation where, in some cases, I need to fix the x_0 and y_0 position of my psf and just fit for flux. A snippet of my code:

`pos = Table(names=["x_0", "y_0", "flux_init"], data=[(xout,), (yout,), (ap_out["counts_bkgsub"][0],)])`
`psf.x_0.fixed = True`
`psf.y_0.fixed = True`

Where `xout`, `yout` and `ap_out["counts_bkgsub"][0]` are just my initial x, y and flux values and `psf` is a photutils compatible model output from WebbPSF. I run PSFPhotometry like so:

`photometry = PSFPhotometry(`
            `grouper=None,  #no grouper needed, one well-defined source`
            `localbkg_estimator=None,`
           ` psf_model=psf,`
           ` fitter=fitter,`
            `fit_shape=(fitbox,fitbox),`
            `aperture_radius=r[f[0].header["FILTER"]]`
       ` )`

`result_tab = photometry(data=data.data[i]-apphot_bg, init_params=pos, mask=dq.data[i] > 0)`

And my output (just one example) looks like the following, even with psf.x_0.fixed set to True (Highlighted x_0, y_0 and x_fit, y_fit):

`id group_id local_bkg       **x_init           y_init**         flux_init           **x_fit             y_fit**            flux_fit      x_err y_err      flux_err      npixfit group_size        qfit                cfit         flags
--- -------- --------- ----------------- --------------- ----------------- ----------------- ----------------- ------------------ ----- ----- ------------------ ------- ---------- ------------------ -------------------- -----
  1        1       0.0 **82.54984351468708 84.413051744848** 66.68241135466099 **83.00775477794804 84.52412607853876** 193.02504841041116   nan   nan 45.951291469995844     118          1 2.2353772326890318 -0.17924899612524195     1

Note how the error on x and y is nan, as if they _have_ been fixed, but x_fit and y_fit differ from their initial values, as if they _haven't_ been fixed

I'm at a loss as to how to fix this right now, so I thought I'd post an issue and see if anybody else had experienced this. Just a note, fixing psf position with almost the same code worked fine in 1.8.0.

Thanks in advance, sorry if my formatting sucks but hopefully it's readable :)

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index cbf50db3..5c76dd2b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -26,6 +26,10 @@ Bug Fixes
     raise an error with ``PSFPhotometry`` if the fit did not converge.
     [#1672]
 
+  - Fixed an issue where ``GriddedPSFModel`` fixed model parameters were
+    not respected when copying the model or fitting with the PSF
+    photometry classes. [#1679]
+
 API Changes
 ^^^^^^^^^^^
 
diff --git a/photutils/psf/griddedpsfmodel.py b/photutils/psf/griddedpsfmodel.py
index 115e58f8..30b761ce 100644
--- a/photutils/psf/griddedpsfmodel.py
+++ b/photutils/psf/griddedpsfmodel.py
@@ -446,14 +446,29 @@ class GriddedPSFModel(ModelGridPlotMixin, Fittable2DModel):
 
     def copy(self):
         """
-        Return a copy of this model.
+        Return a copy of this model where only the model parameters are
+        copied.
 
-        Note that the ePSF grid data is not copied. Use the `deepcopy`
-        method if you want to copy the ePSF grid data.
+        All other copied model attributes are references to the
+        original model. This prevents copying the ePSF grid data, which
+        may contain a large array.
+
+        This method is useful if one is interested in only changing
+        the model parameters in a model copy. It is used in the PSF
+        photometry classes during model fitting.
+
+        Use the `deepcopy` method if you want to copy all of the model
+        attributes, including the ePSF grid data.
         """
-        return self.__class__(self._nddata, flux=self.flux.value,
-                              x_0=self.x_0.value, y_0=self.y_0.value,
-                              fill_value=self.fill_value)
+        newcls = object.__new__(self.__class__)
+
+        for key, val in self.__dict__.items():
+            if key in self.param_names:  # copy only the parameter values
+                newcls.__dict__[key] = copy.deepcopy(val)
+            else:
+                newcls.__dict__[key] = val
+
+        return newcls
 
     def deepcopy(self):
         """


Test patch:
diff --git a/photutils/psf/tests/test_griddedpsfmodel.py b/photutils/psf/tests/test_griddedpsfmodel.py
index 4426dd8f..64664d6b 100644
--- a/photutils/psf/tests/test_griddedpsfmodel.py
+++ b/photutils/psf/tests/test_griddedpsfmodel.py
@@ -203,10 +203,20 @@ class TestGriddedPSFModel:
     def test_copy(self, psfmodel):
         flux = psfmodel.flux.value
         new_model = psfmodel.copy()
+
+        assert_equal(new_model.data, psfmodel.data)
+        assert_equal(new_model.grid_xypos, psfmodel.grid_xypos)
+
         new_model.flux = 100
         assert new_model.flux.value != flux
         assert new_model._nddata is psfmodel._nddata
 
+        new_model.x_0.fixed = True
+        new_model.y_0.fixed = True
+        new_model2 = new_model.copy()
+        assert new_model2.x_0.fixed
+        assert new_model.fixed == new_model2.fixed
+
     def test_deepcopy(self, psfmodel):
         flux = psfmodel.flux.value
         new_model = psfmodel.deepcopy()