Metadata-Version: 2.4
Name: deepfmkit
Version: 1.0.4
Summary: A Python toolkit for Deep Frequency Modulation Interferometry.
Author-email: Miguel Dovale <spectools@pm.me>
License: BSD 3-Clause License
        
        Copyright (c) 2025, Miguel Dovale
        
        Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions are met:
        
        1. Redistributions of source code must retain the above copyright notice, this
           list of conditions and the following disclaimer.
        
        2. Redistributions in binary form must reproduce the above copyright notice,
           this list of conditions and the following disclaimer in the documentation
           and/or other materials provided with the distribution.
        
        3. Neither the name of the copyright holder nor the names of its
           contributors may be used to endorse or promote products derived from
           this software without specific prior written permission.
        
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
        DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
        FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
        SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
        OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
        OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
        
        This software may be subject to U.S. export control laws. By accepting this
        software, the user agrees to comply with all applicable U.S. export laws and
        regulations. User has the responsibility to obtain export licenses, or other
        export authority as may be required before exporting such information to
        foreign countries or providing access to foreign persons.
Project-URL: Homepage, https://github.com/mdovale/DeepFMKit
Project-URL: Repository, https://github.com/mdovale/DeepFMKit
Project-URL: Issues, https://github.com/mdovale/DeepFMKit/issues
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: AUTHORS
Requires-Dist: numpy
Requires-Dist: scipy
Requires-Dist: pandas
Requires-Dist: matplotlib
Requires-Dist: numba
Requires-Dist: tqdm
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# DeepFMKit: A Toolkit for Deep Frequency Modulation Interferometry

**DeepFMKit** is a high-performance Python framework for the end-to-end simulation, processing, and analysis of data from Deep Frequency Modulation Interferometry (DFMI) experiments. It provides a complete computational laboratory for researchers and engineers to design virtual experiments, study systematic errors, and develop and benchmark novel data analysis techniques.

## About The Project

Deep Frequency Modulation Interferometry (DFMI) is a laser-based metrology technique used for high-precision displacement sensing and absolute distance measurement. This toolkit is designed to handle the entire experimental workflow, from simulating complex interferometric signals with realistic noise sources to performing robust, high-speed parameter estimation.

The novelty of DeepFMKit lies in its integrated, object-oriented approach. It combines a detailed physics engine—capable of modeling time-of-flight delays, colored noise sources, and arbitrary modulation waveforms—with a modular set of interchangeable fitting algorithms. This allows users to not only simulate realistic data but also to benchmark the performance of different readout schemes on identical, well-defined datasets.

### Key Features

*   **High-Fidelity Physics Engine:** Simulate realistic DFMI signals with a model that includes prescribed noise sources, dynamic path length changes, and arbitrary laser modulation waveforms.
*   **Comprehensive Noise Modeling:** Inject realistic, colored noise based on user-defined Amplitude Spectral Densities (ASDs) for laser frequency, laser amplitude, modulation amplitude, and arm length fluctuations.
*   **Modular & Interchangeable Fitters:**
    *   **NLS Fitter:** A highly optimized, parallelized Non-Linear Least Squares (NLS) fitter working in the frequency domain.
    *   **EKF Fitter:** A time-domain Extended Kalman Filter (EKF) for real-time state tracking and analysis of dynamic systems.
*   **High-Throughput Experimentation:** A declarative framework for defining and running large-scale, parallelized parameter sweeps and Monte Carlo simulations to systematically characterize system performance.
*   **Data Handling & Visualization:** A suite of tools for loading/saving data, managing experimental configurations, and plotting results.

## Getting Started

### Prerequisites

*   Python 3.9+
*   Standard scientific libraries: NumPy, SciPy, Pandas, Matplotlib
*   For high-performance noise generation: Numba

### Installation

You can either install directly from GitHub or clone the repository locally.  

**Option 1 – Install directly from GitHub (latest version):**  
```sh
pip install git+https://github.com/mdovale/DeepFMKit.git
```

**Option 2 – Clone and install manually:**  
1. **Clone this repository**
   ```sh
   git clone https://github.com/mdovale/DeepFMKit.git
   cd DeepFMKit
   ```

