Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions python/lsst/multiprofit/fitting/fit_psf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from abc import abstractmethod
from functools import cached_property
import logging
import math
import time
from typing import Any, ClassVar, Mapping, Type

Expand Down Expand Up @@ -83,6 +84,11 @@ class CatalogPsfFitterConfig(CatalogFitterConfig):
doc="PSF model configuration",
)
prior_axrat_mean = pexConfig.Field[float](default=0.95, doc="Mean for axis ratio prior")
sigma_min = pexConfig.Field[float](
default=0.8,
doc="Minimum sigma in pixels for PSF components. Must be >=0.8 to avoid undersampling.",
check=lambda x: x >= 0.8,
)

def make_psf_model(
self,
Expand Down Expand Up @@ -565,16 +571,26 @@ def fit(
for param in get_params_uniq(model_source, linear=False, channel=g2f.Channel.NONE, fixed=False)
if isinstance(param, g2f.ProperFractionParameterD)
)
# We're fitting the PSF, so there's nothing to convolve with
# We're fitting the PSF, so make a single Gaussian
model_psf = make_psf_model_null()
# Set the size to the minimum sigma to avoid undersampling
ellipse = model_psf.components[0].ellipse
ellipse.sigma_x_param.value = config.sigma_min
ellipse.sigma_y_param.value = config.sigma_min
sigma_min_sq = config.sigma_min**2

catalog = catexp.get_catalog()
n_rows = len(catalog)
range_idx = range(n_rows)

results, columns = config.make_catalog(n_rows)
prefix = config.prefix_column
columns_param = {f"{prefix}{key}": param for key, param in params.items()}
columns_param = {}
for key, param in params.items():
is_sigma = isinstance(param, g2f.SigmaXParameterD) or isinstance(param, g2f.SigmaYParameterD)
columns_param[f"{prefix}{key}"] = param, is_sigma
if is_sigma:
param.value = math.sqrt(max(param.value**2 - sigma_min_sq, 0.1))

# dummy size for first iteration
size, size_new = 0, 0
Expand Down Expand Up @@ -631,9 +647,12 @@ def fit(
if config.config_fit.eval_residual:
results[f"{prefix}n_eval_jac"][idx] = result_full.n_eval_jac

for (key, param), value in zip(columns_param.items(), result_full.params_best):
for (key, (param, is_sigma)), value in zip(columns_param.items(), result_full.params_best):
param.value_transformed = value
results[key][idx] = param.value
value = param.value
if is_sigma:
value = math.sqrt(sigma_min_sq**2 + value**2)
results[key][idx] = value

time_final = time.process_time()
results[f"{prefix}time_full"][idx] = time_final - time_init
Expand Down
3 changes: 3 additions & 0 deletions python/lsst/multiprofit/fitting/fit_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,9 @@ def fit(
_ = self.modeller.fit_model_linear(model_psf)
model_psf.setup_evaluators(evaluatormode=g2f.EvaluatorMode.loglike)
loglike_psfmodel = model_psf.evaluate()
# Reset fluxes for the next fit
for param in fluxes_psmodel.values():
param.value = 1.
results[f"{prefix}delta_lnL_fit_ps"][idx] = loglike_final[0] - loglike_psfmodel[0]

if compute_errors:
Expand Down
8 changes: 7 additions & 1 deletion tests/test_fit_bootstrap_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import math

import astropy.table
import lsst.gauss2d.fit as g2f
from lsst.multiprofit.componentconfig import (
Expand Down Expand Up @@ -223,21 +225,25 @@ def test_fit_psf(config_fitter_psfs, tables_psf_fits):
params_init = psf_model_init.parameters()
params_fit = psf_model_fit.parameters()
assert len(params_init) == len(params_fit)
sigma_min_sq = config_data_psf.config.sigma_min**2
for p_init, p_meas in zip(params_init, params_fit):
assert p_meas.fixed == p_init.fixed
if p_meas.fixed:
assert p_init.value == p_meas.value
else:
value = p_meas.value
# TODO: come up with better (noise-dependent) thresholds here
if isinstance(p_init, g2f.IntegralParameterD):
atol, rtol = 0, 0.02
elif isinstance(p_init, g2f.ProperFractionParameterD):
atol, rtol = 0.1, 0.01
elif isinstance(p_init, g2f.RhoParameterD):
atol, rtol = 0.05, 0.1
elif isinstance(p_init, g2f.SigmaXParameterD) or isinstance(p_init, g2f.SigmaYParameterD):
value = math.sqrt(value**2 + sigma_min_sq)
else:
atol, rtol = 0.01, 0.1
assert np.isclose(p_init.value, p_meas.value, atol=atol, rtol=rtol)
assert np.isclose(p_init.value, value, atol=atol, rtol=rtol)


def test_fit_source(config_fitter_source, config_data_sources):
Expand Down