Skip to content

Commit c4059f2

Browse files
MatthewPortmankecnryrosteen
authored
Speedup tests (#3696)
* Add kwarg `n_cpu` * Set to use only single core in `test_cube_fit_after_unit_change` to avoid overhead from multiprocessing * Move more tests to single cpu * Also add coverage for multiprocessing vs single cpu in cube fitting backend test. * Move another test to single cpu * Model fitting strikes again! * Move another test to single cpu * Model fitting strikes again! * Use fixture instead of for-loop * Can be a tiny bit slower in serial but in a parallel environment, it's a no-brainer. * Style fixes * Add parallelization for pytest * Example style fix * Change load distribution technique * Should be a bit better than the old since we only have two workers (for now) * Add `cached_uri` logic * Re-arranged uri_or_file logic * Add durations flag for future test optimization/debugging * Update jdaviz/core/tests/test_autoconfig.py Co-authored-by: Kyle Conroy <[email protected]> * Hardcoding n_cpu=1 instead of using a variable * Style adjustment * Re-use n_cpu for one of the tests so it's a variable again * Overwrite uri instead of new variable * Create ParallelMixin to use traitlet instead of kwarg * Barebones framework for eventual work with parallelization * Removed unnecessary import * Use an integer traitlet instead of unicode * To be consistent with current implementation and avoid the try except. This could change in the future * n_cpu is now a traitlet passed directly into the model_fitting object * Revert traitlet (keeping mixin) * parallel_n_cpu is now (temporarily) an attribute of model_fitting * traitlet implementation and ParallelMixin to come later * Also now both fitting backends are parameterized for multiprocessing tests for proper coverage * Fixed bug, properly access object now * Leaving a TODO for future parallel work * Moved comment about running in serial to first instance of calculate_fit * Also force n_cpu to 1 in that test as well. I think I caught them all now. * Style fixes * Style fixes * Remove ParallelMixin * To be worked on eventually! * Update template_mixin.py Style fixes * Update model_fitting.py Taking out references to ParallelMixin * Apply suggestions from code review Removed leftover code (thanks Ricky!) Co-authored-by: Ricky O'Steen <[email protected]> * Update jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py Co-authored-by: Ricky O'Steen <[email protected]> * Apply suggestions from code review Co-authored-by: Kyle Conroy <[email protected]> --------- Co-authored-by: Kyle Conroy <[email protected]> Co-authored-by: Ricky O'Steen <[email protected]>
1 parent 77b09ce commit c4059f2

File tree

6 files changed

+68
-32
lines changed

6 files changed

+68
-32
lines changed

jdaviz/configs/default/plugins/model_fitting/model_fitting.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def __init__(self, *args, **kwargs):
168168
self.hub.subscribe(self, GlobalDisplayUnitChanged,
169169
handler=self._on_global_display_unit_changed)
170170

171+
self.parallel_n_cpu = None
171172
self._set_relevant()
172173

173174
@observe('dataset_items')
@@ -1185,7 +1186,8 @@ def _fit_model_to_spectrum(self, add_data):
11851186
models_to_fit,
11861187
self.model_equation,
11871188
run_fitter=True,
1188-
window=None
1189+
window=None,
1190+
n_cpu=self.parallel_n_cpu,
11891191
)
11901192
except AttributeError:
11911193
msg = SnackbarMessage("Unable to fit: model equation may be invalid",
@@ -1299,7 +1301,8 @@ def _fit_model_to_cube(self, add_data):
12991301
models_to_fit,
13001302
self.model_equation,
13011303
run_fitter=True,
1302-
window=None
1304+
window=None,
1305+
n_cpu=self.parallel_n_cpu
13031306
)
13041307
except ValueError:
13051308
snackbar_message = SnackbarMessage(

jdaviz/configs/default/plugins/model_fitting/tests/test_fitting.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def test_parameter_retrieval(cubeviz_helper, spectral_cube_wcs):
101101
assert cubeviz_helper.app._get_display_unit('sb') == sb_unit
102102

103103
plugin.create_model_component("Linear1D", "L")
104+
# NOTE: Hardcoding n_cpu=1 to run in serial, it's slower to spool up
105+
# multiprocessing for the size of the cube
106+
plugin._obj.parallel_n_cpu = 1
104107
with warnings.catch_warnings():
105108
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
106109
plugin.calculate_fit()
@@ -119,8 +122,13 @@ def test_parameter_retrieval(cubeviz_helper, spectral_cube_wcs):
119122
atol=1e-10 * sb_unit)
120123

