• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nikhil-sarin / redback / 14431351016

13 Apr 2025 04:40PM UTC coverage: 86.656% (+6.0%) from 80.663%
14431351016

push

github

web-flow
Merge pull request #266 from nikhil-sarin/temperature_radius_and_lbol_estimation

A big overhaul including GP estimation, T and R estimation, and Lbol estimation. Some filter changes, lots of tests. New docs. New likelihoods.

1621 of 1828 new or added lines in 14 files covered. (88.68%)

4 existing lines in 2 files now uncovered.

12676 of 14628 relevant lines covered (86.66%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.68
/test/plotting_test.py
1
import os
1✔
2
import unittest
1✔
3
from unittest import mock
1✔
4
from unittest.mock import MagicMock, patch, PropertyMock, ANY
1✔
5
import numpy as np
1✔
6
import pandas as pd
1✔
7
from redback.transient import Spectrum, Transient
1✔
8
from redback.plotting import (SpecPlotter, IntegratedFluxPlotter, LuminosityOpticalPlotter,
1✔
9
    _FilenameGetter, _FilePathGetter, Plotter, SpectrumPlotter, MagnitudePlotter)
10

11
from types import SimpleNamespace
1✔
12
import matplotlib.pyplot as plt
1✔
13

14

15

16
# Dummy transient: Only the attributes needed are defined.
17
class DummyTransient:
1✔
18
    def __init__(self, name, directory_path):
1✔
19
        self.name = name
1✔
20
        self.directory_structure = SimpleNamespace(directory_path=directory_path)
1✔
21
    # For our tests the other transient attributes aren’t required.
22

23
class TestFilenameAndFilePathGetters(unittest.TestCase):
1✔
24

25
    def setUp(self):
1✔
26
        # Create a dummy transient with a name and a directory_path.
27
        self.dummy_transient = DummyTransient(name="TestTransient", directory_path="/dummy/path")
1✔
28
        # Create a Plotter instance with no extra kwargs.
29
        self.plotter = Plotter(self.dummy_transient)
1✔
30

31
    def test_filename_getter_default(self):
1✔
32
        """
33
        Test that an attribute defined with _FilenameGetter returns
34
        the default filename based on the transient name and suffix.
35
        For _data_plot_filename (with suffix "data") it should be:
36
          "TestTransient_data.png"
37
        """
38
        expected = "TestTransient_data.png"
1✔
39
        self.assertEqual(self.plotter._data_plot_filename, expected)
1✔
40

41
    def test_filename_getter_override(self):
1✔
42
        """
43
        Test that if the Plotter is constructed with a 'filename' kwarg, that value overrides the default.
44
        """
45
        kwargs = {"filename": "override.png"}
1✔
46
        plotter_override = Plotter(self.dummy_transient, **kwargs)
1✔
47
        self.assertEqual(plotter_override._data_plot_filename, "override.png")
1✔
48

49
    def test_get_filename_method(self):
1✔
50
        """
51
        Test that the get_filename() method returns the provided value or the default.
52
        """
53
        # When no filename is provided, get_filename(default) should return the default.
54
        default_value = "default.png"
1✔
55
        self.assertEqual(self.plotter.get_filename(default_value), default_value)
1✔
56
        # When an override value is provided via kwargs, the override is returned.
57
        kwargs = {"filename": "override.png"}
1✔
58
        plotter_override = Plotter(self.dummy_transient, **kwargs)
1✔
59
        self.assertEqual(plotter_override.get_filename(default_value), "override.png")
1✔
60

61
    def test_file_path_getter_default(self):
1✔
62
        """
63
        Test that _FilePathGetter returns the join of the output directory and filename.
64
        In our case:
65
          The _data_plot_outdir property returns the transient's directory_structure.directory_path,
66
          and _data_plot_filename returns "TestTransient_data.png"
67
          so the expected file path is os.path.join("/dummy/path", "TestTransient_data.png")
68
        """
69
        expected_outdir = "/dummy/path"
1✔
70
        expected_filename = "TestTransient_data.png"
1✔
71
        expected_filepath = os.path.join(expected_outdir, expected_filename)
1✔
72
        self.assertEqual(self.plotter._data_plot_filepath, expected_filepath)
1✔
73

74
    def test_file_path_getter_with_override(self):
1✔
75
        """
76
        Test that if the Plotter is constructed with an override for outdir and filename,
77
        the _FilePathGetter returns the joined path correctly.
78
        """
79
        kwargs = {"filename": "override.png", "outdir": "/new/path"}
1✔
80
        plotter_override = Plotter(self.dummy_transient, **kwargs)
1✔
81
        expected = os.path.join("/new/path", "override.png")
1✔
82
        self.assertEqual(plotter_override._data_plot_filepath, expected)
1✔
83

84
    def test_filename_set_no_effect(self):
1✔
85
        """
86
        Test that attempting to assign a new value to an attribute defined via _FilenameGetter does not
87
        change its computed value (since __set__ is defined as a no‐op).
88
        """
89
        original = self.plotter._data_plot_filename
1✔
90
        # Even though we try to set a new value, the descriptor’s __set__ (which does nothing) prevents overwriting.
91
        self.plotter._data_plot_filename = "new_value"
1✔
92
        self.assertEqual(self.plotter._data_plot_filename, original)
1✔
93

94
class TestSpecPlotter(unittest.TestCase):
1✔
95

96
    def setUp(self) -> None:
1✔
97
        angstroms = np.array([4000, 5000, 6000])
1✔
98
        flux_density = np.array([1e-17, 2e-17, 3e-17])
1✔
99
        flux_density_err = np.array([0.1e-17, 0.1e-17, 0.1e-17])
1✔
100
        self.spectrum = Spectrum(angstroms, flux_density, flux_density_err, name="test_spectrum")
1✔
101
        self.spectrum.directory_structure = SimpleNamespace(directory_path='/dummy/path')
1✔
102
        self.plotter = SpecPlotter(self.spectrum)
1✔
103

104
    def tearDown(self) -> None:
1✔
105
        plt.close('all')  # reset matplotlib global state
1✔
106
        mock.patch.stopall()
1✔
107
        self.spectrum = None
1✔
108
        self.plotter = None
1✔
109

110
    def test_get_angstroms_linear(self):
1✔
111
        mock_axes = MagicMock()
1✔
112
        mock_axes.get_yscale.return_value = 'linear'
1✔
113
        result = self.plotter._get_angstroms(mock_axes)
1✔
114
        self.assertTrue(np.all(np.isclose(result, np.linspace(
1✔
115
            self.spectrum.angstroms[0] * self.plotter.xlim_low_multiplier,
116
            self.spectrum.angstroms[-1] * self.plotter.xlim_high_multiplier,
117
            200
118
        ))))
119

120
    def test_get_angstroms_log(self):
1✔
121
        mock_axes = MagicMock()
1✔
122
        mock_axes.get_yscale.return_value = 'log'
1✔
123
        result = self.plotter._get_angstroms(mock_axes)
1✔
124
        self.assertTrue(np.allclose(
1✔
125
            result,
126
            np.exp(np.linspace(
127
                np.log(self.spectrum.angstroms[0] * self.plotter.xlim_low_multiplier),
128
                np.log(self.spectrum.angstroms[-1] * self.plotter.xlim_high_multiplier),
129
                200
130
            ))
131
        ))
132

133
    def test_xlim_low_property(self):
1✔
134
        calculated_value = self.plotter._xlim_low
1✔
135
        expected_value = self.plotter.xlim_low_multiplier * self.spectrum.angstroms[0]
1✔
136
        if expected_value == 0:
1✔
NEW
137
            expected_value += 1e-3
×
138
        self.assertEqual(calculated_value, expected_value)
1✔
139

140
    def test_xlim_high_property(self):
1✔
141
        calculated_value = self.plotter._xlim_high
1✔
142
        expected_value = self.plotter.xlim_high_multiplier * self.spectrum.angstroms[-1]
1✔
143
        self.assertEqual(calculated_value, expected_value)
1✔
144

145
    def test_ylim_low_property(self):
1✔
146
        calculated_value = self.plotter._ylim_low
1✔
147
        expected_value = self.plotter.ylim_low_multiplier * min(self.spectrum.flux_density) / 1e-17
1✔
148
        self.assertEqual(calculated_value, expected_value)
1✔
149

150
    def test_ylim_high_property(self):
1✔
151
        calculated_value = self.plotter._ylim_high
1✔
152
        expected_value = self.plotter.ylim_high_multiplier * np.max(self.spectrum.flux_density) / 1e-17
1✔
153
        self.assertEqual(calculated_value, expected_value)
1✔
154

155
    def test_y_err_property(self):
1✔
156
        calculated_value = self.plotter._y_err
1✔
157
        expected_value = np.array([np.abs(self.spectrum.flux_density_err)])
1✔
158
        np.testing.assert_array_equal(calculated_value, expected_value)
1✔
159

160
    def test_data_plot_outdir(self):
1✔
161
        calculated_value = self.plotter._data_plot_outdir
1✔
162
        expected_value = self.spectrum.directory_structure.directory_path
1✔
163
        self.assertEqual(calculated_value, expected_value)
1✔
164

165
    def test_get_filename(self):
1✔
166
        filename = "test_default.png"
1✔
167
        calculated_value = self.plotter.get_filename(default=filename)
1✔
168
        self.assertEqual(calculated_value, filename)
1✔
169

170
    def test_get_random_parameters(self):
1✔
171
        mock_posterior = pd.DataFrame({'log_likelihood': [1, 2, 3], 'param': [0.1, 0.2, 0.3]})
1✔
172
        self.plotter.kwargs['posterior'] = mock_posterior
1✔
173
        self.plotter.kwargs['random_models'] = 2
1✔
174
        random_parameters = self.plotter._get_random_parameters()
1✔
175
        self.assertEqual(len(random_parameters), 2)
1✔
176
        for param in random_parameters:
1✔
177
            self.assertIn(param['param'], mock_posterior['param'].values)
1✔
178

179
    def test_max_like_params(self):
1✔
180
        mock_posterior = pd.DataFrame({'log_likelihood': [1, 2, 3], 'param': [0.1, 0.2, 0.3]})
1✔
181
        self.plotter.kwargs['posterior'] = mock_posterior
1✔
182
        max_like_params = self.plotter._max_like_params
1✔
183
        self.assertTrue(max_like_params['log_likelihood'], 3)
1✔
184

185
class TestIntegratedFluxPlotter(unittest.TestCase):
1✔
186
    def setUp(self) -> None:
1✔
187
        self.transient_mock = MagicMock(spec=Transient)
1✔
188
        self.transient_mock.x = np.logspace(0, 2, 10)
1✔
189
        self.transient_mock.y = np.logspace(0, 2, 10)
1✔
190
        self.transient_mock.ylabel = "Test YLabel"
1✔
191
        self.transient_mock.name = "Test Transient"
1✔
192
        self.transient_mock.use_phase_model = False
1✔
193
        self.transient_mock.directory_structure = MagicMock()
1✔
194
        self.transient_mock.directory_structure.directory_path = "/mock/path"
1✔
195

196
        self.mock_model = MagicMock()
1✔
197
        self.mock_model.__name__ = "MockModel"  # Ensure __name__ is defined.
1✔
198

199
        self.plotter = IntegratedFluxPlotter(transient=self.transient_mock, model=self.mock_model)
1✔
200

201
        self.default_patches = patch.multiple(
1✔
202
            IntegratedFluxPlotter,
203
            _x_err=PropertyMock(return_value=np.zeros_like(self.transient_mock.x)),
204
            _y_err=PropertyMock(return_value=np.zeros_like(self.transient_mock.y)),
205
            _xlim_low=PropertyMock(return_value=0.1),
206
            _xlim_high=PropertyMock(return_value=200),
207
            _ylim_low=PropertyMock(return_value=0.1),
208
            _ylim_high=PropertyMock(return_value=200),
209
            _save_and_show=MagicMock()
210
        )
211
        self.default_patches.start()
1✔
212

213
    def tearDown(self) -> None:
1✔
214
        plt.close('all')  # reset matplotlib global state
1✔
215
        mock.patch.stopall()
1✔
216

217
    @patch("matplotlib.pyplot.figure", autospec=True)
1✔
218
    def test_plot_data(self, mock_figure):
1✔
219
        axes_mock = MagicMock()
1✔
220

221
        # Correctly and locally patch plt.gca:
222
        with patch("matplotlib.pyplot.gca", return_value=axes_mock):
1✔
223
            result_axes = self.plotter.plot_data(save=False, show=False)
1✔
224

225
        self.assertEqual(result_axes, axes_mock)
1✔
226
        axes_mock.errorbar.assert_called_once()
1✔
227
        axes_mock.set_xscale.assert_called_once_with("log")
1✔
228
        axes_mock.set_yscale.assert_called_once_with("log")
1✔
229
        axes_mock.set_xlim.assert_called_once_with(0.1, 200)
1✔
230
        axes_mock.set_ylim.assert_called_once_with(0.1, 200)
1✔
231
        axes_mock.set_xlabel.assert_called_once_with(
1✔
232
            r"Time since burst [s]", fontsize=self.plotter.fontsize_axes)
233
        axes_mock.set_ylabel.assert_called_once_with(
1✔
234
            self.transient_mock.ylabel, fontsize=self.plotter.fontsize_axes)
235
        axes_mock.annotate.assert_called_once_with(
1✔
236
            self.transient_mock.name,
237
            xy=self.plotter.xy,
238
            xycoords=self.plotter.xycoords,
239
            horizontalalignment=self.plotter.horizontalalignment,
240
            size=self.plotter.annotation_size
241
        )
242
        self.plotter._save_and_show.assert_called_once_with(
1✔
243
            filepath=self.plotter._data_plot_filepath, save=False, show=False)
244
        plt.close('all')
1✔
245

246
    def test_plot_data_with_custom_axes(self):
1✔
247
        # Test plot_data with a custom matplotlib Axes object
248
        fig, custom_axes = plt.subplots()
1✔
249
        axes = self.plotter.plot_data(axes=custom_axes, save=False, show=False)
1✔
250
        self.assertEqual(axes, custom_axes)
1✔
251

252
    def test_plot_lightcurve(self):
1✔
253
        # Create a real matplotlib figure and axes
254
        fig, real_axes = plt.subplots()
1✔
255

256
        # Mock the plot_lightcurves method to do nothing
257
        self.plotter._plot_lightcurves = MagicMock()
1✔
258

259
        # Mock get_times to return the test data
260
        self.plotter._get_times = MagicMock(return_value=self.transient_mock.x)
1✔
261

262
        # Use the real axes in the plot_lightcurve call
263
        result_axes = self.plotter.plot_lightcurve(axes=real_axes, save=False, show=False)
1✔
264

265
        # Now the assertion should pass
266
        self.assertIsInstance(result_axes, plt.Axes)
1✔
267
        self.plotter._plot_lightcurves.assert_called_once()
1✔
268

269
        # Clean up
270
        plt.close(fig)
1✔
271

272
    @patch("redback.plotting.IntegratedFluxPlotter._get_times", return_value=np.logspace(0, 2, 10))
1✔
273
    @patch("redback.plotting.IntegratedFluxPlotter._max_like_params", new_callable=PropertyMock)
1✔
274
    @patch("redback.plotting.IntegratedFluxPlotter._model_kwargs", new_callable=PropertyMock)
1✔
275
    def test_plot_residuals(self, mock_model_kwargs, mock_max_like_params, mock_get_times):
1✔
276
        """
277
        Test the plot_residuals method with proper posterior handling
278
        """
279
        # Create a valid posterior DataFrame
280
        valid_posterior = pd.DataFrame({
1✔
281
            "log_likelihood": [0.1, 5],
282
            "other_param": [1.1, 3]
283
        })
284

285
        # Mock the plot_lightcurves method to do nothing
286
        self.plotter._plot_lightcurves = MagicMock()
1✔
287

288
        # Set the posterior directly as an instance attribute instead of using PropertyMock
289
        self.plotter.posterior = valid_posterior
1✔
290

291
        # Mock additional required attributes
292
        mock_max_like_params.return_value = {}
1✔
293
        mock_model_kwargs.return_value = {}
1✔
294
        self.plotter.model = MagicMock(return_value=self.transient_mock.y, __name__="MockModel")
1✔
295

296
        # Execute the method under test
297
        axes = self.plotter.plot_residuals(save=False, show=False)
1✔
298

299
        # Verify the results
300
        self.assertIsInstance(axes, np.ndarray)
1✔
301
        self.assertEqual(axes.shape[0], 2)
1✔
302

303
        # Verify model was called with correct parameters
304
        self.plotter.model.assert_called_once_with(
1✔
305
            self.transient_mock.x,
306
            **self.plotter._max_like_params,
307
            **self.plotter._model_kwargs
308
        )
309

310
class TestLuminosityOpticalPlotter(unittest.TestCase):
1✔
311
    def setUp(self) -> None:
1✔
312
        self.mock_transient = MagicMock()
1✔
313
        self.mock_transient.x = np.array([1, 10, 100])
1✔
314
        self.mock_transient.x_err = None
1✔
315
        self.mock_transient.y = np.array([1e50, 2e50, 1.5e50])
1✔
316
        self.mock_transient.y_err = np.array([0.1e50, 0.2e50, 0.15e50])
1✔
317
        self.mock_transient.use_phase_model = False
1✔
318
        # self.mock_transient.reference_mjd_date = 0
319
        self.mock_transient.ylabel = "Luminosity"
1✔
320
        self.luminosity_plotter = LuminosityOpticalPlotter(transient=self.mock_transient)
1✔
321

322
    def tearDown(self) -> None:
1✔
323
        del self.luminosity_plotter
1✔
324

325
    def test_xlabel_property(self):
1✔
326
        self.assertEqual(
1✔
327
            self.luminosity_plotter._xlabel,
328
            r"Time since explosion [days]"
329
        )
330

331
    def test_ylabel_property(self):
1✔
332
        self.assertEqual(
1✔
333
            self.luminosity_plotter._ylabel,
334
            r"L$_{\rm bol}$ [$10^{50}$ erg s$^{-1}$]"
335
        )
336

337
    @mock.patch("matplotlib.pyplot.gca")
1✔
338
    def test_plot_data_creates_axes(self, mock_gca):
1✔
339
        mock_gca.return_value = plt.figure().add_subplot(111)
1✔
340
        axes = self.luminosity_plotter.plot_data(save=False, show=False)
1✔
341
        self.assertIsInstance(axes, plt.Axes)
1✔
342

343
    def test_plot_data_with_existing_axes(self):
1✔
344
        fig, ax = plt.subplots()
1✔
345
        returned_ax = self.luminosity_plotter.plot_data(axes=ax, save=False, show=False)
1✔
346
        self.assertIs(returned_ax, ax)
1✔
347

348
class TestSpectrumPlotter(unittest.TestCase):
1✔
349
    def setUp(self) -> None:
1✔
350
        # Mock transient setup
351
        self.mock_transient = mock.MagicMock()
1✔
352
        self.mock_transient.angstroms = np.linspace(4000, 7000, 200)  # Match the size in error message
1✔
353
        self.mock_transient.flux_density = np.sin(self.mock_transient.angstroms / 1000) * 1e-17
1✔
354
        self.mock_transient.flux_density_err = np.full_like(self.mock_transient.flux_density, 0.1e-17)
1✔
355
        self.mock_transient.xlabel = "Wavelength [Å]"
1✔
356
        self.mock_transient.ylabel = r"Flux ($10^{-17}$ erg s$^{-1}$ cm$^{-2}$ $\AA^{-1}$)"
1✔
357
        self.mock_transient.plot_with_time_label = True
1✔
358
        self.mock_transient.time = "100s"
1✔
359
        self.mock_transient.name = "TestSpectrum"
1✔
360

361
        # Create mock posterior DataFrame
362
        mock_data = {
1✔
363
            'log_likelihood': [-100, -50, -10],
364
            'param1': [1, 2, 3],
365
            'param2': [0.1, 0.2, 0.3]
366
        }
367
        self.mock_posterior = pd.DataFrame(mock_data)
1✔
368

369
        # Create mock model that returns actual data
370
        def mock_model_func(angstroms, **kwargs):
1✔
371
            # Return synthetic data matching the input wavelength array size
372
            return np.ones_like(angstroms) * 1e-17
1✔
373

374
        self.mock_model = mock.MagicMock(side_effect=mock_model_func)
1✔
375
        self.mock_model.__name__ = "MockModel"
1✔
376

377
        # Initialize plotter with mocked components
378
        self.spectrum_plotter = SpectrumPlotter(
1✔
379
            spectrum=self.mock_transient,
380
            posterior=self.mock_posterior,
381
            model=self.mock_model
382
        )
383

384
    def tearDown(self) -> None:
1✔
385
        del self.spectrum_plotter
1✔
386

387
    def test_plot_data(self):
1✔
388
        axes = mock.MagicMock()
1✔
389
        result_axes = self.spectrum_plotter.plot_data(axes=axes, save=False, show=False)
1✔
390

391
        self.assertEqual(result_axes, axes)
1✔
392

393
        call_args, call_kwargs = axes.plot.call_args
1✔
394
        np.testing.assert_array_equal(call_args[0], self.mock_transient.angstroms)
1✔
395
        np.testing.assert_array_equal(call_args[1], self.mock_transient.flux_density / 1e-17)
1✔
396
        self.assertEqual(call_kwargs.get('color'), self.spectrum_plotter.color)
1✔
397
        self.assertEqual(call_kwargs.get('lw'), self.spectrum_plotter.linewidth)
1✔
398

399
    def test_posterior_property(self):
1✔
400
        result = self.spectrum_plotter._posterior
1✔
401
        self.assertFalse(result.empty)
1✔
402
        self.assertTrue('log_likelihood' in result.columns)
1✔
403

404
    def test_max_like_params(self):
1✔
405
        result = self.spectrum_plotter._max_like_params
1✔
406
        self.assertIsNotNone(result)
1✔
407
        self.assertTrue('param1' in result.index)
1✔
408
        self.assertTrue('param2' in result.index)
1✔
409

410
    def test_plot_spectrum(self):
1✔
411
        axes = self.spectrum_plotter.plot_spectrum(save=False, show=False)
1✔
412
        self.assertIsInstance(axes, plt.Axes)
1✔
413
        # Verify that model was called
414
        self.mock_model.assert_called()
1✔
415

416
    def test_plot_residuals(self):
1✔
417
        axes = self.spectrum_plotter.plot_residuals(save=False, show=False)
1✔
418
        self.assertEqual(len(axes), 2)
1✔
419
        self.assertIsInstance(axes[0], plt.Axes)
1✔
420
        self.assertIsInstance(axes[1], plt.Axes)
1✔
421

422
class TestMagnitudePlotter(unittest.TestCase):
1✔
423
    def setUp(self) -> None:
1✔
424
        self.mock_transient = MagicMock(spec=Transient)
1✔
425
        self.mock_transient.use_phase_model = False
1✔
426
        self.mock_transient.name = "Test"
1✔
427
        self.mock_transient.active_bands = ["r", "g"]
1✔
428
        self.mock_transient.x = np.array([0, 1, 2, 3])
1✔
429
        self.mock_transient.y = np.array([10, 9, 8, 7])
1✔
430
        self.mock_transient.y_err = np.array([0.1, 0.2, 0.1, 0.2])
1✔
431
        self.mock_transient.list_of_band_indices = [[0, 1], [2, 3]]
1✔
432
        self.mock_transient.unique_bands = ["r", "g"]
1✔
433
        self.mock_transient.get_colors = MagicMock(return_value=["red", "green"])
1✔
434
        self.mock_transient.ylabel = "Magnitude"
1✔
435
        self.mock_transient.xlabel = "Time [days]"
1✔
436

437
        mock_posterior = pd.DataFrame({
1✔
438
            'log_likelihood': [-100, -50, -10]  # Example values
439
        })
440

441
        self.mock_transient.directory_structure = MagicMock()
1✔
442
        self.mock_transient.directory_structure.directory_path = "/mock/path"
1✔
443

444
        self.mock_model = mock.MagicMock()
1✔
445
        self.mock_model.__name__ = "MockModel"
1✔
446

447
        self.kwargs = {"xlabel": "Test X Label", "ylabel": "Test Y Label",
1✔
448
                       "posterior": mock_posterior, "model": self.mock_model}
449
        self.plotter = MagnitudePlotter(self.mock_transient, **self.kwargs)
1✔
450

451
    def tearDown(self) -> None:
1✔
452
        plt.close('all')  # reset matplotlib global state
1✔
453
        mock.patch.stopall()
1✔
454
        del self.mock_transient
1✔
455
        del self.plotter
1✔
456

457
    def test_color_property(self):
1✔
458
        self.assertEqual(self.plotter._colors, ["red", "green"])
1✔
459

460
    def test_xlabel_property_with_custom_label(self):
1✔
461
        self.assertEqual(self.plotter._xlabel, "Test X Label")
1✔
462

463
    def test_ylabel_property_with_custom_label(self):
1✔
464
        self.assertEqual(self.plotter._ylabel, "Test Y Label")
1✔
465

466
    def test_xlim_high_property(self):
1✔
467
        self.mock_transient.x = np.array([1, 2, 3])
1✔
468
        self.assertAlmostEqual(self.plotter._xlim_high, 1.2 * 3)
1✔
469

470
    def test_ylim_low_magnitude_property(self):
1✔
471
        self.mock_transient.y = np.array([10, 20, 30])
1✔
472
        self.assertEqual(self.plotter._ylim_low_magnitude, 0.8 * 10)
1✔
473

474
    def test_ylim_high_magnitude_property(self):
1✔
475
        self.mock_transient.y = np.array([10, 20, 30])
1✔
476
        self.assertEqual(self.plotter._ylim_high_magnitude, 1.2 * 30)
1✔
477

478
    @patch('matplotlib.pyplot.gca')
1✔
479
    def test_plot_data(self, mock_gca):
1✔
480
        mock_ax = MagicMock()
1✔
481
        mock_gca.return_value = mock_ax
1✔
482

483
        self.plotter.plot_data(save=False, show=False)
1✔
484

485
        mock_gca.assert_called_once()
1✔
486
        self.assertTrue(mock_ax.set_xlim.called)
1✔
487
        self.assertTrue(mock_ax.set_ylim.called)
1✔
488
        self.assertTrue(mock_ax.errorbar.called)
1✔
489

490
    @patch('matplotlib.pyplot.gca')
1✔
491
    def test_plot_lightcurve(self, mock_gca):
1✔
492
        mock_ax = MagicMock()
1✔
493
        mock_gca.return_value = mock_ax
1✔
494

495
        self.plotter.plot_lightcurve(save=False, show=False)
1✔
496

497
        mock_gca.assert_called_once()
1✔
498
        self.assertTrue(mock_ax.set_yscale.called)
1✔
499
        self.assertTrue(mock_ax.errorbar.called)
1✔
500

501

502
    def test_get_multiband_plot_label(self):
1✔
503
        band = "r"
1✔
504
        freq = 1e12
1✔
505
        label = self.plotter._get_multiband_plot_label(band, freq)
1✔
506
        self.assertEqual(label, "r")
1✔
507

508
    def test_nrows_property(self):
1✔
509
        with patch('redback.plotting.MagnitudePlotter._filters', new_callable=PropertyMock) as mock_filters:
1✔
510
            mock_filters.return_value = ["r", "g"]
1✔
511
            self.assertEqual(self.plotter._nrows, 1)
1✔
512

513
    def test_figsize_property(self):
1✔
514
        with patch('redback.plotting.MagnitudePlotter._nrows', new_callable=PropertyMock) as mock_nrows:
1✔
515
            mock_nrows.return_value = 1
1✔
516
            self.assertEqual(self.plotter._figsize, (12, 4))
1✔
517

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc