Skip to content

Commit 74abdb6

Browse files
committed
Initial commit.
0 parents  commit 74abdb6

File tree

17 files changed

+3282
-0
lines changed

17 files changed

+3282
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
venv
2+
3+
__pycache__
4+
*.egg-info
5+
6+
dist

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Artificial Scientist Lab
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Differometor
2+
3+
[![PyPI version](https://img.shields.io/pypi/v/differometor)](https://pypi.org/project/differometor/)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5+
6+
[**Install guide**](#installation)
7+
| [**Getting started**](#getting-started)
8+
| [**Cavity example**](#cavity-example)
9+
| [**Documentation**](#documentation)
10+
| [**Development**](#development)
11+
| [**Citing Differometor**](#citing-differometor)
12+
13+
**Differometor is a differentiable frequency domain interferometer simulator** implemented in Python using the [JAX](https://docs.jax.dev/en/latest/index.html) framework. Differometor’s implementation closely follows the established [Finesse simulator](https://finesse.ifosim.org/) and offers functionality to simulate plane waves in quasi-static, user-specified setup configurations including light field modulations, quantum noise calculations and optomechanical effects.
14+
15+
Differometor is designed to become a powerful tool for state-of-the-art AI-driven design of novel fundamental physics experiments.
16+
17+
The main difference to existing interferometer simulators is the speed of interferometer optimizations. Running auto-differentiated interferometer optimizations with Differometor on a Nvidia Quadro RTX 6000 GPU can be up to 160 times faster than running numerically differentiated optimizations with Finesse on a Xeon E5-2698 v4 CPU.
18+
19+
<img src="images/workflow.png" alt="workflow" width="1000"/>
20+
21+
Figure 1: Left: The Differentiable Interferometer Simulator (Differometor) takes a user-defined configuration (e.g., a 2×2 universal interferometer, UIFO) and efficiently computes its properties by making use
22+
of just-in-time compilation, GPU acceleration, and automatic differentiation. This enables fast, gradient-based exploration of detector designs. Right: Gradient evaluation speedup over numerical approximation
23+
with Finesse for various optical setups, as a function of optimized parameter count.
24+
25+
## Installation
26+
27+
This installation was tested on Ubuntu 24.04.1 LTS and on SUSE Linux Enterprise Server 15 SP6.
28+
29+
### From PyPI
30+
31+
```bash
32+
pip install differometor
33+
```
34+
35+
### Development mode
36+
37+
```bash
38+
git clone https://github.com/artificial-scientist-lab/Differometor differometor
39+
cd differometor
40+
pip install -e .
41+
```
42+
43+
### GPU support
44+
45+
Both installation methods above automatically install the CPU version of JAX. To upgrade to the GPU version with CUDA 12, please use:
46+
47+
```bash
48+
pip install --upgrade "jax[cuda12]==0.5.0"
49+
```
50+
51+
52+
## Getting started
53+
54+
A good way to familiarize yourself with Differometor is to take a look at the [examples](examples):
55+
56+
* [Optical cavity](examples/cavity.py): A simple case of modelling a Fabry-Pérot cavity. Compare this setup with the corresponding [Finesse example](https://finesse.ifosim.org/docs/latest/examples/01_simple_cav.html).
57+
* [Sensitivity of Advanced LIGO setup](examples/aligo.py): A more advanced example of computing the strain sensitivity of a simplified Advanced LIGO setup. Compare this setup with the corresponding [Finesse example](https://finesse.ifosim.org/docs/latest/examples/09_aligo_sensitivity.html).
58+
* [Sensitivity of Voyager setup](examples/voyager.py): Same as for the Advanced LIGO setup above, but with a Voyager configuration using balanced homodyne readout.
59+
* [Sensitivity optimization of Voyager setup](examples/voyager_optimization.py): This example showcases the potential of Differometor when it comes to interferometer optimization. It uses the Voyager setup from above, initializes its parameters randomly and then optimizes the parameters to achieve the best possible sensitivity.
60+
61+
62+
## Cavity Example
63+
64+
[Optical cavity](examples/cavity.py): A simple case of modelling a Fabry-Pérot cavity. Compare this setup with the corresponding [Finesse example](https://finesse.ifosim.org/docs/latest/examples/01_simple_cav.html).
65+
66+
```python
67+
import differometor as df
68+
import jax.numpy as jnp
69+
from differometor.components import power_detector
70+
import matplotlib.pyplot as plt
71+
72+
# define a simple cavity setup with three detectors
73+
S = df.Setup()
74+
S.add("laser", "l0", power=1)
75+
S.add("mirror", "m0", reflectivity=0.99, loss=0)
76+
S.add("mirror", "m1", reflectivity=0.991, loss=0)
77+
S.space("l0", "m0", length=1)
78+
S.space("m0", "m1", length=1)
79+
S.add("detector", "refl", target="m0", port="left", direction="out")
80+
S.add("detector", "circ", target="m1", port="left", direction="in")
81+
S.add("detector", "trns", target="m1", port="right", direction="out")
82+
83+
# set the tuning range
84+
tunings = jnp.linspace(-180, 180, 400)
85+
86+
# run the simulation with the tuning as the changing parameter
87+
carrier, signal, noise, detector_ports, *_ = df.run(S, [("m0", "tuning")], tunings)
88+
89+
# calculate the power
90+
powers = power_detector(carrier)
91+
92+
# plot the power at the detector ports
93+
plt.figure()
94+
plt.plot(tunings, powers[detector_ports[0]], label="refl")
95+
plt.plot(tunings, powers[detector_ports[1]], label="circ")
96+
plt.plot(tunings, powers[detector_ports[2]], label="trns")
97+
plt.yscale("log")
98+
plt.xlabel("Tuning (degrees)")
99+
plt.ylabel("Power (W)")
100+
plt.grid()
101+
plt.legend()
102+
plt.show()
103+
```
104+
105+
Output:
106+
107+
<img src="images/cavity.png" alt="cavity" width="500"/>
108+
109+
## Documentation
110+
111+
Differometor provides a Setup class to define the interferometer setup. You can add components and connect these components via spaces.
112+
113+
You can add the following components:
114+
115+
* **mirror**
116+
* ```setup.add("mirror", "m0")``` adds a mirror component with name m0 and default properties. Names cannot contain underscores.
117+
* ```setup.add("mirror", "m1", reflectivity=0.5)``` adds a mirror component with name m1 which reflects half of the incoming light and transmits the other half. All other properties are set to default.
118+
* ```setup.add("mirror", "abc", reflectivity=0.25, loss=0.5)``` adds a mirror component with name abc which reflects 25% of the incoming light, looses 50% of the incoming light and transmits the other 25%.
119+
* ```setup.add("mirror", "m3", transmissivity=0.3, loss=0.6, tuning=90)``` adds a mirror component with name m3 which transmits 30% of the incoming light, looses 60% of the incoming light and is [tuned](https://finesse.ifosim.org/docs/latest/physics/plane-waves/beam_splitter.html#tuning) by 90 degrees.
120+
* The default properties of a mirror are:
121+
* reflectivity: 0.5
122+
* loss: 5e-6
123+
* tuning: 0
124+
* A mirror has two ports: left and right. Each port has two directions: in and out.
125+
* Reflectivity, transmissivity and loss always add up to 1 and are given as fractions of the total light amplitude. Differometor expresses reflectivity and transmissivity as one reflectivity parameter which is the fraction of the remaining light after loss that is reflected. Reflectivity and transmissivity of mirror m3 in the example above would therefore be expressed as the fraction: (1-0.3-0.6)/(1-0.6) = 0.25
126+
* **beamsplitter**
127+
* A beamsplitter is handled equivalent to a mirror, but has an additional alpha parameter describing the angle of incidence of the incoming light and two more ports.
128+
* ```setup.add("beamsplitter", "bs", alpha=50)``` adds a beamsplitter component with name bs which reflects half of the incoming light and transmits the other half at an angle of 50 degrees. All other properties are set to default.
129+
* A beamsplitter has four ports: left, right, top and bottom. Each port has two directions: in and out. Incoming light on the left port is transmitted to the right port and reflected to the top port. Incoming light at the top is transmitted to the bottom port and reflected to the left port. Incoming light at the right port is transmitted to the left port and reflected to the bottom port. Incoming light at the bottom port is transmitted to the top port and reflected to the right port.
130+
* The default properties of a beamsplitter are:
131+
* reflectivity: 0.5
132+
* loss: 5e-6
133+
* tuning: 0
134+
* alpha: 45
135+
* **directional_beamsplitter**
136+
* ```setup.add("directional_beamsplitter", "dbs")``` adds a directional beamsplitter component with name dbs
137+
* A directional beamsplitter forwards all light at the left port to the right port, all light at the top port to the left port, all light at the right port to the bottom port and all light at the bottom port to the top port.
138+
* **nothing**
139+
* ```setup.add("nothing", "n0")``` adds a nothing component with name n0. These have two ports, left and right. Each port has two directions: in and out. Nothing components can be used to connect components without using a space.
140+
* The left input goes to the right output and the right input goes to the left output. Nothing changes.
141+
* **laser**
142+
* ```setup.add("laser", "l0")``` adds a laser component with name l0 and default properties.
143+
* ```setup.add("laser", "l1", power=0.5)``` adds a laser component with name l1 which has a power of 0.5. All other properties are set to default.
144+
* lasers can have a target if you want to connect them to a component without using a space. ```setup.add("laser", "l2", target="m0", port="left", direction="in")``` adds a laser component with name l2 which is directly connected to the left port of m0 in the in direction. All other properties are set to default.
145+
* The default properties of a laser are:
146+
* power: 1
147+
* phase: 0
148+
* **squeezer**
149+
* ```setup.add("squeezer", "sq", db=10, angle=0)``` adds a squeezer with name sq producing a squeezed-light beam with given squeezing in decibels db and angle.
150+
* Like a laser above, a squeezer can also have an additional target, port and direction.
151+
* The default properties of a squeezer are:
152+
* db: 0
153+
* angle: 90
154+
* **free_mass**
155+
* ```setup.add("free_mass", "sus", mass=10, target="m0")``` adds a simple free mass suspension with name sus and mass 10kg to the object with name m0. Masses can be added to mirrors or beamsplitters.
156+
* The default properties of a free mass are:
157+
* mass: 40
158+
* **signal**
159+
* ```setup.add("signal", "s0", target="l0_amplitude")``` adds a amplitude signal generator with name s0 to the laser l0.
160+
* ```setup.add("signal", "s1", target="abc_frequency")``` adds a frequency signal generator with name s1 to the laser abc.
161+
* ```setup.add("signal", "GW", target="m0_m1")``` adds a signal generator with name GW to the space between m0 and m1.
162+
* ```setup.add("signal", "s2", target="l0_amplitude", amplitude=("l0_power", jnp.sqrt))``` adds a signal generator with name s2 to the laser l0. The signal amplitude is defined as the square root of the laser power. So instead of actual values, properties can be defined as functions of other properties.
163+
* Signal generators can be connected only to spaces or lasers. For lasers, they can only be applied as amplitude (l0_amplitude) or frequency (l0_frequency) modulation.
164+
* The default properties of a signal are:
165+
* amplitude: 1
166+
* phase: 0
167+
* **qhd**
168+
* ```setup.add("qhd", "qhd0", detector1="noise1", detector2="noise2", phase=45)``` adds a balanced homodyne quantum noise detector with name qhd0 and a phase of 45 degrees. It uses the two noise detectors noise1 and noise2.
169+
* The default properties of a qhd are:
170+
* phase: 0
171+
* **frequency**
172+
* ```setup.add("frequency", "f")``` sets the frequency used by all signals in the simulation to the default 1 Hz. It is a unique element and can only be set once in a given setup.
173+
* The default properties of a frequency are:
174+
* frequency: 1
175+
* **detector**
176+
* ```setup.add("detector", "d0", target="bs", port="top", direction="out")``` adds a detector with name d0 to the top port of the object bs in the out direction.
177+
* ```setup.add("detector", "d1", target="m0")``` adds a detector with name d1 to the object m0. The default port and direction is left and in.
178+
* **qnoised**
179+
* ```setup.add("qnoised", "1", target="m0")``` adds a noise detector with name 1 to the left port of the object m0 in the in direction.
180+
* ```setup.add("qnoised", "noise", target="m0", port="left", direction="in", auxiliary=True)``` adds a noise detector with name noise to the left port of the object m0 in the in direction. This is an auxiliary detector that does not get its own noise output and can be used as detector1 or detector2 in a qhd.
181+
182+
You can connect components via spaces:
183+
184+
* **space**
185+
* ```setup.space("m0", "m1", length=0.5)``` adds a space between the objects m0 and m1 with a length of 0.5 meters. By default it connects the right port of m0 with the left port of m1.
186+
* ```setup.space("bs1", "bs2", length=100, source_port="top", target_port="bottom")``` adds a space between the objects bs1 and bs2 with a length of 100 meters. It connects the top port of bs1 with the bottom port of bs2.
187+
* ```setup.space("l0", "m0", length=1, refractive_index=1.5, target_port="right")``` adds a space between the laser l0 and the object m0 with a length of 1 meter and a refractive index of 1.5. It connects the laser l0 with the right port of m0, in the in direction.
188+
* The default properties of a space are:
189+
* length: 0
190+
* refractive_index: 1
191+
192+
For further details on the limits and inner workings of the simulator, please refer to the [Finesse documentation](https://finesse.ifosim.org/docs/latest/index.html).
193+
194+
195+
## Development
196+
197+
Differometor is developed at the [Artificial Scientist Lab](https://mpl.mpg.de/research-at-mpl/independent-research-groups/krenn-research-group/) under Dr. Mario Krenn at the Max Planck Institute for the Science of Light in Erlangen, Germany.
198+
199+
200+
## Citing Differometor
201+
202+
To cite this repository, please use the following BibTeX entry:
203+
204+
```
205+
@software{differometor2025github,
206+
author = {Jonathan Klimesch and Yehonathan Drori and Rana X Adhikari and Mario Krenn},
207+
title = {Differometor: A Differentiable Interferometer Simulator for the Computational Design of Gravitational Wave Detectors},
208+
url = {http://github.com/artificial-scientist-lab/Differometor},
209+
version = {0.1.0},
210+
year = {2025},
211+
}
212+
```

differometor/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from differometor.setups import Setup
2+
from differometor.simulate import run, run_build_step, run_simulation_step, simulate_in_parallel

0 commit comments

Comments
 (0)