121124

122-
@pytest.mark.parametrize('unc', ('zeros', None))
123-
def test_fitting_backend(unc):
125+
@pytest.mark.parametrize(
126+
('n_cpu', 'unc'), [
127+
(1, "zeros"),
128+
(1, None),
129+
(None, "zeros"),
130+
(None, None),])
131+
def test_fitting_backend(n_cpu, unc):
124132
np.random.seed(42)
125133

126134
x, y = build_spectrum()
@@ -142,24 +150,30 @@ def test_fitting_backend(unc):
142150

143151
# Returns the initial model
144152
fm, fitted_spectrum = fb.fit_model_to_spectrum(spectrum, model_list, expression,
145-
run_fitter=False)
153+
run_fitter=False, n_cpu=n_cpu)
146154

147155
parameters_expected = np.array([0.7, 4.65, 0.3, 2., 5.55, 0.3, -2.,
148156
8.15, 0.2, 1.])
149157
assert_allclose(fm.parameters, parameters_expected, atol=1e-5)
150158

151159
# Returns the fitted model
152160
fm, fitted_spectrum = fb.fit_model_to_spectrum(spectrum, model_list, expression,
153-
run_fitter=True)
161+
run_fitter=True, n_cpu=n_cpu)
154162

155163
parameters_expected = np.array([1.0104705, 4.58956282, 0.19590464, 2.39892026,
156164
5.49867754, 0.10834472, -1.66902953, 8.19714439,
157165
0.09535613, 3.99125545])
158166
assert_allclose(fm.parameters, parameters_expected, atol=1e-5)
159167

160168

161-
@pytest.mark.parametrize('unc', ('zeros', None))
162-
def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path):
169+
# For coverage of serial vs multiprocessing and unc
170+
@pytest.mark.parametrize(
171+
('n_cpu', 'unc'), [
172+
(1, "zeros"),
173+
(1, None),
174+
(None, "zeros"),
175+
(None, None),])
176+
def test_cube_fitting_backend(cubeviz_helper, unc, n_cpu, tmp_path):
163177
np.random.seed(42)
164178

165179
SIGMA = 0.1 # noise in data
@@ -214,9 +228,6 @@ def test_cube_fitting_backend(cubeviz_helper, unc, tmp_path):
214228
model_list = [g1f, g2f, g3f, zero_level]
215229
expression = "g1 + g2 + g3 + const1d"
216230

217-
n_cpu = None
218-
# n_cpu = 1 # NOTE: UNCOMMENT TO DEBUG LOCALLY, AS NEEDED
219-
220231
# Fit to all spaxels.
221232
with warnings.catch_warnings():
222233
warnings.filterwarnings("ignore", message="The fit may be unsuccessful.*")
@@ -316,6 +327,7 @@ def test_results_table(specviz_helper, spectrum1d):
316327
uc = specviz_helper.plugins['Unit Conversion']
317328

318329
mf.add_results.label = 'linear model'
330+
mf._obj.parallel_n_cpu = 1
319331
with warnings.catch_warnings():
320332
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
321333
mf.calculate_fit(add_data=True)
@@ -401,6 +413,7 @@ def test_incompatible_units(specviz_helper, spectrum1d):
401413
mf.create_model_component('Linear1D')
402414

