From a6a2f85bffc23bc31442d16876c956075b9b22a8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bayle <j2b.bayle@gmail.com> Date: Sun, 3 Apr 2022 21:57:53 +0200 Subject: [PATCH] Refactor unit tests --- tests/test_fplan.py | 235 +++++++++++++++++++++++++++++++++++++++ tests/test_instrument.py | 67 ----------- 2 files changed, 235 insertions(+), 67 deletions(-) create mode 100755 tests/test_fplan.py diff --git a/tests/test_fplan.py b/tests/test_fplan.py new file mode 100755 index 0000000..f9a65ae --- /dev/null +++ b/tests/test_fplan.py @@ -0,0 +1,235 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Test the usage of frequency plans.""" + +import numpy as np +import pytest + +from h5py import File +from scipy.interpolate import interp1d +from lisainstrument import Instrument + + +def _valid_locking_beatnotes(instru, fplan=None): + """Check that TPS locking beatnotes are consistent with ``fplan``. + + We check that locking beatnotes are equal up to machine precision. + + Args: + instru (:class:`lisainstrument.Instrument`): instrument object + fplan (dict or None): dictionary of locking beatnotes [Hz] or None to read fplan file + + Returns: + (bool) Whether the locking beatnotes are valid. + """ + # Read fplan file + if fplan is None: + with File(instru.fplan_file, 'r') as fplanf: + t = np.arange(fplanf.attrs['size']) * fplanf.attrs['dt'] + if instru.lock_config == 'N1-12': + fplan = { + '13': interp1d(t, -fplanf[instru.lock_config]['rfi_12'][:] * 1E6)(instru.physics_t), + '31': interp1d(t, fplanf[instru.lock_config]['isi_31'][:] * 1E6)(instru.physics_t), + '32': interp1d(t, -fplanf[instru.lock_config]['rfi_31'][:] * 1E6)(instru.physics_t), + '21': interp1d(t, fplanf[instru.lock_config]['isi_21'][:] * 1E6)(instru.physics_t), + '23': interp1d(t, fplanf[instru.lock_config]['rfi_23'][:] * 1E6)(instru.physics_t), + } + elif instru.lock_config == 'N1-21': + fplan = { + '23': interp1d(t, fplanf[instru.lock_config]['rfi_23'][:] * 1E6)(instru.physics_t), + '32': interp1d(t, fplanf[instru.lock_config]['isi_32'][:] * 1E6)(instru.physics_t), + '31': interp1d(t, fplanf[instru.lock_config]['rfi_31'][:] * 1E6)(instru.physics_t), + '12': interp1d(t, fplanf[instru.lock_config]['isi_12'][:] * 1E6)(instru.physics_t), + '13': interp1d(t, -fplanf[instru.lock_config]['rfi_12'][:] * 1E6)(instru.physics_t), + } + elif instru.lock_config == 'N4-12': + fplan = { + '13': interp1d(t, -fplanf[instru.lock_config]['rfi_12'][:] * 1E6)(instru.physics_t), + '31': interp1d(t, fplanf[instru.lock_config]['isi_31'][:] * 1E6)(instru.physics_t), + '32': interp1d(t, -fplanf[instru.lock_config]['rfi_31'][:] * 1E6)(instru.physics_t), + '23': interp1d(t, fplanf[instru.lock_config]['isi_23'][:] * 1E6)(instru.physics_t), + '21': interp1d(t, fplanf[instru.lock_config]['isi_21'][:] * 1E6)(instru.physics_t), + } + # Check locking beatnotes + if instru.lock_config == 'N1-12': + return np.all([ + np.allclose(instru.tps_rfi_carrier_offsets['13'], fplan['13']), + np.allclose(instru.tps_isi_carrier_offsets['31'], fplan['31']), + np.allclose(instru.tps_rfi_carrier_offsets['32'], fplan['32']), + np.allclose(instru.tps_isi_carrier_offsets['21'], fplan['21']), + np.allclose(instru.tps_rfi_carrier_offsets['23'], fplan['23']), + ]) + elif instru.lock_config == 'N1-21': + return np.all([ + np.allclose(instru.tps_rfi_carrier_offsets['23'], fplan['23']), + np.allclose(instru.tps_isi_carrier_offsets['32'], fplan['32']), + np.allclose(instru.tps_rfi_carrier_offsets['31'], fplan['31']), + np.allclose(instru.tps_isi_carrier_offsets['12'], fplan['12']), + np.allclose(instru.tps_rfi_carrier_offsets['13'], fplan['13']), + ]) + elif instru.lock_config == 'N4-12': + return np.all([ + np.allclose(instru.tps_rfi_carrier_offsets['13'], fplan['13']), + np.allclose(instru.tps_isi_carrier_offsets['31'], fplan['31']), + np.allclose(instru.tps_rfi_carrier_offsets['32'], fplan['32']), + np.allclose(instru.tps_isi_carrier_offsets['23'], fplan['23']), + np.allclose(instru.tps_isi_carrier_offsets['21'], fplan['21']), + ]) + else: + raise ValueError(f"unsupported lock configuration '{instru.lock_config}'") + +def test_static_fplan(): + """Test the default static set of locking beatnotes.""" + + # Check fplan initialization + instru = Instrument(size=100, fplan='static') + static = { + '12': 8.1E6, '23': 9.2E6, '31': 10.3E6, + '13': 1.4E6, '32': -11.6E6, '21': -9.5E6, + } + for mosa in instru.MOSAS: + assert instru.fplan[mosa] == static[mosa] + + # Check locking beatnotes + instru = Instrument(size=100, lock='N1-12', fplan='static') + instru.simulate() + assert _valid_locking_beatnotes(instru, static) + instru = Instrument(size=100, lock='N1-21', fplan='static') + instru.simulate() + assert _valid_locking_beatnotes(instru, static) + instru = Instrument(size=100, lock='N4-12', fplan='static') + instru.simulate() + assert _valid_locking_beatnotes(instru, static) + +def test_constant_equal_fplan(): + """Test a user-defined constant equal fplan.""" + + # Check fplan initialization + instru = Instrument(size=100, fplan=42.0) + for mosa in instru.MOSAS: + assert instru.fplan[mosa] == 42.0 + +def test_constant_unequal_fplan(): + """Test a user-defined constant unequal fplan.""" + + # Check fplan initialization + fplan = { + '12': 8.1E6, '23': 9.2E6, '31': 10.3E6, + '13': 1.4E6, '32': -11.6E6, '21': -9.5E6, + } + instru = Instrument(size=100, lock='N1-12', fplan=fplan) + for mosa in instru.MOSAS: + assert instru.fplan[mosa] == fplan[mosa] + + # Check locking beatnotes + instru = Instrument(size=100, lock='N1-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N1-21', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N4-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + +def test_varying_equal_fplan(): + """Test a user-defined time-varying equal fplan.""" + + # Check fplan initialization + fplan = np.random.uniform(5E6, 25E6, size=400) + instru = Instrument(size=100, fplan=fplan) + for mosa in instru.MOSAS: + assert np.all(instru.fplan[mosa] == fplan) + + # Check locking beatnotes + fplan = {mosa: fplan for mosa in Instrument.MOSAS} + instru = Instrument(size=100, lock='N1-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N1-21', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N4-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + +def test_varying_unequal_fplan(): + """Test a user-defined time-varying unequal fplan.""" + + # Check fplan initialization + fplan = { + mosa: np.random.uniform(5E6, 25E6, size=400) + for mosa in Instrument.MOSAS + } + instru = Instrument(size=100, fplan=fplan) + for mosa in instru.MOSAS: + assert np.all(instru.fplan[mosa] == fplan[mosa]) + + # Check locking beatnotes + instru = Instrument(size=100, lock='N1-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N1-21', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + instru = Instrument(size=100, lock='N4-12', fplan=fplan) + instru.simulate() + assert _valid_locking_beatnotes(instru, fplan) + +def test_keplerian_fplan_1_1(): + """Test standard Keplerian fplan file v1.1.""" + + # Check fplan file with six lasers locked on cavity + instru = Instrument(size=100, lock='six', fplan='tests/keplerian-fplan-1-1.h5') + for mosa in instru.MOSAS: + assert instru.fplan[mosa] == 0.0 + + # Check fplan file with standard lock configs + for primary in Instrument.MOSAS: + for topology in Instrument.LOCK_TOPOLOGIES: + instru = Instrument(size=100, lock=f'{topology}-{primary}', fplan='tests/keplerian-fplan-1-1.h5') + + # Should raise an error for non-standard lock config + with pytest.raises(ValueError): + lock = {'12': 'cavity', '13': 'cavity', '21': 'distant', '31': 'distant', '23': 'adjacent', '32': 'adjacent'} + Instrument(size=100, lock=lock, fplan='tests/keplerian-fplan-1-1.h5') + + # Check locking beatnotes + instru = Instrument(size=100, lock='N1-12', fplan='tests/keplerian-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) + instru = Instrument(size=100, lock='N1-21', fplan='tests/keplerian-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) + instru = Instrument(size=100, lock='N4-12', fplan='tests/keplerian-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) + +def test_esa_trailing_fplan_1_1(): + """Test standard ESA trailing fplan file v1.1.""" + + # Check fplan file with six lasers locked on cavity + instru = Instrument(size=100, lock='six', fplan='tests/esa-trailing-fplan-1-1.h5') + for mosa in instru.MOSAS: + assert instru.fplan[mosa] == 0.0 + + # Check fplan file with standard lock configs + for primary in Instrument.MOSAS: + for topology in Instrument.LOCK_TOPOLOGIES: + instru = Instrument(size=100, lock=f'{topology}-{primary}', fplan='tests/esa-trailing-fplan-1-1.h5') + + # Should raise an error for non-standard lock config + with pytest.raises(ValueError): + lock = {'12': 'cavity', '13': 'cavity', '21': 'distant', '31': 'distant', '23': 'adjacent', '32': 'adjacent'} + Instrument(size=100, lock=lock, fplan='tests/esa-trailing-fplan-1-1.h5') + + # Check locking beatnotes + instru = Instrument(size=100, lock='N1-12', fplan='tests/esa-trailing-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) + instru = Instrument(size=100, lock='N1-21', fplan='tests/esa-trailing-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) + instru = Instrument(size=100, lock='N4-12', fplan='tests/esa-trailing-fplan-1-1.h5') + instru.simulate() + assert _valid_locking_beatnotes(instru) diff --git a/tests/test_instrument.py b/tests/test_instrument.py index ece342a..721218e 100755 --- a/tests/test_instrument.py +++ b/tests/test_instrument.py @@ -49,73 +49,6 @@ def test_esa_trailing_orbits_2_0_dev(): instru = Instrument(size=100, orbits='tests/esa-trailing-orbits-2-0-dev.h5') instru.simulate() -def test_custom_fplan(): - """Test that we can pass a custom frequency plan.""" - # Constant equal fplan - instru = Instrument(size=100, fplan=42.0) - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == 42.0 - instru.simulate() - # Constant unequal fplan - fplan = { - '12': 8.1E6, '23': 9.2E6, '31': 10.3E6, - '13': 1.4E6, '32': -11.6E6, '21': -9.5E6, - } - instru = Instrument(size=100, fplan=fplan) - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == fplan[mosa] - instru.simulate() - # Time-varying equal fplan - fplan = np.random.normal(size=(instru.physics_size)) - instru = Instrument(size=100, fplan=fplan) - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == pytest.approx(fplan) - instru.simulate() - # Time-varying unequal fplan - fplan = {mosa: np.random.normal(size=(instru.physics_size)) for mosa in instru.MOSAS} - instru = Instrument(size=100, fplan=fplan) - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == pytest.approx(fplan[mosa]) - instru.simulate() - -def test_keplerian_fplan_1_1(): - """Test that simulations can run with Keplerian fplan v1.1.""" - # Check with six lasers locked on cavity - instru = Instrument(size=100, lock='six', fplan='tests/keplerian-fplan-1-1.h5') - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == 0.0 - instru.simulate() - # Check standard lock configs - for topology in Instrument.LOCK_TOPOLOGIES: - for primary in Instrument.MOSAS: - instru = Instrument( - size=100, lock=f'{topology}-{primary}', fplan='tests/keplerian-fplan-1-1.h5') - assert instru.fplan[primary] == 0.0 - instru.simulate() - # Should raise an error for non-standard lock config - with pytest.raises(ValueError): - lock = {'12': 'cavity', '13': 'cavity', '21': 'distant', '31': 'distant', '23': 'adjacent', '32': 'adjacent'} - Instrument(size=100, lock=lock, fplan='tests/keplerian-fplan-1-1.h5') - -def test_esa_trailing_fplan_1_1(): - """Test that simulations can run with ESA trailing fplan v1.1.""" - # Check with six lasers locked on cavity - instru = Instrument(size=100, lock='six', fplan='tests/esa-trailing-fplan-1-1.h5') - for mosa in instru.MOSAS: - assert instru.fplan[mosa] == 0.0 - instru.simulate() - # Check standard lock configs - for topology in Instrument.LOCK_TOPOLOGIES: - for primary in Instrument.MOSAS: - instru = Instrument( - size=100, lock=f'{topology}-{primary}', fplan='tests/esa-trailing-fplan-1-1.h5') - assert instru.fplan[primary] == 0.0 - instru.simulate() - # Should raise an error for non-standard lock config - with pytest.raises(ValueError): - lock = {'12': 'cavity', '13': 'cavity', '21': 'distant', '31': 'distant', '23': 'adjacent', '32': 'adjacent'} - Instrument(size=100, lock=lock, fplan='tests/esa-trailing-fplan-1-1.h5') - def test_locking(): """Test that simulations can run with various lock configurations.""" # Test six free-running lasers -- GitLab