Skip to content

Inputs to modeling not always broadcasted as expected #11060

Closed

Description

@Cadair

Description

This might not actually be a bug and nobody but me expects it to work this way, but this worked when I ran the same code through astropy.wcs.WCS and it fails with gwcs.WCS because of the input behaviour in modeling.

Expected behavior

When calling a 2D model with sparse, broadcastable inputs I expect the inputs to be broadcasted together.

Actual behavior

Traceback saying ValueError: All inputs must have identical shapes or must be scalars.

Steps to Reproduce

import numpy as np
from astropy.modeling import models
import astropy.units as u

shape = (3, 3)
data = np.arange(np.product(shape)).reshape(shape) * u.m / u.s

points_unit = u.pix
points = [np.arange(size) * points_unit for size in shape]

kwargs = {
    'bounds_error': False,
    'fill_value': np.nan,
    'method': 'nearest',
}

transform = models.Tabular2D(points, data, **kwargs)
print(transform)
Model: Tabular2D
N_inputs: 2
N_outputs: 1
Parameters: 
  points: [<Quantity [0., 1., 2.] pix>, <Quantity [0., 1., 2.] pix>]
  lookup_table: [[0. 1. 2.]
 [3. 4. 5.]
 [6. 7. 8.]] m / s
  method: nearest
  fill_value: nan
  bounds_error: False

points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=True)
print(points)
[array([[0],
       [1],
       [2]]), array([[0, 1, 2]])]
transform(*points)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-e007ddf28445> in <module>
----> 1 import codecs, os;__pyfile = codecs.open('''/tmp/pypuTmFL''', encoding='''utf-8''');__code = __pyfile.read().encode('''utf-8''');__pyfile.close();os.remove('''/tmp/pypuTmFL''');exec(compile(__code, '''/tmp/pypuTmFL''', 'exec'));

/tmp/pypuTmFL in <module>

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in __call__(self, model_set_axis, with_bounding_box, fill_value, equivalencies, inputs_map, *inputs, **new_inputs)
    398                            ('inputs_map', None)])
    399 
--> 400             new_call = make_function_with_signature(
    401                 __call__, args, kwargs, varargs='inputs', varkwargs='new_inputs')
    402 

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in __call__(self, *inputs, **kwargs)
    377             def __call__(self, *inputs, **kwargs):
    378                 """Evaluate this model on the supplied inputs."""
--> 379                 return super(cls, self).__call__(*inputs, **kwargs)
    380 
    381             # When called, models can take two optional keyword arguments:

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in __call__(self, *args, **kwargs)
    911         new_args, kwargs = self._get_renamed_inputs_as_positional(*args, **kwargs)
    912 
--> 913         return generic_call(self, *new_args, **kwargs)
    914 
    915     def _get_renamed_inputs_as_positional(self, *args, **kwargs):

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in generic_call(self, *inputs, **kwargs)
   3945 def generic_call(self, *inputs, **kwargs):
   3946     """ The base ``Model. __call__`` method."""
-> 3947     inputs, format_info = self.prepare_inputs(*inputs, **kwargs)
   3948     if isinstance(self, CompoundModel):
   3949         # CompoundModels do not normally hold parameters at that level

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in prepare_inputs(self, model_set_axis, equivalencies, *inputs, **kwargs)
   1608         inputs = [np.asanyarray(_input, dtype=float) for _input in inputs]
   1609 
-> 1610         _validate_input_shapes(inputs, self.inputs, n_models,
   1611                                model_set_axis, self.standard_broadcasting)
   1612 

~/.virtualenvs/ndcube-dev/lib/python3.8/site-packages/astropy/modeling/core.py in _validate_input_shapes(inputs, argnames, n_models, model_set_axis, validate_broadcasting)
   3885     input_shape = check_consistent_shapes(*all_shapes)
   3886     if input_shape is None:
-> 3887         raise ValueError(
   3888             "All inputs must have identical shapes or must be scalars.")
   3889 

ValueError: All inputs must have identical shapes or must be scalars.