403415
mf.add_results.label = 'model native units'
416+
mf._obj.parallel_n_cpu = 1
404417
with warnings.catch_warnings():
405418
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
406419
mf.calculate_fit(add_data=True)
@@ -430,6 +443,8 @@ def test_cube_fit_with_nans(cubeviz_helper):
430443
mf = cubeviz_helper.plugins["Model Fitting"]
431444
mf.cube_fit = True
432445
mf.create_model_component("Const1D")
446+
mf._obj.parallel_n_cpu = 1
447+
433448
with warnings.catch_warnings():
434449
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
435450
mf.calculate_fit()
@@ -456,6 +471,8 @@ def test_cube_fit_with_subset_and_nans(cubeviz_helper):
456471
mf.cube_fit = True
457472
mf.spectral_subset = 'Subset 1'
458473
mf.create_model_component("Const1D")
474+
mf._obj.parallel_n_cpu = 1
475+
459476
with warnings.catch_warnings():
460477
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
461478
mf.calculate_fit()
@@ -473,6 +490,7 @@ def test_fit_with_count_units(cubeviz_helper):
473490
mf = cubeviz_helper.plugins["Model Fitting"]
474491
mf.cube_fit = True
475492
mf.create_model_component("Const1D")
493+
mf._obj.parallel_n_cpu = 1
476494

477495
# ensures specutils.Spectrum.with_flux_unit has access to Jdaviz custom equivalencies for
478496
# PIX^2 unit
@@ -504,6 +522,7 @@ def test_cube_fit_after_unit_change(cubeviz_helper, solid_angle_unit):
504522
# Check that the parameter is using the current units when initialized
505523
assert mf._obj.component_models[0]['parameters'][0]['unit'] == f'MJy / {solid_angle_string}'
506524

525+
mf._obj.parallel_n_cpu = 1
507526
with warnings.catch_warnings():
508527
warnings.filterwarnings('ignore', message='Model is linear in parameters.*')
509528
mf.calculate_fit()
@@ -569,6 +588,7 @@ def test_deconf_mf_with_subset(deconfigged_helper):
569588
# ensure deconfigged app can access subsets in plugin
570589
mf.spectral_subset.selected = 'Subset 1'
571590
mf.add_results.label = 'linear model'
591+
mf._obj.parallel_n_cpu = 1
572592
mf.create_model_component()
573593

574594
with warnings.catch_warnings():

jdaviz/configs/default/plugins/model_fitting/tests/test_plugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ def test_register_cube_model(cubeviz_helper, spectrum1d_cube):
150150
modelfit_plugin.reestimate_model_parameters()
151151
assert modelfit_plugin._obj.results_label_default == 'model'
152152
assert modelfit_plugin._obj.results_label == test_label
153+
154+
modelfit_plugin._obj.parallel_n_cpu = 1
153155
with warnings.catch_warnings():
154156
warnings.filterwarnings('ignore', message='.*Model is linear in parameters.*')
155157
modelfit_plugin.calculate_fit()
@@ -177,6 +179,8 @@ def test_fit_cube_no_wcs(cubeviz_helper):
177179
mf.cube_fit = True
178180
# Need to manually reestimate the parameters to update the units
179181
mf.reestimate_model_parameters()
182+
183+
mf._obj.parallel_n_cpu = 1
180184
with warnings.catch_warnings():
181185
warnings.filterwarnings("ignore", message="Model is linear in parameters.*")
182186
fitted_model, output_cube = mf.calculate_fit(add_data=True)

jdaviz/configs/specviz/plugins/line_analysis/tests/test_lineflux.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import pytest
66
from specutils import Spectrum
77

8-
from jdaviz import Cubeviz
9-
108
import warnings
119
warnings.filterwarnings('ignore')
1210

