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