2. **(Recommended) Create and activate a virtual environment**
   ```sh
   python -m venv .venv
   source .venv/bin/activate     # On Windows: .venv\Scripts\activate
   ```

3. **Install dependencies**
   ```sh
   pip install --upgrade pip
   pip install -r requirements.txt
   ```


## Quick Start: A Complete Workflow

The following example demonstrates the primary workflow: defining a physical system, simulating data, running a fit, and plotting the results.

```python
import deepfmkit.core as dfm
import matplotlib.pyplot as plt
# --- 1. Define the interferometer ---
ifo = dfm.IfoConfig()
ifo.ref_arml = 0.10 # Reference armlength (m)
ifo.meas_arml = 0.15 # Measurement armlength (m)
# --- 2. Define the laser source ---
las = dfm.LaserConfig()
las.fm = 1e3 # FM frequency (Hz)
las.set_df_for_m(ifo, 6.) # Target m (rad)
# --- 3. Compose the main channel ---
sim = dfm.SimConfig("ch0",
    laser_config=las,
    ifo_config=ifo,
    f_samp=200e3) # Acquisition frequency (Hz) 
# --- 4. Instantiate DeepFrame ---
df = dfm.DeepFrame(sim_config=sim)
# --- 5. Simulate ---
df.simulate("ch0",
    n_seconds=10) # Simulation length (s)
# --- 6. Run readout algorithm ---
df.fit("ch0")
# --- 7. Plot readout results ---
axes = df.plot(which=['phi', 'm', 'ssq'])
plt.show()
```

## Advanced Usage

### High-Fidelity Simulation with Colored Noise

The 'asd' simulation mode enables realistic noise modeling. Configure the noise properties of the LaserConfig and IfoConfig objects before simulating.

```python
from deepfmkit.physics import LaserConfig, IfoConfig, SimConfig
# Assume 'dff' and 'label' exist from the Quick Start example
# and laser/ifo variables are still in scope.

# Configure laser noise (ASD at 1 Hz, and 1/f^alpha exponent)
laser.f_n = 100.0      # 100 Hz/sqrt(Hz) of laser frequency noise
laser.f_n_alpha = 1.0  # Pink (1/f) frequency noise

# Configure interferometer noise
ifo.arml_n = 1e-12     # 1 pm/sqrt(Hz) of arm length noise
ifo.arml_n_alpha = 2.0 # Red (1/f^2) arm length noise

# Simulate using the 'asd' mode
dff.simulate(label=label, n_seconds=5, mode='asd', trial_num=42) # Use trial_num for reproducibility
```

### Using the Extended Kalman Filter (EKF)

To track a dynamic system, configure a time-varying signal and use the 'ekf' fitter.

```python
from deepfmkit.physics import LaserConfig, IfoConfig, SimConfig
# Assume 'dff', 'laser', 'ifo' exist from previous examples.

# Add a 5 Hz sinusoidal motion to the measurement arm
ifo.arml_mod_f = 5.0
ifo.arml_mod_amp = 10e-9 # 10 nm amplitude

# Re-create the SimConfig and simulate with dynamic motion
sim_dynamic = SimConfig("dynamic_channel", laser, ifo, f_samp=200e3)
dff.add_sim(sim_dynamic)
dff.simulate(label="dynamic_channel", n_seconds=2, mode='asd')

# Fit using the EKF
dff.fit(main_label="dynamic_channel", method='ekf', fit_label="ekf_fit")

# Plot the EKF's phase estimate and compare to the ground truth
ax = dff.plot(labels=['ekf_fit'], which=['phi'])
raw_data = dff.raws["dynamic_channel"]
fit_data = dff.fits["ekf_fit"]
ax.plot(fit_data.time, raw_data.phi_sim_downsamp, 'r--', label='Ground Truth')
ax.legend()
plt.show()
```

### High-Throughput Experiments

The Experiment framework (experiments.py) is designed for large-scale studies. It automates the process of running thousands of "simulate-and-analyze" trials in parallel.

1. Create an ExperimentFactory in factories.py: This class contains your logic for setting up a single trial. For example:

