First commit
This commit is contained in:
commit
098393a4b2
7 changed files with 2350 additions and 0 deletions
32
MaskedReadoutDC.py
Normal file
32
MaskedReadoutDC.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from finesse.model import Node
|
||||||
|
from finesse.components.general import borrows_nodes
|
||||||
|
|
||||||
|
from finesse.components.readout import ReadoutDC
|
||||||
|
from finesse.detectors.general import MaskedDetector
|
||||||
|
|
||||||
|
|
||||||
|
@borrows_nodes()
|
||||||
|
class MaskedReadoutDC(ReadoutDC, MaskedDetector):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
optical_node: Node,
|
||||||
|
*,
|
||||||
|
output_detectors: bool = False,
|
||||||
|
pdtype: None | str | dict[str, str] = None,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name,
|
||||||
|
optical_node,
|
||||||
|
output_detectors=output_detectors,
|
||||||
|
pdtype=pdtype,
|
||||||
|
)
|
||||||
|
MaskedDetector.__init__(self, name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_mask(
|
||||||
|
self,
|
||||||
|
) -> Literal[False]: # should return true here, but cannot override
|
||||||
|
return False
|
533
main.py
Normal file
533
main.py
Normal file
|
@ -0,0 +1,533 @@
|
||||||
|
from logging import getLogger, basicConfig, WARNING
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from importlib.metadata import version, PackageNotFoundError
|
||||||
|
|
||||||
|
from rich.theme import Theme
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress
|
||||||
|
from rich.logging import RichHandler
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
from matplotlib.pyplot import figure, show
|
||||||
|
|
||||||
|
from numpy import sqrt, geomspace, mean, diff
|
||||||
|
|
||||||
|
from finesse.model import Model
|
||||||
|
from finesse.analysis.actions import (
|
||||||
|
TemporaryParameters,
|
||||||
|
Change,
|
||||||
|
Maximize,
|
||||||
|
Minimize,
|
||||||
|
Series,
|
||||||
|
Noxaxis,
|
||||||
|
)
|
||||||
|
from finesse.exceptions import ModelMissingAttributeError
|
||||||
|
|
||||||
|
from astropy.units import W, rad, m, mW # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
|
from gwpy.frequencyseries import FrequencySeries
|
||||||
|
|
||||||
|
from h5py import File
|
||||||
|
|
||||||
|
from git import Repo
|
||||||
|
|
||||||
|
from backscattering_simulation_data import Root, Scatterer
|
||||||
|
from backscattering_simulation_data.metadata import (
|
||||||
|
MetadataRoot,
|
||||||
|
MetadataScatterer,
|
||||||
|
Interferometer,
|
||||||
|
Git,
|
||||||
|
)
|
||||||
|
|
||||||
|
from utils import fix_dark_fringe, TF
|
||||||
|
from MaskedReadoutDC import MaskedReadoutDC
|
||||||
|
|
||||||
|
theme = Theme(
|
||||||
|
{
|
||||||
|
"strong": "cyan underline",
|
||||||
|
"result": "red bold",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console = Console(theme=theme)
|
||||||
|
|
||||||
|
basicConfig(
|
||||||
|
level=WARNING,
|
||||||
|
format="%(message)s",
|
||||||
|
datefmt="[%X]",
|
||||||
|
handlers=[RichHandler(console=console, rich_tracebacks=True)],
|
||||||
|
)
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
|
||||||
|
parser = ArgumentParser(
|
||||||
|
prog="main",
|
||||||
|
description="Generate a model file containing computed Transfer Function",
|
||||||
|
usage="python main.py --filename={filename} --laser-power={laser power} --dark-fringe={dark fringe power} --sr-rotation={sr rotation} --max-tem={maxtem} [--precision={precision} | --quiet | --verbose]",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--filename",
|
||||||
|
help="path to the new file containing the transfer function",
|
||||||
|
type=Path,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--laser-power",
|
||||||
|
help="power injected in the interferometer (in W)",
|
||||||
|
type=float,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dark-fringe",
|
||||||
|
help="power on the dark fringe (dark fringe offset) (in W)",
|
||||||
|
type=float,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r",
|
||||||
|
"--sr-rotation",
|
||||||
|
help="yaw SR misalignement angle (in rad)",
|
||||||
|
type=float,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-m",
|
||||||
|
"--max-tem",
|
||||||
|
help="number of modes in the model",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-n",
|
||||||
|
"--precision",
|
||||||
|
help="number of point in the transfer function model",
|
||||||
|
type=int,
|
||||||
|
default=1000,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-q",
|
||||||
|
"--quiet",
|
||||||
|
help="suppress outputs in the terminal",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-V",
|
||||||
|
"--verbose",
|
||||||
|
help="display graph and table on the terminal",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-a",
|
||||||
|
"--name",
|
||||||
|
help="name of the generated result. Should be unique",
|
||||||
|
type=str,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
filename = args.filename # name of the created file
|
||||||
|
laser_power = args.laser_power * W # power injected
|
||||||
|
dark_fringe_power = args.dark_fringe * W # power on dark fringe
|
||||||
|
precision = args.precision # number of point in the simulation
|
||||||
|
sr_rotation = args.sr_rotation * rad # yaw SR misalignement angle
|
||||||
|
maxtem = args.max_tem # number of modes in the model
|
||||||
|
quiet = args.quiet # no progress bar
|
||||||
|
verbose = args.verbose # display figure and tables
|
||||||
|
name = args.name # unique name of the generated hdf5 data
|
||||||
|
|
||||||
|
if filename.suffix != ".hdf5":
|
||||||
|
filename = filename.with_suffix(".hdf5")
|
||||||
|
|
||||||
|
with Progress(console=console) as progress:
|
||||||
|
model_building = progress.add_task(
|
||||||
|
description="building the model",
|
||||||
|
start=False,
|
||||||
|
total=None,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
locking = progress.add_task(
|
||||||
|
description="locking the model",
|
||||||
|
start=False,
|
||||||
|
total=None,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
set_dark_fringe = progress.add_task(
|
||||||
|
description="set dark fringe offset",
|
||||||
|
start=False,
|
||||||
|
total=None,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
computing_TF = progress.add_task(
|
||||||
|
description="computing transfer function",
|
||||||
|
start=False,
|
||||||
|
total=8,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
write_file = progress.add_task(
|
||||||
|
description="writing file",
|
||||||
|
start=False,
|
||||||
|
total=None,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
C_B1_DETECTOR = "TF" # depends on the code itself (don't change)
|
||||||
|
|
||||||
|
# Creating model
|
||||||
|
|
||||||
|
progress.start_task(model_building)
|
||||||
|
progress.update(model_building, visible=not quiet)
|
||||||
|
|
||||||
|
model = Model()
|
||||||
|
model.phase_config(zero_k00=False, zero_tem00_gouy=True)
|
||||||
|
model.parse(Path("model.kat").read_text())
|
||||||
|
model.lambda0 = model.get("wavelength")
|
||||||
|
model.get("fsig").f = 1.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
model.get("SR").xbeta = sr_rotation.to(rad).value
|
||||||
|
model.get("laser").P = laser_power.to(W).value
|
||||||
|
except ModelMissingAttributeError:
|
||||||
|
logger.warning("SR, or laser is not define in the model")
|
||||||
|
|
||||||
|
try:
|
||||||
|
model.get("B1")
|
||||||
|
logger.info("B1 already exists")
|
||||||
|
except ModelMissingAttributeError:
|
||||||
|
try:
|
||||||
|
model.add(
|
||||||
|
MaskedReadoutDC(
|
||||||
|
"B1",
|
||||||
|
optical_node=model.get("SDB1").p2.o,
|
||||||
|
output_detectors=True,
|
||||||
|
pdtype=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logger.info("B1 created")
|
||||||
|
except ModelMissingAttributeError:
|
||||||
|
logger.warning("SDB1 does not exist in the model")
|
||||||
|
|
||||||
|
progress.remove_task(model_building)
|
||||||
|
progress.start_task(locking)
|
||||||
|
progress.update(locking, visible=not quiet)
|
||||||
|
# Locking
|
||||||
|
|
||||||
|
result = model.run(
|
||||||
|
TemporaryParameters(
|
||||||
|
Series(
|
||||||
|
Change(
|
||||||
|
{
|
||||||
|
"SR.misaligned": True,
|
||||||
|
"PR.misaligned": True,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Maximize(
|
||||||
|
model.get("NE_p1"),
|
||||||
|
model.get("NORTH_ARM.DC"),
|
||||||
|
bounds=[-180, 180],
|
||||||
|
tol=1e-14,
|
||||||
|
),
|
||||||
|
Maximize(
|
||||||
|
model.get("WE_p1"),
|
||||||
|
model.get("WEST_ARM.DC"),
|
||||||
|
bounds=[-180, 180],
|
||||||
|
tol=1e-14,
|
||||||
|
),
|
||||||
|
Minimize(
|
||||||
|
model.get("SR_p2"),
|
||||||
|
model.get("MICH.DC"),
|
||||||
|
bounds=[-180, 180],
|
||||||
|
tol=1e-14,
|
||||||
|
),
|
||||||
|
Change(
|
||||||
|
{
|
||||||
|
"PR.misaligned": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Maximize(
|
||||||
|
model.get("PR_p2"),
|
||||||
|
model.get("PRCL.DC"),
|
||||||
|
bounds=[-180, 180],
|
||||||
|
tol=1e-14,
|
||||||
|
),
|
||||||
|
Change(
|
||||||
|
{
|
||||||
|
"SR.misaligned": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Maximize(
|
||||||
|
model.get("B1_DC"),
|
||||||
|
model.get("SRCL.DC"),
|
||||||
|
bounds=[-180, 180],
|
||||||
|
tol=1e-14,
|
||||||
|
),
|
||||||
|
Change(
|
||||||
|
{
|
||||||
|
"SRCL.DC": -90,
|
||||||
|
},
|
||||||
|
relative=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
exclude=(
|
||||||
|
"NE.phi",
|
||||||
|
"NI.Phi",
|
||||||
|
"WE.phi",
|
||||||
|
"WI.phi",
|
||||||
|
"SR.phi",
|
||||||
|
"PR.phi",
|
||||||
|
"NORTH_ARM.DC",
|
||||||
|
"WEST_ARM.DC",
|
||||||
|
"DARM.DC",
|
||||||
|
"MICH.DC",
|
||||||
|
"PRCL.DC",
|
||||||
|
"SRCL.DC",
|
||||||
|
"SR.misaligned",
|
||||||
|
"PR.misaligned",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adding HOMs
|
||||||
|
model.modes(maxtem=maxtem)
|
||||||
|
model.get("B1").select_mask(exclude=(0, 0))
|
||||||
|
|
||||||
|
progress.remove_task(locking)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
solution = model.run(Noxaxis())
|
||||||
|
if solution is None:
|
||||||
|
raise Exception("error when modeling the locked model")
|
||||||
|
table = Table(title="Puissances dans l'interferomètre")
|
||||||
|
table.add_column("position", justify="left", style="white")
|
||||||
|
table.add_column("puissance (W)", justify="left", style="cyan")
|
||||||
|
|
||||||
|
table.add_row("Injection", str(model.get("laser").P.eval()))
|
||||||
|
table.add_row("PR", str(solution["PR_p1"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row(
|
||||||
|
"cavité de recyclage de puissance",
|
||||||
|
str(solution["PR_p2"]), # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
)
|
||||||
|
table.add_row("cavité ouest", str(solution["WE_p1"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row("cavité nord", str(solution["NE_p1"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row("frange noire", str(solution["SR_p2"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row("SNEB", str(solution["SNEB_DC"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row("SWEB", str(solution["SWEB_DC"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
table.add_row("SDB1", str(solution["SDB1_DC"])) # pyright: ignore[reportArgumentType,reportCallIssue]
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
progress.start_task(set_dark_fringe)
|
||||||
|
progress.update(set_dark_fringe, visible=not quiet)
|
||||||
|
|
||||||
|
number, power = fix_dark_fringe(
|
||||||
|
model, dark_fringe_power.to(W).value
|
||||||
|
)
|
||||||
|
power = power * W
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
console.print(
|
||||||
|
"[result]{dof}[/result] with [result]{number} steps[/result] give [result]{power:.6f}[/result] on the [strong]dark fringe[/strong]".format(
|
||||||
|
number=number,
|
||||||
|
dof=model.get("DARM").DC,
|
||||||
|
power=power.to(mW),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
dark_fringe_power = (
|
||||||
|
power # to keep this result for the h5df metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.remove_task(set_dark_fringe)
|
||||||
|
progress.start_task(computing_TF)
|
||||||
|
progress.update(computing_TF, visible=not quiet)
|
||||||
|
|
||||||
|
# Computing TF
|
||||||
|
|
||||||
|
model.get("SNEB").phi = model.get("NE").phi - 45
|
||||||
|
model.get("SWEB").phi = model.get("WE").phi - 45
|
||||||
|
model.get("SDB1").phi = model.get("SR").phi + 45
|
||||||
|
|
||||||
|
quad_tf: dict[str, TF] = dict() # bench is in phase quadrature (Kn)
|
||||||
|
in_tf: dict[str, TF] = dict() # bench is in phase (Kp)
|
||||||
|
|
||||||
|
for bench_name in ["SNEB", "SWEB", "SDB1"]:
|
||||||
|
quad_tf[bench_name] = TF(
|
||||||
|
model,
|
||||||
|
["{}_z".format(bench_name)],
|
||||||
|
[C_B1_DETECTOR],
|
||||||
|
geomspace(5, 10000, precision), # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
progress.update(computing_TF, advance=1)
|
||||||
|
|
||||||
|
quad_tf["DARM"] = TF(
|
||||||
|
model,
|
||||||
|
["DARM"],
|
||||||
|
[C_B1_DETECTOR],
|
||||||
|
geomspace(5, 10000, precision), # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
progress.update(computing_TF, advance=1)
|
||||||
|
|
||||||
|
model.get("SNEB").phi = model.get("NE").phi
|
||||||
|
model.get("SWEB").phi = model.get("WE").phi
|
||||||
|
model.get("SDB1").phi = model.get("SR").phi
|
||||||
|
|
||||||
|
for bench_name in ["SNEB", "SWEB", "SDB1"]:
|
||||||
|
in_tf[bench_name] = TF(
|
||||||
|
model,
|
||||||
|
["{}_z".format(bench_name)],
|
||||||
|
[C_B1_DETECTOR],
|
||||||
|
geomspace(5, 10000, precision), # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
progress.update(computing_TF, advance=1)
|
||||||
|
|
||||||
|
in_tf["DARM"] = TF(
|
||||||
|
model,
|
||||||
|
["DARM"],
|
||||||
|
[C_B1_DETECTOR],
|
||||||
|
geomspace(5, 10000, precision), # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
progress.update(computing_TF, advance=1)
|
||||||
|
|
||||||
|
bench_names = ["SNEB", "SWEB", "SDB1"]
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
for bench_name in bench_names:
|
||||||
|
Figure = figure()
|
||||||
|
ax = Figure.gca()
|
||||||
|
ax.set_title(
|
||||||
|
"finesse {bench_name}".format(bench_name=bench_name)
|
||||||
|
).figure.gca().loglog(
|
||||||
|
quad_tf[bench_name].f,
|
||||||
|
sqrt(
|
||||||
|
(
|
||||||
|
abs(quad_tf[bench_name].get(C_B1_DETECTOR))
|
||||||
|
/ abs(quad_tf["DARM"].get(C_B1_DETECTOR))
|
||||||
|
/ model.get("space_NI_NE").L.eval()
|
||||||
|
)
|
||||||
|
** 2
|
||||||
|
+ (
|
||||||
|
abs(in_tf[bench_name].get(C_B1_DETECTOR))
|
||||||
|
/ abs(in_tf["DARM"].get(C_B1_DETECTOR))
|
||||||
|
/ model.get("space_NI_NE").L.eval()
|
||||||
|
)
|
||||||
|
** 2
|
||||||
|
),
|
||||||
|
label="sum of TF ($\\sqrt{{K_n^2 + K_p^2}}$)",
|
||||||
|
)[0].figure.gca().loglog(
|
||||||
|
quad_tf[bench_name].f,
|
||||||
|
abs(quad_tf[bench_name].get(C_B1_DETECTOR))
|
||||||
|
/ abs(quad_tf["DARM"].get(C_B1_DETECTOR))
|
||||||
|
/ model.get("space_NI_NE").L.eval(),
|
||||||
|
label="Kn",
|
||||||
|
)[0].figure.gca().loglog(
|
||||||
|
in_tf[bench_name].f,
|
||||||
|
abs(in_tf[bench_name].get(C_B1_DETECTOR))
|
||||||
|
/ abs(in_tf["DARM"].get(C_B1_DETECTOR))
|
||||||
|
/ model.get("space_NI_NE").L.eval(),
|
||||||
|
label="Kp",
|
||||||
|
)[0].figure.gca().set_ylabel(
|
||||||
|
"$\\frac{{m}}{{m}}$"
|
||||||
|
).figure.gca().legend().figure.gca().set_xlabel(
|
||||||
|
"Frequencies ($Hz$)"
|
||||||
|
).figure.gca().grid(True, "both", "both")
|
||||||
|
show()
|
||||||
|
|
||||||
|
progress.remove_task(computing_TF)
|
||||||
|
progress.start_task(write_file)
|
||||||
|
progress.update(write_file, visible=not quiet)
|
||||||
|
|
||||||
|
repo = Repo(Path(__file__).parent)
|
||||||
|
|
||||||
|
current_version = "unknown"
|
||||||
|
try:
|
||||||
|
current_version = version(__name__)
|
||||||
|
except PackageNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
data = Root(
|
||||||
|
MetadataRoot(
|
||||||
|
name=name,
|
||||||
|
maxtem=maxtem,
|
||||||
|
git=Git(
|
||||||
|
remote=urlparse(repo.remote().url),
|
||||||
|
commit=repo.active_branch.commit.hexsha,
|
||||||
|
version=current_version,
|
||||||
|
branch=repo.active_branch.name,
|
||||||
|
),
|
||||||
|
interferometer=Interferometer(
|
||||||
|
dark_fringe_power=dark_fringe_power,
|
||||||
|
injection_power=laser_power,
|
||||||
|
sr_yaw=sr_rotation,
|
||||||
|
north_arm=model.get("space_NI_NE").L.eval() * m,
|
||||||
|
west_arm=model.get("space_WI_WE").L.eval() * m,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
Scatterer(
|
||||||
|
MetadataScatterer(
|
||||||
|
"SDB1",
|
||||||
|
),
|
||||||
|
kn=FrequencySeries(
|
||||||
|
data=quad_tf["SDB1"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=quad_tf["SDB1"].f[0],
|
||||||
|
df=mean(diff(quad_tf["SDB1"].f)),
|
||||||
|
),
|
||||||
|
kp=FrequencySeries(
|
||||||
|
data=in_tf["SDB1"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=in_tf["SDB1"].f[0],
|
||||||
|
df=mean(diff(in_tf["SDB1"].f)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Scatterer(
|
||||||
|
MetadataScatterer(
|
||||||
|
"SNEB",
|
||||||
|
),
|
||||||
|
kn=FrequencySeries(
|
||||||
|
data=quad_tf["SNEB"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=quad_tf["SNEB"].f[0],
|
||||||
|
df=mean(diff(quad_tf["SNEB"].f)),
|
||||||
|
),
|
||||||
|
kp=FrequencySeries(
|
||||||
|
data=in_tf["SNEb"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=in_tf["SNEB"].f[0],
|
||||||
|
df=mean(diff(in_tf["SNEB"].f)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Scatterer(
|
||||||
|
MetadataScatterer(
|
||||||
|
"SWEB",
|
||||||
|
),
|
||||||
|
kn=FrequencySeries(
|
||||||
|
data=quad_tf["SWEb"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=quad_tf["SWEB"].f[0],
|
||||||
|
df=mean(diff(quad_tf["SWEB"].f)),
|
||||||
|
),
|
||||||
|
kp=FrequencySeries(
|
||||||
|
data=in_tf["SWEB"].get(C_B1_DETECTOR),
|
||||||
|
unit=m / W,
|
||||||
|
f0=in_tf["SWEB"].f[0],
|
||||||
|
df=mean(diff(in_tf["SWEB"].f)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
FrequencySeries(
|
||||||
|
data=quad_tf["DARM"].get(C_B1_DETECTOR),
|
||||||
|
unit=W / m, # power on B1 to arm displacement
|
||||||
|
f0=quad_tf["DARM"].f[0],
|
||||||
|
df=mean(diff(quad_tf["DARM"].f)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
with File(name=filename, mode="w") as file:
|
||||||
|
data.to_hdf5(file)
|
||||||
|
|
||||||
|
progress.remove_task(write_file)
|
5
mise.toml
Normal file
5
mise.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[tools]
|
||||||
|
python = "3.13.2"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
_.python.venv = { path = ".venv", create = true, uv_create_args = ["--seed"] }
|
182
model.kat
Normal file
182
model.kat
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
# An Advanced Virgo Plus input file for Finesse 3
|
||||||
|
|
||||||
|
# Source: current finesse-virgo's model
|
||||||
|
# Modified with: https://git.ligo.org/virgo/isc/parameters/-/blob/377982cc0fd35a561a2f0d9356ca847072d39c57/mechanics.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/isc/parameters/-/blob/c7e2cf038a096fbb5a20cc58ff01650cec5a96fa/advanced_virgo.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/isc/parameters/-/blob/7de842d4f3ebe3744a608e2f19d6d0a15a437cdf/advanced_virgo_current.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/isc/parameters/-/blob/a89dfb3b8556c9796b64531da93343f56e773e6b/detection_current.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/isc/optickle/-/blob/f577e6f073c0dda2ef9db3d2fbab27021f406182/models/DRITFscatter.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/virgoapp/simulinkNoiseBudget/-/blob/6e7e1b95b7817cdb6cc67d5748281ed1d7ffc805/DRITF/scatterRadiationPressureTune.m
|
||||||
|
# Modified with: https://git.ligo.org/virgo/virgoapp/simulinkNoiseBudget/-/blob/6e7e1b95b7817cdb6cc67d5748281ed1d7ffc805/DRITF/scatterRadiationPressure.m
|
||||||
|
|
||||||
|
# Defines general variables needed for the model creation here
|
||||||
|
|
||||||
|
# Constant
|
||||||
|
variable speed_of_light 299792458
|
||||||
|
|
||||||
|
# Material
|
||||||
|
variable silica 1.44963
|
||||||
|
|
||||||
|
# Injection
|
||||||
|
variable wavelength 1.064e-6 units='m'
|
||||||
|
variable frequency speed_of_light/wavelength units='Hz'
|
||||||
|
|
||||||
|
# Schnupp asymmetry
|
||||||
|
variable schnupp_asymetry 0.23 units='m'
|
||||||
|
|
||||||
|
# Cavity length
|
||||||
|
variable length_PRCL 11.952 units='m'
|
||||||
|
variable length_SRCL 11.952 units='m'
|
||||||
|
|
||||||
|
# Laser
|
||||||
|
laser laser P=25
|
||||||
|
|
||||||
|
# Modulator
|
||||||
|
|
||||||
|
#modulator eom1 f=6270777 midx=0.22 order=1
|
||||||
|
#space space_laser_eom1 laser.p1 eom1.p1 L=0.1
|
||||||
|
|
||||||
|
#modulator eom2 f=4/3*eom1.f midx=0.15 order=1
|
||||||
|
#space space_eom1_eom2 eom1.p2 eom2.p1 L=0.1
|
||||||
|
|
||||||
|
#modulator eom3 f=9*eom1.f midx=0.204 order=1
|
||||||
|
#space space_eom2_eom3 eom2.p2 eom3.p1 L=0.1
|
||||||
|
|
||||||
|
#modulator eom4 f=21*eom1.f midx=0.128 order=1
|
||||||
|
#space space_eom3_eom4 eom3.p2 eom4.p1 L=0.1
|
||||||
|
|
||||||
|
# Power Recycling
|
||||||
|
mirror PR_AR T=9.9984e-1 L=1.6e-4 Rc=-3.62 phi=PR.phi
|
||||||
|
mirror PR T=0.04835 L=37.5e-6 Rc=-1430
|
||||||
|
space inside_PR PR_AR.p2 PR.p1 L=0.1003 nr=silica
|
||||||
|
|
||||||
|
space space_laser_PR laser.p1 PR_AR.p1 L=11.67
|
||||||
|
|
||||||
|
# Beamsplitter
|
||||||
|
|
||||||
|
beamsplitter BS T=0.5012 L=37.5e-6 alpha=-45.0
|
||||||
|
|
||||||
|
space space_PR_BS PR.p2 BS.p1 L=5.925
|
||||||
|
|
||||||
|
# North arm
|
||||||
|
|
||||||
|
mirror NI_AR T=9.9968e-1 L=0.0 Rc=-1420 phi=NI.phi
|
||||||
|
mirror NI T=0.01377 L=37.5e-6 Rc=-1420
|
||||||
|
space inside_NI NI_AR.p2 NI.p1 L=0.2 nr=silica
|
||||||
|
|
||||||
|
mirror NE T=4.4e-6 L=37.5e-6 Rc=1683
|
||||||
|
mirror NE_AR R=100e-6 L=1.33e-4 phi=NE.phi Rc=1683
|
||||||
|
space inside_NE NE.p2 NE_AR.p1 L=0.2 nr=silica
|
||||||
|
|
||||||
|
space space_NI_NE NI.p2 NE.p1 L=2999.8
|
||||||
|
|
||||||
|
space space_BS_NI BS.p3 NI_AR.p1 L=length_PRCL-space_PR_BS.L+schnupp_asymetry/2-inside_NI.L*inside_NI.nr
|
||||||
|
|
||||||
|
# West arm
|
||||||
|
|
||||||
|
mirror WI_AR T=9.99942e-1 L=0.0 Rc=-1424 phi=WI.phi
|
||||||
|
mirror WI T=0.01377 L=37.5e-6 Rc=-1420
|
||||||
|
space inside_WI WI_AR.p2 WI.p1 L=0.2 nr=silica
|
||||||
|
|
||||||
|
mirror WE T=4.3e-6 L=37.5e-6 Rc=1683
|
||||||
|
mirror WE_AR T=9.99845e-1 L=1.55e-4 phi=WE.phi Rc=1683
|
||||||
|
space inside_WE WE.p2 WE_AR.p1 L=0.2 nr=silica
|
||||||
|
|
||||||
|
space space_WI_WE WI.p2 WE.p1 L=2999.8
|
||||||
|
|
||||||
|
# schnupp asymetry correction
|
||||||
|
space space_BS_WI BS.p2 WI_AR.p1 L=length_PRCL-space_PR_BS.L-schnupp_asymetry/2-inside_WI.L*inside_NI.nr
|
||||||
|
|
||||||
|
# SR
|
||||||
|
|
||||||
|
mirror SR T=0.40 L=37.5e-6 Rc=1430
|
||||||
|
mirror SR_AR T=9.99859e-1 L=1.41e-4 Rc=1430 phi=SR.phi
|
||||||
|
space inside_SR SR.p2 SR_AR.p1 L=0.1004
|
||||||
|
|
||||||
|
space space_BS_SR BS.p4 SR.p1 L=length_SRCL-(space_BS_WI.L+space_BS_NI.L)/2
|
||||||
|
|
||||||
|
# power detectors
|
||||||
|
|
||||||
|
power_detector_dc NE_p1 NE.p1.o
|
||||||
|
power_detector_dc NI_p1 NI.p1.o
|
||||||
|
power_detector_dc NE_p2 NE.p2.o
|
||||||
|
power_detector_dc NI_p2 NI.p2.o
|
||||||
|
|
||||||
|
power_detector_dc WE_p1 WE.p1.o
|
||||||
|
power_detector_dc WI_p1 WI.p1.o
|
||||||
|
power_detector_dc WE_p2 WE.p2.o
|
||||||
|
power_detector_dc WI_p2 WI.p2.o
|
||||||
|
|
||||||
|
power_detector_dc PR_p1 PR.p1.o
|
||||||
|
power_detector_dc PR_p2 PR.p2.o
|
||||||
|
|
||||||
|
power_detector_dc SR_p1 SR.p1.o
|
||||||
|
power_detector_dc SR_p2 SR.p2.o
|
||||||
|
|
||||||
|
# degree of freedom
|
||||||
|
|
||||||
|
degree_of_freedom NORTH_ARM NE.dofs.z +1
|
||||||
|
degree_of_freedom WEST_ARM WE.dofs.z +1
|
||||||
|
degree_of_freedom PRCL PR.dofs.z +1
|
||||||
|
degree_of_freedom SRCL SR.dofs.z +1
|
||||||
|
|
||||||
|
degree_of_freedom MICH NI.dofs.z -0.5 WI.dofs.z +0.5 NE.dofs.z -0.5 WE.dofs.z +0.5
|
||||||
|
degree_of_freedom DARM NE.dofs.z -0.5 WE.dofs.z +0.5
|
||||||
|
degree_of_freedom CARM NE.dofs.z +1 WE.dofs.z +1
|
||||||
|
degree_of_freedom DARM_Fz NE.dofs.F_z -0.5 WE.dofs.F_z +0.5
|
||||||
|
|
||||||
|
# cavities
|
||||||
|
|
||||||
|
cavity north_arm NI.p2.o priority=3
|
||||||
|
cavity west_arm WI.p2.o priority=3
|
||||||
|
|
||||||
|
cavity PRCL_north PR.p2.o via=WE.p1.i priority=2
|
||||||
|
cavity PRCL_west PR.p2.o via=NE.p1.i priority=2
|
||||||
|
|
||||||
|
cavity SRCL_north SR.p1.o via=WE.p1.i priority=1
|
||||||
|
cavity SRCL_west SR.p1.o via=WE.p1.i priority=1
|
||||||
|
|
||||||
|
# benches
|
||||||
|
|
||||||
|
mirror SNEB R=1e-6 L=0.0 Rc=1683+7
|
||||||
|
mirror SWEB R=1e-6 L=0.0 Rc=1683+7
|
||||||
|
mirror SDB1 R=1e-6 L=0.0 Rc=1430
|
||||||
|
|
||||||
|
space space_NE_AR_SNEB NE_AR.p2 SNEB.p1 L=7
|
||||||
|
space space_WE_AR_SWEB WE_AR.p2 SWEB.p1 L=7
|
||||||
|
space space_SR_SDB1 SR_AR.p2 SDB1.p1 L=0
|
||||||
|
|
||||||
|
power_detector_dc SNEB_DC SNEB.p1.i
|
||||||
|
power_detector_dc SWEB_DC SWEB.p1.i
|
||||||
|
power_detector_dc SDB1_DC SDB1.p1.i
|
||||||
|
|
||||||
|
degree_of_freedom SNEB_z SNEB.dofs.z -1
|
||||||
|
degree_of_freedom SWEB_z SWEB.dofs.z -1
|
||||||
|
degree_of_freedom SDB1_z SDB1.dofs.z -1
|
||||||
|
|
||||||
|
# Suspensions
|
||||||
|
|
||||||
|
pendulum NI_suspension NI.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum NI_AR_suspension NI_AR.mech mass=NI_suspension.mass fz=NI_suspension.fz Qz=NI_suspension.Qz
|
||||||
|
|
||||||
|
pendulum NE_suspension NE.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum NE_AR_suspension NE_AR.mech mass=NE_suspension.mass fz=NE_suspension.fz Qz=NE_suspension.Qz
|
||||||
|
|
||||||
|
pendulum WI_suspension WI.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum WI_AR_suspension WI_AR.mech mass=WI_suspension.mass fz=WI_suspension.fz Qz=WI_suspension.Qz
|
||||||
|
|
||||||
|
pendulum WE_suspension WE.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum WE_AR_suspension WE_AR.mech mass=WE_suspension.mass fz=WE_suspension.fz Qz=WE_suspension.Qz
|
||||||
|
|
||||||
|
pendulum PR_suspension PR.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum PR_AR_suspension PR_AR.mech mass=PR_suspension.mass fz=PR_suspension.fz Qz=PR_suspension.Qz
|
||||||
|
|
||||||
|
pendulum SR_suspension SR.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum SR_AR_suspension SR_AR.mech mass=SR_suspension.mass fz=SR_suspension.fz Qz=SR_suspension.Qz
|
||||||
|
|
||||||
|
pendulum SNEB_suspension SNEB.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum SWEB_suspension SWEB.mech mass=42 fz=0.76 Qz=40
|
||||||
|
pendulum SDB1_suspension SDB1.mech mass=42 fz=0.76 Qz=40
|
||||||
|
|
||||||
|
free_mass BS_suspension BS.mech mass=34
|
45
pyproject.toml
Normal file
45
pyproject.toml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
[project]
|
||||||
|
name = "backscattering_simulation"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = [{ name = "linarphy", email = "linarphy@linarphy.net" }]
|
||||||
|
description = "Generate simulation data used to measure backscattered light in the Virgo interferometer"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">3.11"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||||
|
"Intended Audience :: Science/Research",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Topic :: Scientific/Engineering",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"backscattering-simulation-data",
|
||||||
|
"finesse>=3.0a33",
|
||||||
|
"gitpython>=3.1.44",
|
||||||
|
"gwpy>=3.0.10",
|
||||||
|
"h5py>=3.14.0",
|
||||||
|
"rich>=14.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 72
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
docstring-code-format = true
|
||||||
|
|
||||||
|
[tool.basedpyright]
|
||||||
|
typeCheckingMode = "recommended"
|
||||||
|
allowedUntypedLibraries = ["gwpy", "h5py", "astropy", "finesse"]
|
||||||
|
reportUnknownMemberType = false
|
||||||
|
reportUnknownVariableType = false
|
||||||
|
reportUnknownArgumentType = false
|
||||||
|
reportAny = false
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
backscattering-simulation-data = { git = "https://git.ligo.org/augustin.demagny/backscattering_simulation_data.git" }
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = ["basedpyright>=1.28.4", "ruff>=0.11.2"]
|
105
utils.py
Normal file
105
utils.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from finesse.model import Model, Port, SeriesSolution, DegreeOfFreedom
|
||||||
|
from finesse.components import SignalGenerator
|
||||||
|
from finesse.detectors import PowerDetectorDemod1
|
||||||
|
from finesse.analysis.actions.axes import Xaxis, Noxaxis
|
||||||
|
|
||||||
|
from numpy.typing import NDArray
|
||||||
|
from numpy import float64
|
||||||
|
|
||||||
|
|
||||||
|
def typingfix_seriessolution(
|
||||||
|
solution: None | SeriesSolution | tuple[Any],
|
||||||
|
) -> SeriesSolution:
|
||||||
|
if solution is None:
|
||||||
|
raise ValueError("Result of the simulation is None")
|
||||||
|
if isinstance(solution, tuple):
|
||||||
|
raise ValueError("Result is not in the expected format")
|
||||||
|
return solution
|
||||||
|
|
||||||
|
|
||||||
|
def fix_dark_fringe(model: Model, power: float) -> tuple[int, float]:
|
||||||
|
solution: SeriesSolution = typingfix_seriessolution(
|
||||||
|
model.run(Noxaxis())
|
||||||
|
)
|
||||||
|
if "B1_DC" not in solution.outputs:
|
||||||
|
raise Exception("Model does not contain B1 readout")
|
||||||
|
|
||||||
|
if type(model.get("DARM")) is DegreeOfFreedom:
|
||||||
|
result = solution["B1_DC"]
|
||||||
|
start, stop, nb = 0, 1, 0
|
||||||
|
while (abs(result - power) > 1e-6) and (nb < 500):
|
||||||
|
nb += 1
|
||||||
|
temp = start + (stop - start) / 2
|
||||||
|
|
||||||
|
model.get("DARM").DC = temp
|
||||||
|
solution: SeriesSolution = typingfix_seriessolution(
|
||||||
|
model.run(Noxaxis())
|
||||||
|
)
|
||||||
|
if "B1_DC" not in solution.outputs:
|
||||||
|
raise Exception("Model do not contains B1 readout")
|
||||||
|
result = solution["B1_DC"]
|
||||||
|
if result > power:
|
||||||
|
stop = temp
|
||||||
|
else:
|
||||||
|
start = temp
|
||||||
|
return nb, solution["B1_DC"]
|
||||||
|
raise Exception("DARM is not a degree of freedom in the model")
|
||||||
|
|
||||||
|
|
||||||
|
def compute_TF(
|
||||||
|
model: Model,
|
||||||
|
frequencies: NDArray[float64],
|
||||||
|
inputs: list[Port | str],
|
||||||
|
) -> SeriesSolution:
|
||||||
|
model.fsig.f = 1 # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
||||||
|
for input in inputs:
|
||||||
|
model.add(SignalGenerator("sig1", model.get(input), 1, 0))
|
||||||
|
model.add(
|
||||||
|
PowerDetectorDemod1(
|
||||||
|
"TF", model.get("SDB1").p2.o, f=model.get("fsig").f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
model.get("TF").select_mask(exclude=(0, 0))
|
||||||
|
return model.run(
|
||||||
|
Xaxis(
|
||||||
|
model.fsig.f, # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
"log",
|
||||||
|
min(frequencies),
|
||||||
|
max(frequencies),
|
||||||
|
frequencies.shape[0] - 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TF:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model: Model,
|
||||||
|
inputs: list[Port | str],
|
||||||
|
outputs: list[Port | str],
|
||||||
|
frequencies: NDArray[float64],
|
||||||
|
):
|
||||||
|
self.model = model.deepcopy()
|
||||||
|
self.inputs = inputs
|
||||||
|
self.outputs = outputs
|
||||||
|
self.frequencies = frequencies
|
||||||
|
self.result: SeriesSolution | None = None
|
||||||
|
self.compute()
|
||||||
|
|
||||||
|
# alias
|
||||||
|
self.f = self.frequencies
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
solution = compute_TF(self.model, self.frequencies, self.inputs)
|
||||||
|
self.result = solution
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get(self, output: Port | str):
|
||||||
|
if self.result is None:
|
||||||
|
raise Exception(
|
||||||
|
"TF need to be computed before getting result"
|
||||||
|
)
|
||||||
|
return self.result[output]
|
Loading…
Reference in a new issue