What I expect the output to be is what happens if you set sparse=False:

points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=False) * u.pix
print(transform(*points))
[[[0. 0. 0.]
  [1. 1. 1.]
  [2. 2. 2.]]

 [[0. 1. 2.]
  [0. 1. 2.]
  [0. 1. 2.]]] pix

System Details

Astropy 4.1

Activity

  1. nden commented on Nov 19, 2020

    @nden
    Contributor

    This is a known issue. I attempted a solution in #10362 but it's incomplete - handling of the bounding box needs to be fixed. Similar issues: #9953 , #10170

  2. pllim commented on Nov 19, 2020

    @pllim
    Member

    Closing as duplicate. Thanks!

  3. WilliamJamieson commented on Apr 15, 2021

    @WilliamJamieson
    Contributor

    Even with the changes in #11548 I still get a crash with this example. Though it is slightly different. @nden can you reopen this?

  4. reopened this on Apr 15, 2021
  5. WilliamJamieson commented on Apr 15, 2021

    @WilliamJamieson
    Contributor

    With current changes in #11548 when I try:

    import numpy as np
    from astropy.modeling import models
    import astropy.units as u
    
    shape = (3, 3)
    data = np.arange(np.product(shape)).reshape(shape) * u.m / u.s
    
    points_unit = u.pix
    points = [np.arange(size) * points_unit for size in shape]
    
    kwargs = {
        'bounds_error': False,
        'fill_value': np.nan,
        'method': 'nearest',
    }
    
    transform = models.Tabular2D(points, data, **kwargs)
    points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=True)
    
    transform(*points)

    The error:

    ---------------------------------------------------------------------------
    UnitsError                                Traceback (most recent call last)
    <ipython-input-3-9c5af24ecf8c> in <module>
    ----> 1 transform(*points)
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, model_set_axis, with_bounding_box, fill_value, equivalencies, inputs_map, *inputs, **new_inputs)
        396                            ('inputs_map', None)])
        397
    --> 398             new_call = make_function_with_signature(
        399                 __call__, args, kwargs, varargs='inputs', varkwargs='new_inputs')
        400
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, *inputs, **kwargs)
        375             def __call__(self, *inputs, **kwargs):
        376                 """Evaluate this model on the supplied inputs."""
    --> 377                 return super(cls, self).__call__(*inputs, **kwargs)
        378
        379             # When called, models can take two optional keyword arguments:
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, *args, **kwargs)
        916         new_args, kwargs = self._get_renamed_inputs_as_positional(*args, **kwargs)
        917
    --> 918         return generic_call(self, *new_args, **kwargs)
        919
        920     def _get_renamed_inputs_as_positional(self, *args, **kwargs):
    
    ~/projects/astropy/astropy/modeling/core.py in generic_call(self, *inputs, **kwargs)
       4109 def generic_call(self, *inputs, **kwargs):
       4110     """ The base ``Model. __call__`` method."""
    -> 4111     inputs, format_info = self.prepare_inputs(*inputs, **kwargs)
       4112     if isinstance(self, CompoundModel):
       4113         # CompoundModels do not normally hold parameters at that level
    
    ~/projects/astropy/astropy/modeling/core.py in prepare_inputs(self, model_set_axis, equivalencies, *inputs, **kwargs)
       1682         inputs_map = kwargs.get('inputs_map', None)
       1683
    -> 1684         inputs = self._validate_input_units(inputs, equivalencies, inputs_map)
       1685
       1686         # The input formatting required for single models versus a multiple
    
    ~/projects/astropy/astropy/modeling/core.py in _validate_input_units(self, inputs, equivalencies, inputs_map)
       1779                         input_unit is not None):
       1780                         if np.any(inputs[i] != 0):
    -> 1781                             raise UnitsError("{0}: Units of input '{1}', (dimensionless), could not be "
       1782                                              "converted to required input units of "
       1783                                              "{2} ({3})".format(name, self.inputs[i], input_unit,
    
    UnitsError: Tabular2D: Units of input 'x', (dimensionless), could not be converted to required input units of pix (unknown)
  6. WilliamJamieson commented on Apr 15, 2021

    @WilliamJamieson
    Contributor

    Note that the above unit error can be resolved by running instead running:

    import numpy as np
    from astropy.modeling import models
    import astropy.units as u
    
    shape = (3, 3)
    data = np.arange(np.product(shape)).reshape(shape) * u.m / u.s
    
    points_unit = u.pix
    points = [np.arange(size) * points_unit for size in shape]
    
    kwargs = {
        'bounds_error': False,
        'fill_value': np.nan,
        'method': 'nearest',
    }
    
    transform = models.Tabular2D(points, data, **kwargs)
    points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=True)
    
    x = points[0] * u.pix
    y = points[1] * u.pix
    
    print(transform(x, y))

    However, this creates another shaping error which is related to the others fixed in #11548:

    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    ~/projects/astropy/astropy/modeling/core.py in _prepare_outputs_single_model(outputs, format_info)
       3919                 try:
    -> 3920                     outputs[idx] = output.reshape(broadcast_shape)
       3921                 except ValueError:
    
    ValueError: cannot reshape array of size 3 into shape (3,3)
    
    During handling of the above exception, another exception occurred:
    
    ValueError                                Traceback (most recent call last)
    <ipython-input-12-d62d220f23e9> in <module>
    ----> 1 transform(x, y)
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, model_set_axis, with_bounding_box, fill_value, equivalencies, inputs_map, *inputs, **new_inputs)
        396                            ('inputs_map', None)])
        397
    --> 398             new_call = make_function_with_signature(
        399                 __call__, args, kwargs, varargs='inputs', varkwargs='new_inputs')
        400
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, *inputs, **kwargs)
        375             def __call__(self, *inputs, **kwargs):
        376                 """Evaluate this model on the supplied inputs."""
    --> 377                 return super(cls, self).__call__(*inputs, **kwargs)
        378
        379             # When called, models can take two optional keyword arguments:
    
    ~/projects/astropy/astropy/modeling/core.py in __call__(self, *args, **kwargs)
        916         new_args, kwargs = self._get_renamed_inputs_as_positional(*args, **kwargs)
        917
    --> 918         return generic_call(self, *new_args, **kwargs)
        919
        920     def _get_renamed_inputs_as_positional(self, *args, **kwargs):
    
    ~/projects/astropy/astropy/modeling/core.py in generic_call(self, *inputs, **kwargs)
       4140         if self.n_outputs == 1:
       4141             outputs = (outputs,)
    -> 4142     outputs = self.prepare_outputs(format_info, *outputs, **kwargs)
       4143
       4144     outputs = self._process_output_units(inputs, outputs)
    
    ~/projects/astropy/astropy/modeling/core.py in prepare_outputs(self, format_info, *outputs, **kwargs)
       1802
       1803         if len(self) == 1:
    -> 1804             return _prepare_outputs_single_model(outputs, format_info)
       1805         else:
       1806             return _prepare_outputs_model_set(self, outputs, format_info, model_set_axis)
    
    ~/projects/astropy/astropy/modeling/core.py in _prepare_outputs_single_model(outputs, format_info)
       3920                     outputs[idx] = output.reshape(broadcast_shape)
       3921                 except ValueError:
    -> 3922                     outputs[idx] = output.item()
       3923
       3924     return tuple(outputs)
    
    ~/projects/astropy/astropy/units/quantity.py in item(self, *args)
       1333
       1334     def item(self, *args):
    -> 1335         return self._new_view(super().item(*args))
       1336
       1337     def tolist(self):
    
    ValueError: can only convert an array of size 1 to a Python scalar

    I have a possible fix for this crash. However I am not convinced that I get the correct answer back when I use this fix. Namely, under my fix the above code would return:

    [[0.]
     [4.]
     [8.]] m / s

    Does this seem reasonable?

  7. added a commit that references this issue on Apr 22, 2021
  8. nden commented on Apr 22, 2021

    @nden
    Contributor

    @WilliamJamieson 👍 The above numbers match the answer without units.

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