1# Licensed under a 3-clause BSD style license - see LICENSE.rst 2# pylint: disable=invalid-name 3import os 4import sys 5import subprocess 6 7import pytest 8import unittest.mock as mk 9import numpy as np 10from inspect import signature 11from numpy.testing import assert_allclose, assert_equal 12 13import astropy 14from astropy.modeling.core import (Model, CompoundModel, custom_model, 15 SPECIAL_OPERATORS, _add_special_operator, 16 bind_bounding_box, bind_compound_bounding_box, 17 fix_inputs) 18from astropy.modeling.bounding_box import ModelBoundingBox, CompoundBoundingBox 19from astropy.modeling.separable import separability_matrix 20from astropy.modeling.parameters import Parameter 21from astropy.modeling import models 22from astropy.convolution import convolve_models 23import astropy.units as u 24from astropy.tests.helper import assert_quantity_allclose 25from astropy.utils.compat.optional_deps import HAS_SCIPY # noqa 26import astropy.modeling.core as core 27 28 29class NonFittableModel(Model): 30 """An example class directly subclassing Model for testing.""" 31 32 a = Parameter() 33 34 def __init__(self, a, model_set_axis=None): 35 super().__init__(a, model_set_axis=model_set_axis) 36 37 @staticmethod 38 def evaluate(): 39 pass 40 41 42def test_Model_instance_repr_and_str(): 43 m = NonFittableModel(42.5) 44 assert repr(m) == "<NonFittableModel(a=42.5)>" 45 assert (str(m) == 46 "Model: NonFittableModel\n" 47 "Inputs: ()\n" 48 "Outputs: ()\n" 49 "Model set size: 1\n" 50 "Parameters:\n" 51 " a \n" 52 " ----\n" 53 " 42.5") 54 55 assert len(m) == 1 56 57 58def test_Model_array_parameter(): 59 model = models.Gaussian1D(4, 2, 1) 60 assert_allclose(model.param_sets, [[4], [2], [1]]) 61 62 63def test_inputless_model(): 64 """ 65 Regression test for 66 https://github.com/astropy/astropy/pull/3772#issuecomment-101821641 67 """ 68 69 class TestModel(Model): 70 71 n_outputs = 1 72 a = Parameter() 73 74 @staticmethod 75 def evaluate(a): 76 return a 77 78 m = TestModel(1) 79 assert m.a == 1 80 assert m() == 1 81 82 # Test array-like output 83 m = TestModel([1, 2, 3], model_set_axis=False) 84 assert len(m) == 1 85 assert np.all(m() == [1, 2, 3]) 86 87 # Test a model set 88 m = TestModel(a=[1, 2, 3], model_set_axis=0) 89 assert len(m) == 3 90 assert np.all(m() == [1, 2, 3]) 91 92 # Test a model set 93 m = TestModel(a=[[1, 2, 3], [4, 5, 6]], model_set_axis=0) 94 assert len(m) == 2 95 assert np.all(m() == [[1, 2, 3], [4, 5, 6]]) 96 97 # Test a model set 98 m = TestModel(a=[[1, 2, 3], [4, 5, 6]], model_set_axis=np.int64(0)) 99 assert len(m) == 2 100 assert np.all(m() == [[1, 2, 3], [4, 5, 6]]) 101 102 103def test_ParametericModel(): 104 with pytest.raises(TypeError): 105 models.Gaussian1D(1, 2, 3, wrong=4) 106 107 108def test_custom_model_signature(): 109 """ 110 Tests that the signatures for the __init__ and __call__ 111 methods of custom models are useful. 112 """ 113 114 @custom_model 115 def model_a(x): 116 return x 117 118 assert model_a.param_names == () 119 assert model_a.n_inputs == 1 120 sig = signature(model_a.__init__) 121 assert list(sig.parameters.keys()) == ['self', 'args', 'meta', 'name', 'kwargs'] 122 sig = signature(model_a.__call__) 123 assert list(sig.parameters.keys()) == ['self', 'inputs', 'model_set_axis', 124 'with_bounding_box', 'fill_value', 125 'equivalencies', 'inputs_map', 'new_inputs'] 126 127 @custom_model 128 def model_b(x, a=1, b=2): 129 return x + a + b 130 131 assert model_b.param_names == ('a', 'b') 132 assert model_b.n_inputs == 1 133 sig = signature(model_b.__init__) 134 assert list(sig.parameters.keys()) == ['self', 'a', 'b', 'kwargs'] 135 assert [x.default for x in sig.parameters.values()] == [sig.empty, 1, 2, sig.empty] 136 sig = signature(model_b.__call__) 137 assert list(sig.parameters.keys()) == ['self', 'inputs', 'model_set_axis', 138 'with_bounding_box', 'fill_value', 139 'equivalencies', 'inputs_map', 'new_inputs'] 140 141 @custom_model 142 def model_c(x, y, a=1, b=2): 143 return x + y + a + b 144 145 assert model_c.param_names == ('a', 'b') 146 assert model_c.n_inputs == 2 147 sig = signature(model_c.__init__) 148 assert list(sig.parameters.keys()) == ['self', 'a', 'b', 'kwargs'] 149 assert [x.default for x in sig.parameters.values()] == [sig.empty, 1, 2, sig.empty] 150 sig = signature(model_c.__call__) 151 assert list(sig.parameters.keys()) == ['self', 'inputs', 'model_set_axis', 152 'with_bounding_box', 'fill_value', 153 'equivalencies', 'inputs_map', 'new_inputs'] 154 155 156def test_custom_model_subclass(): 157 """Test that custom models can be subclassed.""" 158 159 @custom_model 160 def model_a(x, a=1): 161 return x * a 162 163 class model_b(model_a): 164 # Override the evaluate from model_a 165 @classmethod 166 def evaluate(cls, x, a): 167 return -super().evaluate(x, a) 168 169 b = model_b() 170 assert b.param_names == ('a',) 171 assert b.a == 1 172 assert b(1) == -1 173 174 sig = signature(model_b.__init__) 175 assert list(sig.parameters.keys()) == ['self', 'a', 'kwargs'] 176 sig = signature(model_b.__call__) 177 assert list(sig.parameters.keys()) == ['self', 'inputs', 'model_set_axis', 178 'with_bounding_box', 'fill_value', 179 'equivalencies', 'inputs_map', 'new_inputs'] 180 181 182def test_custom_model_parametrized_decorator(): 183 """Tests using custom_model as a decorator with parameters.""" 184 185 def cosine(x, amplitude=1): 186 return [amplitude * np.cos(x)] 187 188 @custom_model(fit_deriv=cosine) 189 def sine(x, amplitude=1): 190 return amplitude * np.sin(x) 191 192 assert issubclass(sine, Model) 193 s = sine(2) 194 assert_allclose(s(np.pi / 2), 2) 195 assert_allclose(s.fit_deriv(0, 2), 2) 196 197 198def test_custom_model_n_outputs(): 199 """ 200 Test creating a custom_model which has more than one output, which 201 requires special handling. 202 Demonstrates issue #11791's ``n_outputs`` error has been solved 203 """ 204 205 @custom_model 206 def model(x, y, n_outputs=2): 207 return x+1, y+1 208 209 m = model() 210 assert not isinstance(m.n_outputs, Parameter) 211 assert isinstance(m.n_outputs, int) 212 assert m.n_outputs == 2 213 assert m.outputs == ('x0', 'x1') 214 assert (separability_matrix(m) == [[True, True], 215 [True, True]]).all() 216 217 @custom_model 218 def model(x, y, z, n_outputs=3): 219 return x+1, y+1, z+1 220 221 m = model() 222 assert not isinstance(m.n_outputs, Parameter) 223 assert isinstance(m.n_outputs, int) 224 assert m.n_outputs == 3 225 assert m.outputs == ('x0', 'x1', 'x2') 226 assert (separability_matrix(m) == [[True, True, True], 227 [True, True, True], 228 [True, True, True]]).all() 229 230 231def test_custom_model_settable_parameters(): 232 """ 233 Test creating a custom_model which specifically sets adjustable model 234 parameters. 235 Demonstrates part of issue #11791's notes about what passed parameters 236 should/shouldn't be allowed. In this case, settable parameters 237 should be allowed to have defaults set. 238 """ 239 @custom_model 240 def model(x, y, n_outputs=2, bounding_box=((1, 2), (3, 4))): 241 return x+1, y+1 242 243 m = model() 244 assert m.n_outputs == 2 245 assert m.bounding_box == ((1, 2), (3, 4)) 246 m.bounding_box = ((9, 10), (11, 12)) 247 assert m.bounding_box == ((9, 10), (11, 12)) 248 m = model(bounding_box=((5, 6), (7, 8))) 249 assert m.n_outputs == 2 250 assert m.bounding_box == ((5, 6), (7, 8)) 251 m.bounding_box = ((9, 10), (11, 12)) 252 assert m.bounding_box == ((9, 10), (11, 12)) 253 254 @custom_model 255 def model(x, y, n_outputs=2, outputs=('z0', 'z1')): 256 return x+1, y+1 257 258 m = model() 259 assert m.n_outputs == 2 260 assert m.outputs == ('z0', 'z1') 261 m.outputs = ('a0', 'a1') 262 assert m.outputs == ('a0', 'a1') 263 m = model(outputs=('w0', 'w1')) 264 assert m.n_outputs == 2 265 assert m.outputs == ('w0', 'w1') 266 m.outputs = ('a0', 'a1') 267 assert m.outputs == ('a0', 'a1') 268 269 270def test_custom_model_regected_parameters(): 271 """ 272 Test creating a custom_model which attempts to override non-overridable 273 parameters. 274 Demonstrates part of issue #11791's notes about what passed parameters 275 should/shouldn't be allowed. In this case, non-settable parameters 276 should raise an error (unexpected behavior may occur). 277 """ 278 279 with pytest.raises(ValueError, 280 match=r"Parameter 'n_inputs' cannot be a model property: *"): 281 @custom_model 282 def model(x, y, n_outputs=2, n_inputs=3): 283 return x+1, y+1 284 285 with pytest.raises(ValueError, 286 match=r"Parameter 'uses_quantity' cannot be a model property: *"): 287 @custom_model 288 def model(x, y, n_outputs=2, uses_quantity=True): 289 return x+1, y+1 290 291 292def test_custom_inverse(): 293 """Test setting a custom inverse on a model.""" 294 295 p = models.Polynomial1D(1, c0=-2, c1=3) 296 # A trivial inverse for a trivial polynomial 297 inv = models.Polynomial1D(1, c0=(2./3.), c1=(1./3.)) 298 299 with pytest.raises(NotImplementedError): 300 p.inverse 301 302 p.inverse = inv 303 304 x = np.arange(100) 305 306 assert_allclose(x, p(p.inverse(x))) 307 assert_allclose(x, p.inverse(p(x))) 308 309 p.inverse = None 310 311 with pytest.raises(NotImplementedError): 312 p.inverse 313 314 315def test_custom_inverse_reset(): 316 """Test resetting a custom inverse to the model's default inverse.""" 317 318 class TestModel(Model): 319 n_inputs = 0 320 outputs = ('y',) 321 322 @property 323 def inverse(self): 324 return models.Shift() 325 326 @staticmethod 327 def evaluate(): 328 return 0 329 330 # The above test model has no meaning, nor does its inverse--this just 331 # tests that setting an inverse and resetting to the default inverse works 332 333 m = TestModel() 334 assert isinstance(m.inverse, models.Shift) 335 336 m.inverse = models.Scale() 337 assert isinstance(m.inverse, models.Scale) 338 339 del m.inverse 340 assert isinstance(m.inverse, models.Shift) 341 342 343def test_render_model_2d(): 344 imshape = (71, 141) 345 image = np.zeros(imshape) 346 coords = y, x = np.indices(imshape) 347 348 model = models.Gaussian2D(x_stddev=6.1, y_stddev=3.9, theta=np.pi / 3) 349 350 # test points for edges 351 ye, xe = [0, 35, 70], [0, 70, 140] 352 # test points for floating point positions 353 yf, xf = [35.1, 35.5, 35.9], [70.1, 70.5, 70.9] 354 355 test_pts = [(a, b) for a in xe for b in ye] 356 test_pts += [(a, b) for a in xf for b in yf] 357 358 for x0, y0 in test_pts: 359 model.x_mean = x0 360 model.y_mean = y0 361 expected = model(x, y) 362 for xy in [coords, None]: 363 for im in [image.copy(), None]: 364 if (im is None) & (xy is None): 365 # this case is tested in Fittable2DModelTester 366 continue 367 actual = model.render(out=im, coords=xy) 368 if im is None: 369 assert_allclose(actual, model.render(coords=xy)) 370 # assert images match 371 assert_allclose(expected, actual, atol=3e-7) 372 # assert model fully captured 373 if (x0, y0) == (70, 35): 374 boxed = model.render() 375 flux = np.sum(expected) 376 assert ((flux - np.sum(boxed)) / flux) < 1e-7 377 # test an error is raised when the bounding box is larger than the input array 378 try: 379 actual = model.render(out=np.zeros((1, 1))) 380 except ValueError: 381 pass 382 383 384def test_render_model_1d(): 385 npix = 101 386 image = np.zeros(npix) 387 coords = np.arange(npix) 388 389 model = models.Gaussian1D() 390 391 # test points 392 test_pts = [0, 49.1, 49.5, 49.9, 100] 393 394 # test widths 395 test_stdv = np.arange(5.5, 6.7, .2) 396 397 for x0, stdv in [(p, s) for p in test_pts for s in test_stdv]: 398 model.mean = x0 399 model.stddev = stdv 400 expected = model(coords) 401 for x in [coords, None]: 402 for im in [image.copy(), None]: 403 if (im is None) & (x is None): 404 # this case is tested in Fittable1DModelTester 405 continue 406 actual = model.render(out=im, coords=x) 407 # assert images match 408 assert_allclose(expected, actual, atol=3e-7) 409 # assert model fully captured 410 if (x0, stdv) == (49.5, 5.5): 411 boxed = model.render() 412 flux = np.sum(expected) 413 assert ((flux - np.sum(boxed)) / flux) < 1e-7 414 415 416@pytest.mark.filterwarnings('ignore:invalid value encountered in less') 417def test_render_model_3d(): 418 imshape = (17, 21, 27) 419 image = np.zeros(imshape) 420 coords = np.indices(imshape) 421 422 def ellipsoid(x, y, z, x0=13., y0=10., z0=8., a=4., b=3., c=2., amp=1.): 423 rsq = ((x - x0) / a) ** 2 + ((y - y0) / b) ** 2 + ((z - z0) / c) ** 2 424 val = (rsq < 1) * amp 425 return val 426 427 class Ellipsoid3D(custom_model(ellipsoid)): 428 @property 429 def bounding_box(self): 430 return ((self.z0 - self.c, self.z0 + self.c), 431 (self.y0 - self.b, self.y0 + self.b), 432 (self.x0 - self.a, self.x0 + self.a)) 433 434 model = Ellipsoid3D() 435 436 # test points for edges 437 ze, ye, xe = [0, 8, 16], [0, 10, 20], [0, 13, 26] 438 # test points for floating point positions 439 zf, yf, xf = [8.1, 8.5, 8.9], [10.1, 10.5, 10.9], [13.1, 13.5, 13.9] 440 441 test_pts = [(x, y, z) for x in xe for y in ye for z in ze] 442 test_pts += [(x, y, z) for x in xf for y in yf for z in zf] 443 444 for x0, y0, z0 in test_pts: 445 model.x0 = x0 446 model.y0 = y0 447 model.z0 = z0 448 expected = model(*coords[::-1]) 449 for c in [coords, None]: 450 for im in [image.copy(), None]: 451 if (im is None) & (c is None): 452 continue 453 actual = model.render(out=im, coords=c) 454 boxed = model.render() 455 # assert images match 456 assert_allclose(expected, actual) 457 # assert model fully captured 458 if (z0, y0, x0) == (8, 10, 13): 459 boxed = model.render() 460 assert (np.sum(expected) - np.sum(boxed)) == 0 461 462 463def test_render_model_out_dtype(): 464 """Test different out.dtype for model.render.""" 465 for model in [models.Gaussian2D(), models.Gaussian2D() + models.Planar2D()]: 466 for dtype in [np.float64, np.float32, np.complex64]: 467 im = np.zeros((40, 40), dtype=dtype) 468 imout = model.render(out=im) 469 assert imout is im 470 assert imout.sum() != 0 471 with pytest.raises(TypeError): 472 im = np.zeros((40, 40), dtype=np.int32) 473 imout = model.render(out=im) 474 475 476def test_custom_bounding_box_1d(): 477 """ 478 Tests that the bounding_box setter works. 479 """ 480 # 1D models 481 g1 = models.Gaussian1D() 482 bb = g1.bounding_box 483 expected = g1.render() 484 485 # assign the same bounding_box, now through the bounding_box setter 486 g1.bounding_box = bb 487 assert_allclose(g1.render(), expected) 488 489 # 2D models 490 g2 = models.Gaussian2D() 491 bb = g2.bounding_box 492 expected = g2.render() 493 494 # assign the same bounding_box, now through the bounding_box setter 495 g2.bounding_box = bb 496 assert_allclose(g2.render(), expected) 497 498 499def test_n_submodels_in_single_models(): 500 assert models.Gaussian1D().n_submodels == 1 501 assert models.Gaussian2D().n_submodels == 1 502 503 504def test_compound_deepcopy(): 505 model = (models.Gaussian1D(10, 2, 3) | models.Shift(2)) & models.Rotation2D(21.3) 506 new_model = model.deepcopy() 507 assert id(model) != id(new_model) 508 assert id(model._leaflist) != id(new_model._leaflist) 509 assert id(model[0]) != id(new_model[0]) 510 assert id(model[1]) != id(new_model[1]) 511 assert id(model[2]) != id(new_model[2]) 512 513 514@pytest.mark.skipif('not HAS_SCIPY') 515def test_units_with_bounding_box(): 516 points = np.arange(10, 20) 517 table = np.arange(10) * u.Angstrom 518 t = models.Tabular1D(points, lookup_table=table) 519 520 assert isinstance(t(10), u.Quantity) 521 assert isinstance(t(10, with_bounding_box=True), u.Quantity) 522 523 assert_quantity_allclose(t(10), t(10, with_bounding_box=True)) 524 525 526RENAMED_MODEL = models.Gaussian1D.rename('CustomGaussian') 527 528MODEL_RENAME_CODE = """ 529from astropy.modeling.models import Gaussian1D 530print(repr(Gaussian1D)) 531print(repr(Gaussian1D.rename('CustomGaussian'))) 532""".strip() 533 534MODEL_RENAME_EXPECTED = b""" 535<class 'astropy.modeling.functional_models.Gaussian1D'> 536Name: Gaussian1D 537N_inputs: 1 538N_outputs: 1 539Fittable parameters: ('amplitude', 'mean', 'stddev') 540<class '__main__.CustomGaussian'> 541Name: CustomGaussian (Gaussian1D) 542N_inputs: 1 543N_outputs: 1 544Fittable parameters: ('amplitude', 'mean', 'stddev') 545""".strip() 546 547 548def test_rename_path(tmpdir): 549 550 # Regression test for a bug that caused the path to the class to be 551 # incorrect in a renamed model's __repr__. 552 553 assert repr(RENAMED_MODEL).splitlines()[0] == "<class 'astropy.modeling.tests.test_core.CustomGaussian'>" 554 555 # Make sure that when called from a user script, the class name includes 556 # __main__. 557 558 env = os.environ.copy() 559 paths = [os.path.dirname(astropy.__path__[0])] + sys.path 560 env['PYTHONPATH'] = os.pathsep.join(paths) 561 562 script = tmpdir.join('rename.py').strpath 563 with open(script, 'w') as f: 564 f.write(MODEL_RENAME_CODE) 565 566 output = subprocess.check_output([sys.executable, script], env=env) 567 assert output.splitlines() == MODEL_RENAME_EXPECTED.splitlines() 568 569 570@pytest.mark.parametrize('model_class', 571 [models.Gaussian1D, models.Polynomial1D, 572 models.Shift, models.Tabular1D]) 573def test_rename_1d(model_class): 574 new_model = model_class.rename(name='Test1D') 575 assert new_model.name == 'Test1D' 576 577 578@pytest.mark.parametrize('model_class', 579 [models.Gaussian2D, models.Polynomial2D, models.Tabular2D]) 580def test_rename_2d(model_class): 581 new_model = model_class.rename(name='Test2D') 582 assert new_model.name == 'Test2D' 583 584 585def test_fix_inputs_integer(): 586 """ 587 Tests that numpy integers can be passed as dictionary keys to fix_inputs 588 Issue #11358 589 """ 590 m = models.Identity(2) 591 592 mf = models.fix_inputs(m, {1: 22}) 593 assert mf(1) == (1, 22) 594 595 mf_int32 = models.fix_inputs(m, {np.int32(1): 33}) 596 assert mf_int32(1) == (1, 33) 597 598 mf_int64 = models.fix_inputs(m, {np.int64(1): 44}) 599 assert mf_int64(1) == (1, 44) 600 601 602def test_fix_inputs_empty_dict(): 603 """ 604 Tests that empty dictionary can be passed to fix_inputs 605 Issue #11355 606 """ 607 m = models.Identity(2) 608 609 mf = models.fix_inputs(m, {}) 610 assert mf(1, 2) == (1, 2) 611 612 613def test_rename_inputs_outputs(): 614 g2 = models.Gaussian2D(10, 2, 3, 1, 2) 615 assert g2.inputs == ("x", "y") 616 assert g2.outputs == ("z",) 617 618 with pytest.raises(ValueError): 619 g2.inputs = ("w", ) 620 621 with pytest.raises(ValueError): 622 g2.outputs = ("w", "e") 623 624 625def test__prepare_output_single_model(): 626 model = models.Gaussian1D() 627 628 # No broadcast 629 assert (np.array([1, 2]) == 630 model._prepare_output_single_model(np.array([1, 2]), None)).all() 631 632 # Broadcast to scalar 633 assert 1 == model._prepare_output_single_model(np.array([1]), ()) 634 assert 2 == model._prepare_output_single_model(np.asanyarray(2), ()) 635 636 # Broadcast reshape 637 output = np.array([[1, 2, 3], 638 [4, 5, 6]]) 639 reshape = np.array([[1, 2], 640 [3, 4], 641 [5, 6]]) 642 assert (output == model._prepare_output_single_model(output, (2, 3))).all() 643 assert (reshape == model._prepare_output_single_model(output, (3, 2))).all() 644 645 # Broadcast reshape scalar 646 assert 1 == model._prepare_output_single_model(np.array([1]), (1, 2)) 647 assert 2 == model._prepare_output_single_model(np.asanyarray(2), (3, 4)) 648 649 # Fail to broadcast 650 assert (output == model._prepare_output_single_model(output, (1, 2))).all() 651 assert (output == model._prepare_output_single_model(output, (3, 4))).all() 652 653 654def test_prepare_outputs_mixed_broadcast(): 655 """ 656 Tests that _prepare_outputs_single_model does not fail when a smaller 657 array is passed as first input, but output is broadcast to larger 658 array. 659 Issue #10170 660 """ 661 662 model = models.Gaussian2D(1, 2, 3, 4, 5) 663 664 output = model([1, 2], 3) 665 assert output.shape == (2,) 666 np.testing.assert_array_equal(output, [0.9692332344763441, 1.0]) 667 668 output = model(4, [5, 6]) 669 assert output.shape == (2,) 670 np.testing.assert_array_equal(output, [0.8146473164114145, 0.7371233743916278]) 671 672 673def test_prepare_outputs_complex_reshape(): 674 x = np.array([[1, 2, 3, 4, 5], 675 [6, 7, 8, 9, 10], 676 [11, 12, 13, 14, 15]]) 677 y = np.array([[16, 17, 18, 19, 20], 678 [21, 22, 23, 24, 25], 679 [26, 27, 28, 29, 30]]) 680 681 m = models.Identity(3) | models.Mapping((2, 1, 0)) 682 m.bounding_box = ((0, 100), (0, 200), (0, 50)) 683 mf = models.fix_inputs(m, {2: 22}) 684 t = mf | models.Mapping((2, 1), n_inputs=3) 685 686 output = mf(1, 2) 687 assert output == (22, 2, 1) 688 689 output = t(1, 2) 690 assert output == (1, 2) 691 692 output = t(x, y) 693 assert len(output) == 2 694 np.testing.assert_array_equal(output[0], x) 695 np.testing.assert_array_equal(output[1], y) 696 697 m = models.Identity(3) | models.Mapping((0, 1, 2)) 698 m.bounding_box = ((0, 100), (0, 200), (0, 50)) 699 mf = models.fix_inputs(m, {2: 22}) 700 t = mf | models.Mapping((0, 1), n_inputs=3) 701 702 output = mf(1, 2) 703 assert output == (1, 2, 22) 704 705 output = t(1, 2) 706 assert output == (1, 2) 707 708 output = t(x, y) 709 assert len(output) == 2 710 np.testing.assert_array_equal(output[0], x) 711 np.testing.assert_array_equal(output[1], y) 712 713 714def test_prepare_outputs_single_entry_vector(): 715 """ 716 jwst and gwcs both require that single entry vectors produce single entry output vectors, not scalars. This 717 tests for that behavior. 718 """ 719 720 model = models.Gaussian2D(1, 2, 3, 4, 5) 721 722 output = model(np.array([1]), np.array([2])) 723 assert output.shape == (1,) 724 np.testing.assert_array_equal(output, [0.9500411305585278]) 725 726 727@pytest.mark.skipif('not HAS_SCIPY') 728@pytest.mark.filterwarnings('ignore: Using a non-tuple') 729def test_prepare_outputs_sparse_grid(): 730 """ 731 Test to show that #11060 has been solved. 732 """ 733 734 shape = (3, 3) 735 data = np.arange(np.product(shape)).reshape(shape) * u.m / u.s 736 737 points_unit = u.pix 738 points = [np.arange(size) * points_unit for size in shape] 739 740 kwargs = { 741 'bounds_error': False, 742 'fill_value': np.nan, 743 'method': 'nearest', 744 } 745 746 transform = models.Tabular2D(points, data, **kwargs) 747 truth = np.array([[0., 1., 2.], 748 [3., 4., 5.], 749 [6., 7., 8.]]) * u.m / u.s 750 751 points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=True) 752 x = points[0] * u.pix 753 y = points[1] * u.pix 754 value = transform(x, y) 755 assert (value == truth).all() 756 757 points = np.meshgrid(np.arange(3), np.arange(3), indexing='ij', sparse=False) * u.pix 758 value = transform(*points) 759 assert (value == truth).all() 760 761 762def test_coerce_units(): 763 model = models.Polynomial1D(1, c0=1, c1=2) 764 765 with pytest.raises(u.UnitsError): 766 model(u.Quantity(10, u.m)) 767 768 with_input_units = model.coerce_units({"x": u.m}) 769 result = with_input_units(u.Quantity(10, u.m)) 770 assert np.isclose(result, 21.0) 771 772 with_input_units_tuple = model.coerce_units((u.m,)) 773 result = with_input_units_tuple(u.Quantity(10, u.m)) 774 assert np.isclose(result, 21.0) 775 776 with_return_units = model.coerce_units(return_units={"y": u.s}) 777 result = with_return_units(10) 778 assert np.isclose(result.value, 21.0) 779 assert result.unit == u.s 780 781 with_return_units_tuple = model.coerce_units(return_units=(u.s,)) 782 result = with_return_units_tuple(10) 783 assert np.isclose(result.value, 21.0) 784 assert result.unit == u.s 785 786 with_both = model.coerce_units({"x": u.m}, {"y": u.s}) 787 788 result = with_both(u.Quantity(10, u.m)) 789 assert np.isclose(result.value, 21.0) 790 assert result.unit == u.s 791 792 with pytest.raises(ValueError, match=r"input_units keys.*do not match model inputs"): 793 model.coerce_units({"q": u.m}) 794 795 with pytest.raises(ValueError, match=r"input_units length does not match n_inputs"): 796 model.coerce_units((u.m, u.s)) 797 798 model_with_existing_input_units = models.BlackBody() 799 with pytest.raises(ValueError, match=r"Cannot specify input_units for model with existing input units"): 800 model_with_existing_input_units.coerce_units({"x": u.m}) 801 802 with pytest.raises(ValueError, match=r"return_units keys.*do not match model outputs"): 803 model.coerce_units(return_units={"q": u.m}) 804 805 with pytest.raises(ValueError, match=r"return_units length does not match n_outputs"): 806 model.coerce_units(return_units=(u.m, u.s)) 807 808 809def test_bounding_box_general_inverse(): 810 model = NonFittableModel(42.5) 811 812 with pytest.raises(NotImplementedError): 813 model.bounding_box 814 model.bounding_box = () 815 assert model.bounding_box.bounding_box() == () 816 817 model.inverse = NonFittableModel(3.14) 818 inverse_model = model.inverse 819 with pytest.raises(NotImplementedError): 820 inverse_model.bounding_box 821 822 823def test__add_special_operator(): 824 sop_name = 'name' 825 sop = 'value' 826 827 key = _add_special_operator(sop_name, 'value') 828 assert key[0] == sop_name 829 assert key[1] == SPECIAL_OPERATORS._unique_id 830 831 assert key in SPECIAL_OPERATORS 832 assert SPECIAL_OPERATORS[key] == sop 833 834 835def test_print_special_operator_CompoundModel(capsys): 836 """ 837 Test that issue #11310 has been fixed 838 """ 839 840 model = convolve_models(models.Sersic2D(), models.Gaussian2D()) 841 print(model) 842 843 true_out = "Model: CompoundModel\n" +\ 844 "Inputs: ('x', 'y')\n" +\ 845 "Outputs: ('z',)\n" +\ 846 "Model set size: 1\n" +\ 847 "Expression: convolve_fft (([0]), ([1]))\n" +\ 848 "Components: \n" +\ 849 " [0]: <Sersic2D(amplitude=1., r_eff=1., n=4., x_0=0., y_0=0., ellip=0., theta=0.)>\n" +\ 850 "\n" +\ 851 " [1]: <Gaussian2D(amplitude=1., x_mean=0., y_mean=0., x_stddev=1., y_stddev=1., theta=0.)>\n" +\ 852 "Parameters:\n" +\ 853 " amplitude_0 r_eff_0 n_0 x_0_0 y_0_0 ... y_mean_1 x_stddev_1 y_stddev_1 theta_1\n" +\ 854 " ----------- ------- --- ----- ----- ... -------- ---------- ---------- -------\n" +\ 855 " 1.0 1.0 4.0 0.0 0.0 ... 0.0 1.0 1.0 0.0\n" 856 857 out, err = capsys.readouterr() 858 assert err == '' 859 assert out == true_out 860 861 862def test__validate_input_shape(): 863 model = models.Gaussian1D() 864 model._n_models = 2 865 866 _input = np.array([[1, 2, 3], 867 [4, 5, 6]]) 868 869 # Successful validation 870 assert model._validate_input_shape(_input, 0, model.inputs, 1, False) == (2, 3) 871 872 # Fail number of axes 873 with pytest.raises(ValueError) as err: 874 model._validate_input_shape(_input, 0, model.inputs, 2, True) 875 assert str(err.value) == \ 876 "For model_set_axis=2, all inputs must be at least 3-dimensional." 877 878 # Fail number of models (has argname) 879 with pytest.raises(ValueError) as err: 880 model._validate_input_shape(_input, 0, model.inputs, 1, True) 881 assert str(err.value) == \ 882 "Input argument 'x' does not have the correct dimensions in model_set_axis=1 " +\ 883 "for a model set with n_models=2." 884 885 # Fail number of models (no argname) 886 with pytest.raises(ValueError) as err: 887 model._validate_input_shape(_input, 0, [], 1, True) 888 assert str(err.value) == \ 889 "Input argument '0' does not have the correct dimensions in model_set_axis=1 " +\ 890 "for a model set with n_models=2." 891 892 893def test__validate_input_shapes(): 894 model = models.Gaussian1D() 895 model._n_models = 2 896 inputs = [mk.MagicMock() for _ in range(3)] 897 argnames = mk.MagicMock() 898 model_set_axis = mk.MagicMock() 899 all_shapes = [mk.MagicMock() for _ in inputs] 900 901 # Successful validation 902 with mk.patch.object(Model, '_validate_input_shape', 903 autospec=True, side_effect=all_shapes) as mkValidate: 904 with mk.patch.object(core, 'check_broadcast', 905 autospec=True) as mkCheck: 906 assert mkCheck.return_value == \ 907 model._validate_input_shapes(inputs, argnames, model_set_axis) 908 assert mkCheck.call_args_list == [mk.call(*all_shapes)] 909 assert mkValidate.call_args_list == \ 910 [mk.call(model, _input, idx, argnames, model_set_axis, True) 911 for idx, _input in enumerate(inputs)] 912 913 # Fail check_broadcast 914 with mk.patch.object(Model, '_validate_input_shape', 915 autospec=True, side_effect=all_shapes) as mkValidate: 916 with mk.patch.object(core, 'check_broadcast', 917 autospec=True, return_value=None) as mkCheck: 918 with pytest.raises(ValueError) as err: 919 model._validate_input_shapes(inputs, argnames, model_set_axis) 920 assert str(err.value) == \ 921 "All inputs must have identical shapes or must be scalars." 922 assert mkCheck.call_args_list == [mk.call(*all_shapes)] 923 assert mkValidate.call_args_list == \ 924 [mk.call(model, _input, idx, argnames, model_set_axis, True) 925 for idx, _input in enumerate(inputs)] 926 927 928def test__remove_axes_from_shape(): 929 model = models.Gaussian1D() 930 931 # len(shape) == 0 932 assert model._remove_axes_from_shape((), mk.MagicMock()) == () 933 934 # axis < 0 935 assert model._remove_axes_from_shape((1, 2, 3), -1) == (1, 2) 936 assert model._remove_axes_from_shape((1, 2, 3), -2) == (1, 3) 937 assert model._remove_axes_from_shape((1, 2, 3), -3) == (2, 3) 938 939 # axis >= len(shape) 940 assert model._remove_axes_from_shape((1, 2, 3), 3) == () 941 assert model._remove_axes_from_shape((1, 2, 3), 4) == () 942 943 # 0 <= axis < len(shape) 944 assert model._remove_axes_from_shape((1, 2, 3), 0) == (2, 3) 945 assert model._remove_axes_from_shape((1, 2, 3), 1) == (3,) 946 assert model._remove_axes_from_shape((1, 2, 3), 2) == () 947 948 949def test_get_bounding_box(): 950 model = models.Const2D(2) 951 952 # No with_bbox 953 assert model.get_bounding_box(False) is None 954 955 # No bounding_box 956 with pytest.raises(NotImplementedError): 957 model.bounding_box 958 assert model.get_bounding_box(True) is None 959 960 # Normal bounding_box 961 model.bounding_box = ((0, 1), (0, 1)) 962 assert not isinstance(model.bounding_box, CompoundBoundingBox) 963 assert model.get_bounding_box(True) == ((0, 1), (0, 1)) 964 965 # CompoundBoundingBox with no removal 966 bbox = CompoundBoundingBox.validate(model, {(1,): ((-1, 0), (-1, 0)), (2,): ((0, 1), (0, 1))}, 967 selector_args=[('y', False)]) 968 model.bounding_box = bbox 969 assert isinstance(model.bounding_box, CompoundBoundingBox) 970 # Get using argument not with_bbox 971 assert model.get_bounding_box(True) == bbox 972 # Get using with_bbox not argument 973 assert model.get_bounding_box((1,)) == ((-1, 0), (-1, 0)) 974 assert model.get_bounding_box((2,)) == ((0, 1), (0, 1)) 975 976 977def test_compound_bounding_box(): 978 model = models.Gaussian1D() 979 truth = models.Gaussian1D() 980 bbox1 = CompoundBoundingBox.validate(model, {(1,): (-1, 0), (2,): (0, 1)}, 981 selector_args=[('x', False)]) 982 bbox2 = CompoundBoundingBox.validate(model, {(-0.5,): (-1, 0), (0.5,): (0, 1)}, 983 selector_args=[('x', False)]) 984 985 # Using with_bounding_box to pass a selector 986 model.bounding_box = bbox1 987 assert model(-0.5) == truth(-0.5) 988 assert model(-0.5, with_bounding_box=(1,)) == truth(-0.5) 989 assert np.isnan(model(-0.5, with_bounding_box=(2,))) 990 assert model(0.5) == truth(0.5) 991 assert model(0.5, with_bounding_box=(2,)) == truth(0.5) 992 assert np.isnan(model(0.5, with_bounding_box=(1,))) 993 994 # Using argument value to pass bounding_box 995 model.bounding_box = bbox2 996 assert model(-0.5) == truth(-0.5) 997 assert model(-0.5, with_bounding_box=True) == truth(-0.5) 998 assert model(0.5) == truth(0.5) 999 assert model(0.5, with_bounding_box=True) == truth(0.5) 1000 with pytest.raises(RuntimeError): 1001 model(0, with_bounding_box=True) 1002 1003 model1 = models.Gaussian1D() 1004 truth1 = models.Gaussian1D() 1005 model2 = models.Const1D(2) 1006 truth2 = models.Const1D(2) 1007 model = model1 + model2 1008 truth = truth1 + truth2 1009 assert isinstance(model, CompoundModel) 1010 1011 model.bounding_box = bbox1 1012 assert model(-0.5) == truth(-0.5) 1013 assert model(-0.5, with_bounding_box=1) == truth(-0.5) 1014 assert np.isnan(model(-0.5, with_bounding_box=(2,))) 1015 assert model(0.5) == truth(0.5) 1016 assert model(0.5, with_bounding_box=2) == truth(0.5) 1017 assert np.isnan(model(0.5, with_bounding_box=(1,))) 1018 1019 model.bounding_box = bbox2 1020 assert model(-0.5) == truth(-0.5) 1021 assert model(-0.5, with_bounding_box=True) == truth(-0.5) 1022 assert model(0.5) == truth(0.5) 1023 assert model(0.5, with_bounding_box=True) == truth(0.5) 1024 with pytest.raises(RuntimeError): 1025 model(0, with_bounding_box=True) 1026 1027 1028def test_bind_bounding_box(): 1029 model = models.Polynomial2D(3) 1030 bbox = ((-1, 1), (-2, 2)) 1031 1032 bind_bounding_box(model, bbox) 1033 assert model.get_bounding_box() is not None 1034 assert model.bounding_box == bbox 1035 assert model.bounding_box['x'] == (-2, 2) 1036 assert model.bounding_box['y'] == (-1, 1) 1037 1038 bind_bounding_box(model, bbox, order='F') 1039 assert model.get_bounding_box() is not None 1040 assert model.bounding_box == bbox 1041 assert model.bounding_box['x'] == (-1, 1) 1042 assert model.bounding_box['y'] == (-2, 2) 1043 1044 1045def test_bind_compound_bounding_box_using_with_bounding_box_select(): 1046 """ 1047 This demonstrates how to bind multiple bounding_boxes which are 1048 selectable using the `with_bounding_box`, note there must be a 1049 fall-back to implicit. 1050 """ 1051 model = models.Gaussian1D() 1052 truth = models.Gaussian1D() 1053 1054 bbox = (0, 1) 1055 with pytest.raises(AttributeError): 1056 bind_compound_bounding_box(model, bbox, 'x') 1057 1058 bbox = {0: (-1, 0), 1: (0, 1)} 1059 bind_compound_bounding_box(model, bbox, [('x', False)]) 1060 1061 # No bounding box 1062 assert model(-0.5) == truth(-0.5) 1063 assert model(0.5) == truth(0.5) 1064 assert model(0) == truth(0) 1065 assert model(1) == truth(1) 1066 1067 # `with_bounding_box` selects as `-0.5` will not be a key 1068 assert model(-0.5, with_bounding_box=0) == truth(-0.5) 1069 assert np.isnan(model(-0.5, with_bounding_box=1)) 1070 1071 # `with_bounding_box` selects as `0.5` will not be a key 1072 assert model(0.5, with_bounding_box=1) == truth(0.5) 1073 assert np.isnan(model(0.5, with_bounding_box=(0,))) 1074 1075 # Fall back onto implicit selector 1076 assert model(0, with_bounding_box=True) == truth(0) 1077 assert model(1, with_bounding_box=True) == truth(1) 1078 1079 # Attempt to fall-back on implicit selector, but no bounding_box 1080 with pytest.raises(RuntimeError): 1081 model(0.5, with_bounding_box=True) 1082 1083 # Override implicit selector 1084 assert np.isnan(model(1, with_bounding_box=0)) 1085 1086 1087def test_fix_inputs_compound_bounding_box(): 1088 base_model = models.Gaussian2D(1, 2, 3, 4, 5) 1089 bbox = {2.5: (-1, 1), 3.14: (-7, 3)} 1090 1091 model = fix_inputs(base_model, {'y': 2.5}, bounding_boxes=bbox) 1092 assert model.bounding_box == (-1, 1) 1093 model = fix_inputs(base_model, {'x': 2.5}, bounding_boxes=bbox) 1094 assert model.bounding_box == (-1, 1) 1095 1096 model = fix_inputs(base_model, {'y': 2.5}, bounding_boxes=bbox, selector_args=(('y', True),)) 1097 assert model.bounding_box == (-1, 1) 1098 model = fix_inputs(base_model, {'x': 2.5}, bounding_boxes=bbox, selector_args=(('x', True),)) 1099 assert model.bounding_box == (-1, 1) 1100 model = fix_inputs(base_model, {'x': 2.5}, bounding_boxes=bbox, selector_args=((0, True),)) 1101 assert model.bounding_box == (-1, 1) 1102 1103 base_model = models.Identity(4) 1104 bbox = {(2.5, 1.3): ((-1, 1), (-3, 3)), (2.5, 2.71): ((-3, 3), (-1, 1))} 1105 1106 model = fix_inputs(base_model, {'x0': 2.5, 'x1': 1.3}, bounding_boxes=bbox) 1107 assert model.bounding_box == ((-1, 1), (-3, 3)) 1108 1109 model = fix_inputs(base_model, {'x0': 2.5, 'x1': 1.3}, bounding_boxes=bbox, 1110 selector_args=(('x0', True), ('x1', True))) 1111 assert model.bounding_box == ((-1, 1), (-3, 3)) 1112 model = fix_inputs(base_model, {'x0': 2.5, 'x1': 1.3}, bounding_boxes=bbox, 1113 selector_args=((0, True), (1, True))) 1114 assert model.bounding_box == ((-1, 1), (-3, 3)) 1115 1116 1117def test_model_copy_with_bounding_box(): 1118 model = models.Polynomial2D(2) 1119 bbox = ModelBoundingBox.validate(model, ((-0.5, 1047.5), (-0.5, 2047.5)), order='F') 1120 1121 # No bbox 1122 model_copy = model.copy() 1123 assert id(model_copy) != id(model) 1124 assert model_copy.get_bounding_box() == model.get_bounding_box() == None 1125 1126 # with bbox 1127 model.bounding_box = bbox 1128 model_copy = model.copy() 1129 assert id(model_copy) != id(model) 1130 assert id(model_copy.bounding_box) != id(model.bounding_box) 1131 for index, interval in model.bounding_box.intervals.items(): 1132 interval_copy = model_copy.bounding_box.intervals[index] 1133 assert interval == interval_copy 1134 assert id(interval) != interval_copy 1135 1136 # add model to compound model 1137 model1 = model | models.Identity(1) 1138 model_copy = model1.copy() 1139 assert id(model_copy) != id(model1) 1140 assert model_copy.get_bounding_box() == model1.get_bounding_box() == None 1141 1142 1143def test_compound_model_copy_with_bounding_box(): 1144 model = models.Shift(1) & models.Shift(2) & models.Identity(1) 1145 model.inputs = ('x', 'y', 'slit_id') 1146 bbox = ModelBoundingBox.validate(model, ((-0.5, 1047.5), (-0.5, 2047.5), (-np.inf, np.inf)), order='F') 1147 1148 # No bbox 1149 model_copy = model.copy() 1150 assert id(model_copy) != id(model) 1151 assert model_copy.get_bounding_box() == model.get_bounding_box() == None 1152 1153 # with bbox 1154 model.bounding_box = bbox 1155 model_copy = model.copy() 1156 assert id(model_copy) != id(model) 1157 assert id(model_copy.bounding_box) != id(model.bounding_box) 1158 for index, interval in model.bounding_box.intervals.items(): 1159 interval_copy = model_copy.bounding_box.intervals[index] 1160 assert interval == interval_copy 1161 assert id(interval) != interval_copy 1162 1163 # add model to compound model 1164 model1 = model | models.Identity(3) 1165 model_copy = model1.copy() 1166 assert id(model_copy) != id(model1) 1167 assert model_copy.get_bounding_box() == model1.get_bounding_box() == None 1168 1169 1170def test_model_copy_with_compound_bounding_box(): 1171 model = models.Polynomial2D(2) 1172 bbox = {(0,): (-0.5, 1047.5), 1173 (1,): (-0.5, 3047.5)} 1174 cbbox = CompoundBoundingBox.validate(model, bbox, selector_args=[('x', True)], order='F') 1175 1176 # No cbbox 1177 model_copy = model.copy() 1178 assert id(model_copy) != id(model) 1179 assert model_copy.get_bounding_box() == model.get_bounding_box() == None 1180 1181 # with cbbox 1182 model.bounding_box = cbbox 1183 model_copy = model.copy() 1184 assert id(model_copy) != id(model) 1185 assert id(model_copy.bounding_box) != id(model.bounding_box) 1186 assert model_copy.bounding_box.selector_args == model.bounding_box.selector_args 1187 assert id(model_copy.bounding_box.selector_args) != id(model.bounding_box.selector_args) 1188 for selector, bbox in model.bounding_box.bounding_boxes.items(): 1189 for index, interval in bbox.intervals.items(): 1190 interval_copy = model_copy.bounding_box.bounding_boxes[selector].intervals[index] 1191 assert interval == interval_copy 1192 assert id(interval) != interval_copy 1193 1194 # add model to compound model 1195 model1 = model | models.Identity(1) 1196 model_copy = model1.copy() 1197 assert id(model_copy) != id(model1) 1198 assert model_copy.get_bounding_box() == model1.get_bounding_box() == None 1199 1200 1201def test_compound_model_copy_with_compound_bounding_box(): 1202 model = models.Shift(1) & models.Shift(2) & models.Identity(1) 1203 model.inputs = ('x', 'y', 'slit_id') 1204 bbox = {(0,): ((-0.5, 1047.5), (-0.5, 2047.5)), 1205 (1,): ((-0.5, 3047.5), (-0.5, 4047.5)), } 1206 cbbox = CompoundBoundingBox.validate(model, bbox, selector_args=[('slit_id', True)], order='F') 1207 1208 # No cbbox 1209 model_copy = model.copy() 1210 assert id(model_copy) != id(model) 1211 assert model_copy.get_bounding_box() == model.get_bounding_box() == None 1212 1213 # with cbbox 1214 model.bounding_box = cbbox 1215 model_copy = model.copy() 1216 assert id(model_copy) != id(model) 1217 assert id(model_copy.bounding_box) != id(model.bounding_box) 1218 assert model_copy.bounding_box.selector_args == model.bounding_box.selector_args 1219 assert id(model_copy.bounding_box.selector_args) != id(model.bounding_box.selector_args) 1220 for selector, bbox in model.bounding_box.bounding_boxes.items(): 1221 for index, interval in bbox.intervals.items(): 1222 interval_copy = model_copy.bounding_box.bounding_boxes[selector].intervals[index] 1223 assert interval == interval_copy 1224 assert id(interval) != interval_copy 1225 1226 # add model to compound model 1227 model1 = model | models.Identity(3) 1228 model_copy = model1.copy() 1229 assert id(model_copy) != id(model1) 1230 assert model_copy.get_bounding_box() == model1.get_bounding_box() == None 1231 1232 1233def test_compound_model_copy_user_attribute(): 1234 """Regression test for issue #12370""" 1235 1236 model = models.Gaussian2D(100, 25, 25, 5, 5) | models.Identity(1) 1237 model.xname = 'x_mean' # user-defined attribute 1238 assert hasattr(model, 'xname') 1239 assert model.xname == 'x_mean' 1240 1241 model_copy = model.copy() 1242 model_copy.xname 1243 assert hasattr(model_copy, 'xname') 1244 assert model_copy.xname == 'x_mean' 1245 1246 1247def test_model_mixed_array_scalar_bounding_box(): 1248 """Regression test for issue #12319""" 1249 1250 model = models.Gaussian2D() 1251 bbox = ModelBoundingBox.validate(model, ((-1, 1), (-np.inf, np.inf)), order='F') 1252 model.bounding_box = bbox 1253 1254 x = np.array([-0.5, 0.5]) 1255 y = 0 1256 1257 # Everything works when its all in the bounding box 1258 assert (model(x, y) == (model(x, y, with_bounding_box=True))).all() 1259 1260 1261def test_compound_model_mixed_array_scalar_bounding_box(): 1262 """Regression test for issue #12319""" 1263 1264 model = models.Shift(1) & models.Shift(2) & models.Identity(1) 1265 model.inputs = ('x', 'y', 'slit_id') 1266 bbox = ModelBoundingBox.validate(model, ((-0.5, 1047.5), (-0.5, 2047.5), (-np.inf, np.inf)), order='F') 1267 model.bounding_box = bbox 1268 x = np.array([1000, 1001]) 1269 y = np.array([2000, 2001]) 1270 slit_id = 0 1271 1272 # Everything works when its all in the bounding box 1273 value0 = model(x, y, slit_id) 1274 value1 = model(x, y, slit_id, with_bounding_box=True) 1275 assert_equal(value0, value1) 1276 1277 1278def test_model_with_bounding_box_true_and_single_output(): 1279 """Regression test for issue #12373""" 1280 1281 model = models.Mapping((1,)) 1282 x = [1, 2] 1283 y = [3, 4] 1284 1285 # Check baseline 1286 assert_equal(model(x, y), [3, 4]) 1287 # Check with_bounding_box=True should be the same 1288 assert_equal(model(x, y, with_bounding_box=True), [3, 4]) 1289 1290 model.bounding_box = ((-np.inf, np.inf), (-np.inf, np.inf)) 1291 # Check baseline 1292 assert_equal(model(x, y), [3, 4]) 1293 # Check with_bounding_box=True should be the same 1294 assert_equal(model(x, y, with_bounding_box=True), [3, 4]) 1295 1296 1297def test_compound_model_with_bounding_box_true_and_single_output(): 1298 """Regression test for issue #12373""" 1299 1300 model = models.Mapping((1,)) | models.Shift(1) 1301 x = [1, 2] 1302 y = [3, 4] 1303 1304 # Check baseline 1305 assert_equal(model(x, y), [4, 5]) 1306 # Check with_bounding_box=True should be the same 1307 assert_equal(model(x, y, with_bounding_box=True), [4, 5]) 1308 1309 model.bounding_box = ((-np.inf, np.inf), (-np.inf, np.inf)) 1310 # Check baseline 1311 assert_equal(model(x, y), [4, 5]) 1312 # Check with_bounding_box=True should be the same 1313 assert_equal(model(x, y, with_bounding_box=True), [4, 5]) 1314