@@ -73,24 +71,30 @@ def _calculate_line_flux(viz_helper):
7371
@pytest.mark.filterwarnings(r"ignore:.* contains multiple slashes")
7472
@pytest.mark.filterwarnings(r"ignore:.* apply_slider_redshift")
7573
@pytest.mark.parametrize('spectra_fluxunit', test_cases)
76-
def test_cubeviz_collapse_fluxunits(spectrum1d_cube_custom_fluxunit, spectra_fluxunit):
77-
''' Calculates line flux and checks the units for each collapse function '''
74+
@pytest.mark.parametrize('function', COLLAPSE_FUNCTIONS)
75+
def test_cubeviz_collapse_fluxunits(
76+
cubeviz_helper,
77+
spectrum1d_cube_custom_fluxunit,
78+
function, spectra_fluxunit):
79+
"""
80+
Calculates line flux and checks the units for each collapse function.
81+
"""
82+
7883
data = spectrum1d_cube_custom_fluxunit(spectra_fluxunit)
79-
for function in COLLAPSE_FUNCTIONS:
80-
# Initialize Cubeviz with specific data and collapse function
81-
cubeviz_helper = Cubeviz()
82-
data_label = "Test Cube"
83-
cubeviz_helper.load_data(data, data_label=data_label)
84-
cubeviz_helper.app.get_viewer('spectrum-viewer').state.function = function
85-
86-
lineflux_result = _calculate_line_flux(cubeviz_helper)
87-
autocollapsed_spectrum_unit = (cubeviz_helper.
88-
specviz.get_spectra()["Spectrum (sum)"].flux.unit)
89-
# Futureproofing: Eventually Cubeviz autocollapse will change the flux units of the
90-
# spectra depending on whether the spectrum was collapsed OVER SPAXELS or not. Only
91-
# do the assertion check if we KNOW what the expected lineflux results should be
92-
if autocollapsed_spectrum_unit in expected_lineflux_results.keys():
93-
assert u.Unit(lineflux_result['unit']) == expected_lineflux_results[spectra_fluxunit]
84+
85+
# Initialize Cubeviz with specific data and collapse function
86+
data_label = "Test Cube"
87+
cubeviz_helper.load_data(data, data_label=data_label)
88+
cubeviz_helper.app.get_viewer('spectrum-viewer').state.function = function
89+
90+
lineflux_result = _calculate_line_flux(cubeviz_helper)
91+
autocollapsed_spectrum_unit = (cubeviz_helper.
92+
specviz.get_spectra()["Spectrum (sum)"].flux.unit)
93+
# Futureproofing: Eventually Cubeviz autocollapse will change the flux units of the
94+
# spectra depending on whether the spectrum was collapsed OVER SPAXELS or not. Only
95+
# do the assertion check if we KNOW what the expected lineflux results should be
96+
if autocollapsed_spectrum_unit in expected_lineflux_results.keys():
97+
assert u.Unit(lineflux_result['unit']) == expected_lineflux_results[spectra_fluxunit]
9498

9599

96100
@pytest.mark.filterwarnings(r"ignore:.* contains multiple slashes")

jdaviz/core/tests/test_autoconfig.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@ def test_launcher(tmp_path):
6060

6161
# Test with real files
6262
for uri, config in AUTOCONFIG_EXAMPLES:
63+
uri = cached_uri(uri)
6364
if uri.startswith("mast:"):
6465
download_path = str(tmp_path / Path(uri).name)
6566
Observations.download_file(uri, local_path=download_path)
6667
elif uri.startswith("http"):
6768
download_path = download_file(uri, cache=True, timeout=100)
69+
else:
70+
# cached local file
71+
download_path = uri
6872
launcher.filepath = download_path
6973
# In testing, setting the filepath will stall until identifying is complete
7074
# No need to be concerned for race condition here

tox.ini

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ deps =
6262
devdeps: git+https://github.com/glue-viz/glue-astronomy.git
6363
devdeps: git+https://github.com/widgetti/solara.git
6464
devdeps: git+https://github.com/astropy/specreduce.git
65+
pytest-xdist
6566

6667
# The following indicates which extras_require from pyproject.toml will be installed
6768
extras =
@@ -73,8 +74,8 @@ extras =
7374
commands =
7475
jupyter --paths
7576
pip freeze
76-
!cov: pytest --pyargs jdaviz {toxinidir}/docs --ignore=jdaviz/qt.py {posargs}
77-
cov: pytest --pyargs jdaviz {toxinidir}/docs --cov jdaviz --cov-config={toxinidir}/pyproject.toml --ignore=jdaviz/qt.py {posargs}
77+
!cov: pytest -n auto --dist loadscope --pyargs jdaviz {toxinidir}/docs --ignore=jdaviz/qt.py {posargs} --durations=50
78+
cov: pytest -n auto --dist loadscope --pyargs jdaviz {toxinidir}/docs --cov jdaviz --cov-config={toxinidir}/pyproject.toml --ignore=jdaviz/qt.py {posargs} --durations=50
7879
cov: coverage xml -o {toxinidir}/coverage.xml
7980

8081
pip_pre =

0 commit comments

Comments
 (0)