```python
class VairableAmplitudeOffset(ExperimentFactory):
    def __init__(self, opd_main: float = 0.1):
        self.opd_main = opd_main
        if self.opd_main == 0:
            raise ValueError("opd_main cannot be zero in the factory.")

    def _get_expected_params_keys(self) -> Set[str]:
        """Declares the top-level parameters consumed by this factory's __call__ method.
        """
        return {'m_main', 'nominal_amplitude', 'amplitude_offset'}

    def __call__(self, params: dict) -> dict:
        """Generates the physics configurations for one standard DFMI trial.
        """
        m_main = params['m_main']
        nominal_amplitude = params['nominal_amplitude']
        amplitude_offset = params['amplitude_offset']
        laser_config = physics.LaserConfig(label="ExperimentLaser")
        laser_config.amp = nominal_amplitude + amplitude_offset 
        laser_config.df = (m_main * sc.c) / (2 * np.pi * self.opd_main)
        main_ifo_config = physics.IfoConfig(label="main_ifo")
        main_ifo_config.ref_arml = 0.1
        main_ifo_config.meas_arml = main_ifo_config.ref_arml + self.opd_main
        return {
            'laser_config': laser_config,
            'main_ifo_config': main_ifo_config
        }
```

2. Define and run the Experiment:

```python
from deepfmkit.experiments import Experiment
from deepfmkit.factories import VairableAmplitudeOffset

import numpy as np
from functools import partial

def amplitude_random_offset_generator(nominal_amplitude: float, relative_noise_std: float) -> float:
    """
    Generates a random amplitude offset whose standard deviation scales
    with the `nominal_amplitude`. This simulates proportional noise.

    Parameters
    ----------
    nominal_amplitude : float
        The nominal signal amplitude, which dictates the scale of the random offset.

    Returns
    -------
    float
        A single random offset value.
    """
    noise_std = nominal_amplitude * relative_noise_std
    return np.random.normal(loc=0.0, scale=noise_std)

my_factory_instance = VairableAmplitudeOffset(
    opd_main=0.1
)

# Create an Experiment object.
experiment = Experiment(description="Example using VairableAmplitudeOffset factory")
experiment.set_config_factory(my_factory_instance)
experiment.n_cycles = 200
experiment.f_samp = 200e3

# Define sweep axis: the nominal signal amplitude.
nominal_amplitudes = np.linspace(0.5, 2.0, 10)
experiment.add_axis('nominal_amplitude', nominal_amplitudes)

# Define static parameters for all trials.
experiment.set_static({
    'm_main': 5.0,                  # Keep modulation depth constant
})

# Add stochastic variable: a random offset to the signal amplitude.
# It 'depends_on' the 'nominal_amplitude' axis.
gen_fun = partial(amplitude_random_offset_generator, relative_noise_std=0.05)

experiment.add_stochastic_variable(
    name='amplitude_offset',
    generator_func=gen_fun,
    depends_on='nominal_amplitude' # This links the noise scale to the nominal amplitude
)

# Specify the number of Monte Carlo trials for each point on the axis.
experiment.n_trials = 100 # 50 trials per nominal_amplitude point for good statistics

# Define the analysis to be performed on each simulated data set.
experiment.add_analysis(
    name='NLS_Fit',
    fitter_method='nls',
    result_cols=['amp', 'm', 'phi', 'psi', 'ssq'], # Parameters I want to collect
    fitter_kwargs={
        'n': 20,       # 20 modulation cycles per fit buffer
        'n_harmonics': 10,   # Use this many harmonics for NLS
    }
)

# --- Run the Experiment ---
print(f"Starting experiment: '{experiment.description}'...")
experiment.results = experiment.run()
print("Experiment completed.")
```

## Citing DeepFMKit

If you use DeepFMKit in your research, please cite this paper:

```bibtex
@article{Dovale2025,
  doi = {10.48550/ARXIV.2507.23183},
  url = {https://arxiv.org/abs/2507.23183},
  author = {Dovale-\'Alvarez,  Miguel},
  keywords = {Optics (physics.optics),  Applied Physics (physics.app-ph),  Instrumentation and Detectors (physics.ins-det),  FOS: Physical sciences,  FOS: Physical sciences},
  title = {Fundamental Limitations of Absolute Ranging via Deep Frequency Modulation Interferometry},
  publisher = {arXiv},
  year = {2025},
  copyright = {arXiv.org perpetual,  non-exclusive license}
}
```

## License
This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
