Skip to content

Provide a way to make a copy of a model with different parameter values, or be more flexible with parameter shape #16593

Closed

Description

@astrofrog

GIven a model with scalar or array parameters, I would like to be able to easily make another model which has different parameter values (either scalar or array), while preserving other things (e.g. constraints, polynomial degree, etc). One can try the naive approach of making a copy of the model and then changing the parameters, but this doesn't always work:

Common imports

from astropy.modeling.models import Polynomial1D

import numpy as np

Scalar ▶️ Scalar

This case works fine:

poly = Polynomial1D(2, c0=1, c1=3, c2=5)
poly.c0.fixed = True
poly.c1.bounds = [0, 10]

poly2 = poly.copy()
poly2.c0=1.5
poly2.c1=3.2
poly2.c2 = 5.5

Array ▶️ scalar

poly = Polynomial1D(2, c0=np.ones((10, 10)), c1=np.ones((10, 10)) * 3, c2=np.ones((10, 10)) * 5)
poly.c0.fixed = True
poly.c1.bounds = [0, 10]

poly2 = poly.copy()
poly2.c0=1.5
poly2.c1=3.2
poly2.c2 = 5.5

This works ok because scalars are broadcastable to arrays. This is not ideal though as one temporarily has a copy of all the arrays in memory, and if the arrays are big it might be annoying.

Array ▶️ array

This does not work if the new array shape is not broadcastable to the old one:

poly = Polynomial1D(2, c0=np.ones((10, 10)), c1=np.ones((10, 10)) * 3, c2=np.ones((10, 10)) * 5)
poly.c0.fixed = True
poly.c1.bounds = [0, 10]

poly2 = poly.copy()
poly2.c0=np.arange(5)

The error is:

InputParameterError: Value for parameter c0 does not match shape or size
expected by model ((5,), 5) vs ((10, 10), 100)

Scalar ▶️ Array

This does not work at all:

poly = Polynomial1D(2, c0=1, c1=3, c2=5)
poly.c0.fixed = True
poly.c1.bounds = [0, 10]

poly2 = poly.copy()
poly2.c0=np.arange(5)

the error is:

InputParameterError: Value for parameter c0 does not match shape or size
expected by model ((5,), 5) vs ((1,), 1)

Possible solutions

One option would not be to check the dimensions whenever parameters are set, but to instead validate them at the point where the model is called - otherwise it is not possible to change the parameters to have a shape that is not broadcastable to the current one.

Another option would be to have a way to do e.g. poly.copy_with_new_parameters(c0=np.arange(5), ...) or similar, that is, do a copy but replace the parameter value with the ones provided in the method call.

I'm open to other ideas though!

Activity

  1. nstarman commented on Jun 19, 2024

    @nstarman
    Member

    #15750 would have worked for this.
    replace is the new paradigm for dataclasses (which astropy.models are conceptually, if not in actual code).
    singledispatch allows for us to make a function in astropy (or better yet a companion utility library), called replace that dispatches like this:

    @singledispatch
    def replace(obj: Any, /, **kwargs: Any) -> Any:
        raise NotImplementedError
    
    @replace.register  # a superset of python's dataclasses.replace!
    def replace(obj: DataclassInstance, /,  **kwargs: Any) -> DataclassInstance:
        from dataclasses import replace
        return replace(obj, **kwargs)
    
    @replace.register  # it works on anything!
    def replace(obj: AstropyModel, /, **kwargs: Any) -> AstropyModel:
        ... whatever mechanism we want
        return newobj

    With this dispatcher, each sub-package in astropy can register copy-and-modify methods into a single tool.
    Stick an example in the docstring Examples and it's easily discoverable.

  2. astrofrog commented on Jul 12, 2024

    @astrofrog
    MemberAuthor

    Ok so after digging into this further, it is possible to get some of these examples to work by setting the parameter value instead of the top-level parameter, so e.g.:

    poly = Polynomial1D(2, c0=1, c1=3, c2=5)
    poly.c0.fixed = True
    poly.c1.bounds = [0, 10]
    
    poly2 = poly.copy()
    poly2.c0.value=np.arange(5)
    

    works, and if parameters are set with incompatible shapes an error is raised at model evaluation time, which I think is sensible. I guess the question is then should Model.__setattr__ be more permissive to match what is possible by setting Parameter.value directly?

  3. astrofrog commented on Jul 12, 2024

    @astrofrog
    MemberAuthor

    See #16710 for a bug that occurs when doing this though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions