From ce8bbac5f1f7689c826ae6126d2c6ffdde16068f Mon Sep 17 00:00:00 2001
From: Kohei Yamamoto <kohei.yamamoto@aei.mpg.de>
Date: Mon, 4 Jul 2022 14:31:10 +0200
Subject: [PATCH] write() and _write_attr()

---
 lisainstrument/hexagon.py | 71 ++++++++++++++++++++++++++++++++-------
 1 file changed, 59 insertions(+), 12 deletions(-)

diff --git a/lisainstrument/hexagon.py b/lisainstrument/hexagon.py
index 4c8c03a..35a3a99 100644
--- a/lisainstrument/hexagon.py
+++ b/lisainstrument/hexagon.py
@@ -16,6 +16,8 @@ Authors:
 
 import numpy as np
 
+from h5py import File
+
 from . import noises
 
 
@@ -29,8 +31,8 @@ class Hexagon():
         locked_laser_asd (float): ASD added to locked lasers 2 & 3 [Hz/sqrt(Hz)]
     """
 
-    INDICES = [1, 2, 3]
-    BEATNOTES = [12, 23, 31]
+    INDICES = ['1', '2', '3']
+    BEATNOTES = ['12', '23', '31']
 
     def __init__(self,
                  size=100, dt=1 / (80e6/2**17/10/6/3),
@@ -39,6 +41,7 @@ class Hexagon():
                  offset_freqs='default',
                  central_freq=2.816E14):
 
+        self.simulated = False
         self.size = int(size)
         self.dt = float(dt)
         self.fs = 1.0 / self.dt
@@ -50,16 +53,16 @@ class Hexagon():
 
         self.central_freq = float(central_freq)
         if offset_freqs == 'default':
-            self.offset_freqs = {1: 0.0, 2: 15E6, 3: 7E6}
+            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
+        self.carrier_fluctuations = None
         # initialize beatnote-related attributes
-        self.carrier_beatnote = None
+        self.carrier_beatnotes = None
         self.carrier_beatnote_offsets = None
         self.carrier_beatnote_fluctuations = None
         # initialize signal-combination attributes
@@ -71,10 +74,11 @@ class Hexagon():
 
         Args:
         """
+        self.simulated = True
 
         self.laser_noises = np.empty((self.size, 3)) # (size, laser) [Hz]
 
-        # Laser 1 is free-running
+        # Laser 1 has its own stability
         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] \
@@ -84,7 +88,7 @@ class Hexagon():
             + noises.white(self.fs, self.size, self.locked_laser_asd)
 
         # Carrier beams
-        self.carrier_flucuations = self.laser_noises # (size, laser) [Hz]
+        self.carrier_fluctuations = self.laser_noises # (size, laser) [Hz]
         self.carrier_offsets = np.array(
             [[self.offset_freqs[index] for index in self.INDICES]]
         ) # (size, laser) [Hz]
@@ -92,15 +96,58 @@ class Hexagon():
         # 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]
+            self.carrier_offsets[:, int(ij[1]) - 1] - self.carrier_offsets[:, int(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]
+            self.carrier_fluctuations[:, int(ij[1]) - 1] - self.carrier_fluctuations[:, int(ij[0]) - 1]
             for ij in self.BEATNOTES
         ], axis=-1) # (size, beatnote) [Hz]
-        self.carrier_beatnote = self.carrier_beatnote_offsets + self.carrier_beatnote_fluctuations
+        self.carrier_beatnotes = 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
+        self.three_signal_combination = self.carrier_beatnotes[:,0] \
+            + self.carrier_beatnotes[:,1] + self.carrier_beatnotes[:,2]
+
+
+    def write(self, output='measurements.h5', mode='w'):
+        """Write simulation results.
+
+        Args:
+            output: path to measurement file
+            mode: measurement file opening mode
+        """
+
+        with File(output, mode) as hdf5:
+
+            if not self.simulated:
+                self.simulate()
+
+            self._write_attr(hdf5, 'laser_noises', indices=self.INDICES)
+            self._write_attr(hdf5, 'carrier_offsets', indices=self.INDICES)
+            self._write_attr(hdf5, 'carrier_fluctuations', indices=self.INDICES)
+            self._write_attr(hdf5, 'carrier_beatnotes', indices=self.BEATNOTES)
+            self._write_attr(hdf5, 'carrier_beatnote_offsets', indices=self.BEATNOTES)
+            self._write_attr(hdf5, 'carrier_beatnote_fluctuations', indices=self.BEATNOTES)
+            self._write_attr(hdf5, 'three_signal_combination')
+
+            
+    def _write_attr(self, hdf5, dataset, indices=None):
+        """Write a single object attribute on ``hdf5``.
+
+        Args:
+            hdf5 (:obj:`h5py.Group`): an HDF5 file, or a dataset
+            indices ([str]): index list
+        """
+
+        # Get a target attribute
+        attr = getattr(self, dataset)
+        # Write dataset
+        if indices is not None:
+            size = attr.shape[0]
+            dtype = np.dtype({'names': indices, 'formats': len(indices) * [np.float64]})
+            hdf5.create_dataset(dataset, (size,), dtype=dtype)
+            for i, index in enumerate(indices):
+                hdf5[dataset][index] = attr[:, i]
+        else:
+            hdf5.create_dataset(dataset, data=attr, dtype=np.float64)
-- 
GitLab