From 519024feb8af950f6874d87b981609f9826b9b55 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Bayle <j2b.bayle@gmail.com>
Date: Thu, 16 Jun 2022 09:53:44 +0200
Subject: [PATCH] First attempt to simulate hexagon

---
 lisainstrument/__init__.py |   1 +
 lisainstrument/hexagon.py  | 106 +++++++++++++++++++++++++++++++++++++
 tests/test_hexagon.py      |  12 +++++
 3 files changed, 119 insertions(+)
 create mode 100644 lisainstrument/hexagon.py
 create mode 100755 tests/test_hexagon.py

diff --git a/lisainstrument/__init__.py b/lisainstrument/__init__.py
index 1781ab0..85367f1 100644
--- a/lisainstrument/__init__.py
+++ b/lisainstrument/__init__.py
@@ -7,3 +7,4 @@ from .meta import __author__
 from .meta import __email__
 
 from .instrument import Instrument
+from .hexagon import Hexagon
diff --git a/lisainstrument/hexagon.py b/lisainstrument/hexagon.py
new file mode 100644
index 0000000..4c8c03a
--- /dev/null
+++ b/lisainstrument/hexagon.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Simulation of the Hexagon experiment [1].
+
+Reference:
+
+    [1] Yamamoto, K., Vorndamme, C., Hartwig, O., Staab, M., Schwarze, T. S., &#38; Heinzel, G. (2021).
+    Experimental verification of intersatellite clock synchronization at LISA performance levels.
+    https://doi.org/10.1103/PhysRevD.105.042009
+
+Authors:
+    ...
+    Jean-Baptiste Bayle <j2b.bayle@gmail.com>
+"""
+
+import numpy as np
+
+from . import noises
+
+
+class Hexagon():
+    """Represent the Hexagon instrument.
+
+    Args:
+        size (int): numer of samples in the simulation
+        dt (float): sampling period [s]
+        primary_laser_asd (float): ASD of primary laser 1 [Hz/sqrt(Hz)]
+        locked_laser_asd (float): ASD added to locked lasers 2 & 3 [Hz/sqrt(Hz)]
+    """
+
+    INDICES = [1, 2, 3]
+    BEATNOTES = [12, 23, 31]
+
+    def __init__(self,
+                 size=100, dt=1 / (80e6/2**17/10/6/3),
+                 primary_laser_asd=100,
+                 locked_laser_asd=60,
+                 offset_freqs='default',
+                 central_freq=2.816E14):
+
+        self.size = int(size)
+        self.dt = float(dt)
+        self.fs = 1.0 / self.dt
+        self.duration = self.size * self.dt
+        self.time = np.linspace(0, self.duration-self.dt, self.size)
+
+        self.primary_laser_asd = primary_laser_asd
+        self.locked_laser_asd = locked_laser_asd
+
+        self.central_freq = float(central_freq)
+        if offset_freqs == 'default':
+            self.offset_freqs = {1: 0.0, 2: 15E6, 3: 7E6}
+        else:
+            self.offset_freqs = offset_freqs
+
+        # initialize single-laser-related attributes
+        self.laser_noises = None
+        self.carrier_flucuations = None
+        self.carrier_offsets = None
+        # initialize beatnote-related attributes
+        self.carrier_beatnote = None
+        self.carrier_beatnote_offsets = None
+        self.carrier_beatnote_fluctuations = None
+        # initialize signal-combination attributes
+        self.three_signal_combination = None
+
+
+    def simulate(self):
+        """Run a simulation, and generate all intermediary signals.
+
+        Args:
+        """
+
+        self.laser_noises = np.empty((self.size, 3)) # (size, laser) [Hz]
+
+        # Laser 1 is free-running
+        self.laser_noises[:, 0] = noises.laser(self.fs, self.size, self.primary_laser_asd)
+        # Laser 2 replicated laser 1 with added locking noise
+        self.laser_noises[:, 1] = self.laser_noises[:, 0] \
+            + noises.white(self.fs, self.size, self.locked_laser_asd)
+        # Laser 3 replicated laser 1 with added locking noise
+        self.laser_noises[:, 2] = self.laser_noises[:, 0] \
+            + noises.white(self.fs, self.size, self.locked_laser_asd)
+
+        # Carrier beams
+        self.carrier_flucuations = self.laser_noises # (size, laser) [Hz]
+        self.carrier_offsets = np.array(
+            [[self.offset_freqs[index] for index in self.INDICES]]
+        ) # (size, laser) [Hz]
+
+        # Compute beatnotes
+        # Convention is from paper: beatnote ij is beam j - beam i
+        self.carrier_beatnote_offsets = np.stack([
+            self.carrier_offsets[:, int(str(ij)[1]) - 1] - self.carrier_offsets[:, int(str(ij)[0]) - 1]
+            for ij in self.BEATNOTES
+        ], axis=-1) # (size, beatnote) [Hz]
+        self.carrier_beatnote_fluctuations = np.stack([
+            self.carrier_flucuations[:, int(str(ij)[1]) - 1] - self.carrier_flucuations[:, int(str(ij)[0]) - 1]
+            for ij in self.BEATNOTES
+        ], axis=-1) # (size, beatnote) [Hz]
+        self.carrier_beatnote = self.carrier_beatnote_offsets + self.carrier_beatnote_fluctuations
+
+        # Three-signal combination
+        self.three_signal_combination = self.carrier_beatnote[:,0] \
+            + self.carrier_beatnote[:,1] + self.carrier_beatnote[:,2]
\ No newline at end of file
diff --git a/tests/test_hexagon.py b/tests/test_hexagon.py
new file mode 100755
index 0000000..e38ce3e
--- /dev/null
+++ b/tests/test_hexagon.py
@@ -0,0 +1,12 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*-
+# pylint: disable=missing-module-docstring
+
+import pytest
+from lisainstrument import Hexagon
+
+
+def test_run():
+    """Test that simulations can run."""
+    hex = Hexagon()
+    hex.simulate()
-- 
GitLab