Commit ecf4924a authored by Daniel Brown's avatar Daniel Brown
Browse files

Merge branch 'develop' of git.ligo.org:finesse/finesse3 into develop

parents c096135a 23f82dff
......@@ -6,9 +6,8 @@ This file is part of the interferometer simulation Finesse 3
<http://www.gwoptics.org/finesse>
Copyright (C) 2019 onwards Andreas Freise, Daniel Brown,
Philip Jones, Sam Rowlinson and Sean Leavey, with
contributions to earlier versions of Finesse from Paul
Cochrane, and Gerhard Heinzel.
Philip Jones, Sam Rowlinson and Sean Leavey. Other contributions
to the Finesse project are linked in the documentation.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......
......@@ -17,6 +17,7 @@ Author: Sean Leavey
import sys
import argparse
import inspect
from textwrap import indent as _indent, wrap
from pathlib import Path
from importlib import import_module
import warnings
......@@ -24,7 +25,11 @@ from numpydoc.docscrape import FunctionDoc, ClassDoc
import finesse
indent = lambda text: _indent("\n".join(wrap(str(text))) + "\n", prefix=" " * 4)
FINESSE_ROOT = Path(finesse.__file__.replace("__init__.py", ""))
DOCURL = "https://finesse.docs.ligo.org/finesse3/developer/documenting.html#writing-sphinx-compatible-docstrings"
def check_module(path):
......@@ -47,52 +52,124 @@ def check_module(path):
has_issue = False
# Categorise members.
classes = []
functions = []
for _, obj in inspect.getmembers(module):
if not inspect.isclass(obj):
try:
if obj.__module__ != module.__name__:
# Reject imported modules.
continue
except AttributeError:
# This is not a class or function; maybe a dict or something else defined on
# the top level that we don't care about.
continue
if obj.__module__ != module.__name__:
# Reject imported modules.
if inspect.isclass(obj):
classes.append(obj)
elif inspect.isfunction(obj):
functions.append(obj)
else:
warnings.warn(f"don't know how to handle member {repr(obj)}")
continue
clsobj = f"{module_path}::{obj.__name__}"
initmeth = None
try:
if obj.__init__.__module__ == obj.__module__:
# There is an init method.
initmeth = obj.__init__
except AttributeError:
# Probably a C parent.
pass
for class_ in classes:
classname = f"{module_path}::{class_.__name__}"
# Parse docstring.
initdoc = None
with warnings.catch_warnings(record=True) as warnlist:
# Import classdoc so that it triggers warnings caught by the context manager.
# Create a ClassDoc object so that we can grab any warnings issued by
# numpydoc.
try:
ClassDoc(obj)
if initmeth:
initdoc = FunctionDoc(initmeth)
ClassDoc(class_)
except Exception as e:
print(f"error while processing docstring for {obj}: {e}")
print(
f"error while processing docstring for {class_.__name__}: \n{indent(e)}"
)
has_issue = True
# Print any caught warnings.
if warnlist:
for wrng in warnlist:
print(f"{clsobj}: numpydoc warning: {wrng.message}")
print(f"{classname}: numpydoc warning: {wrng.message}")
has_issue = True
if initdoc and initdoc.get("Parameters"):
print(
f"{clsobj}: constructor parameters should be documented in the class "
"docstring, not __init__ (see https://finesse.readthedocs.io/en/latest/developer/documenting.html#writing-sphinx-compatible-docstrings)."
)
# Create FunctionDoc objects for each of the class methods to grab any
# warnings issued by numpydoc.
for _, method in inspect.getmembers(class_):
try:
methodname = method.__name__
methodmodule = method.__module__
except AttributeError:
# Maybe a property?
try:
methodname = method.fget.__name__
methodmodule = method.fget.__module__
except AttributeError:
continue
if methodmodule is not class_.__module__:
# This is an inherited member, which we'll skip.
continue
if methodname.startswith("__"):
# Skip dunder methods.
continue
methodpath = f"{classname}.{methodname}"
with warnings.catch_warnings(record=True) as warnlist:
try:
methoddoc = FunctionDoc(method)
except Exception as e:
print(
f"error while processing docstring for "
f"{class_.__name__}.{method.__name__}:\n{indent(e)}"
)
has_issue = True
else:
# Detect if the method is the current class's init method.
try:
isinit = method is class_.__init__
except AttributeError:
# Probably a C parent.
isinit = False
if isinit:
# Check whether the __init__ documentation incorrectly lists the
# init parameters.
if methoddoc and methoddoc.get("Parameters"):
print(
f"{methodpath}: constructor parameters should be "
f"documented in the class docstring, not __init__ "
f"(see {DOCURL})."
)
has_issue = True
# Print any caught warnings.
if warnlist:
for wrng in warnlist:
print(f"{methodpath}: numpydoc warning: {wrng.message}")
has_issue = True
for function in functions:
functionname = f"{module_path}::{function.__name__}"
has_issue = True
with warnings.catch_warnings(record=True) as warnlist:
try:
FunctionDoc(function)
except Exception as e:
print(f"error while processing docstring for {function}: \n{indent(e)}")
has_issue = True
# Print any caught warnings.
if warnlist:
for wrng in warnlist:
print(f"{functionname}: numpydoc warning: {wrng.message}")
has_issue = True
return 1 if has_issue else 0
......
......@@ -822,7 +822,7 @@ class RunLocks(Action):
if self.exception_on_fail:
raise Exception("Locks failed: max iterations reached")
else:
LOGGER.warn("Locks failed")
LOGGER.warning("Locks failed")
for lock, value in zip(locks, initial_parameters):
lock.feedback.value = value
return sol
......@@ -1327,7 +1327,7 @@ class SensingMatrixSolution(BaseSolution):
readouts=None,
tablefmt="pandas",
floatfmt=".2G",
highlight="dof",
highlight=None,
highlight_color="#FFD54F",
):
"""Displays a HTML table of the sensing matrix, with the largest absolute value
......@@ -1350,15 +1350,15 @@ class SensingMatrixSolution(BaseSolution):
falling back to 'html'.
floatfmt : str, optional
Format to print numbers in, defaults to '.2G'.
highlight : str, optional
highlight : str or None, optional
Either 'dof' to highlight the readout that gives the largest
output for each dof, or 'readout' to highlight the dof for
which each readout gives the largest output. Defaults to
'dof'.
None (no highlighting).
highlight_color : str, optional
Color to highlight the maximum values with, or None for no
highlighting. Pandas is required for this to have an effect.
Defaults to pale orange.
Color to highlight the maximum values with. Pandas is
required for this to have an effect. Defaults to pale
orange.
"""
from IPython.display import display
......@@ -1379,19 +1379,21 @@ class SensingMatrixSolution(BaseSolution):
"",
)
B = pd.DataFrame(B, index=dofs, columns=readouts)
if highlight == "dof":
axis = 1
style = B.style.apply(highlight_max, axis=1)
elif highlight == "readout":
axis = 0
style = B.style.apply(highlight_max, axis=0)
elif highlight is None:
style = B.style
else:
raise ValueError(
"Argument 'highlight' must be one of 'dof' or 'readout'."
"Argument 'highlight' must be one of 'dof', 'readout' or None."
)
B = pd.DataFrame(B, index=dofs, columns=readouts)
display(
B.style.apply(highlight_max, axis=axis).format("{:" + floatfmt + "}")
)
display(style.format("{:" + floatfmt + "}"))
elif tablefmt == "html":
display(
tabulate(
......@@ -1739,7 +1741,8 @@ class OptimizeSolution(BaseSolution):
class Optimize(Action):
"""An action that will optimize the value of `parameter` to either maximize or
minimize the output of `detector`.
minimize the output of `detector`. Extra keyword arguments are passed on to
`scipy.optimize.minimize`.
Parameters
----------
......@@ -1778,6 +1781,7 @@ class Optimize(Action):
max_iterations=10000,
accuracy=None,
name="maximize",
**kwargs,
):
super().__init__(name)
self.detector = detector
......@@ -1787,6 +1791,7 @@ class Optimize(Action):
self.kind = kind
self.max_iterations = max_iterations
self.accuracy = accuracy
self.kwargs = kwargs
def _do(self, state):
from scipy.optimize import minimize
......@@ -1835,9 +1840,10 @@ class Optimize(Action):
func,
np.array([param.value]),
tol=self.accuracy,
bounds=[self.bounds],
bounds=[self.bounds] if self.bounds else None,
options={"maxiter": self.max_iterations},
callback=callback,
**self.kwargs,
)
if self.offset == 0:
print(
......@@ -1856,7 +1862,7 @@ class Optimize(Action):
class Minimize(Optimize):
"""An action that will optimize the value of `parameter` to minimize the output of
`detector`.
`detector`. Extra keyword arguments are passed on to `scipy.optimize.minimize`.
Parameters
----------
......@@ -1888,7 +1894,7 @@ class Minimize(Optimize):
class Maximize(Optimize):
"""An action that will optimize the value of `parameter` to maximize the output of
`detector`.
`detector`. Extra keyword arguments are passed on to `scipy.optimize.minimize`.
Parameters
----------
......@@ -1940,7 +1946,7 @@ class NoiseProjectionSolution(BaseSolution):
ax.legend((*np.array(self.noises)[noises_to_plot], "Total"))
ax.set_ylim(*rng)
else:
LOGGER.warn("No noise data to plot in this solution")
LOGGER.warning("No noise data to plot in this solution")
ax.set_ylabel(
f"ASD [{output_node if not self.scaling else self.scaling}/$\\sqrt{{\\mathrm{{Hz}}}}$]"
......
......@@ -497,7 +497,7 @@ cdef class CCSMatrix:
print(" (col, row) = value")
ccol = -1
longest = np.zeros(2, dtype=np.int)
longest = np.zeros(2, dtype=int)
for i in range(self.__nnz):
if self.col_ptr[ccol+1] == i:
ccol += 1
......@@ -541,7 +541,7 @@ cdef class CCSMatrix:
print(f"Vector {self.name}: neqs={self.num_eqs}")
print(" (row) = value")
longest = np.zeros(2, dtype=np.int)
longest = np.zeros(2, dtype=int)
for i in range(self.num_eqs):
longest[0] = max(longest[0], len(f"({i})"))
longest[1] = max(longest[1], len(f"{rhs[i]}"))
......
......@@ -181,7 +181,7 @@ class Joint(Connector):
def __init__(self, name=None, portA=None, portB=None, connections=None):
if (portA is None) != (portB is None):
LOGGER.warn(
LOGGER.warning(
"Can't construct a joint with only one port "
"connected, ignoring ports."
)
......
......@@ -226,8 +226,8 @@ class Port(Freezable):
predicate : callable(finesse.components.Node) -> bool
A callable that accepts a Node and returns a boolean value
Example
-------
Examples
--------
Selecting a unique output node:
port.get_unique_node(lambda node: not node.is_input)
"""
......
......@@ -53,7 +53,7 @@ class Space(Connector):
def __init__(self, name, portA=None, portB=None, L=0, nr=1.0, gouy_x=0, gouy_y=0):
if (portA is None) != (portB is None):
LOGGER.warn(
LOGGER.warning(
"Can't construct a space with only one port connected; ignoring ports."
)
portA = None
......
......@@ -33,7 +33,7 @@ class Wire(Connector):
def __init__(self, name=None, portA=None, portB=None, delay=0):
if (portA is None) != (portB is None):
LOGGER.warn(
LOGGER.warning(
"Can't construct a wire with only one port "
"connected, ignoring ports."
)
......
......@@ -476,7 +476,7 @@ cdef class QShot0Workspace(DetectorWorkspace):
# carrier frequency, and each signal frequency can only have a maximum of 2 contributing
# carrier frequencies
self.Nf = 2 * self.sim.carrier.optical_frequencies.size
self.demod_vac_contri = np.empty((self.Nf, 2), dtype=np.int)
self.demod_vac_contri = np.empty((self.Nf, 2), dtype=int)
self.demod_f = np.full(self.Nf, np.nan, dtype=np.float64)
self.nsr = nsr
......@@ -604,8 +604,8 @@ cdef class QShotNWorkspace(DetectorWorkspace):
self.num_demod = num_demod + 1
self.Ndm = 2**self.num_demod
self.Nf = self.Ndm * self.sim.carrier.optical_frequencies.size
self.demod_vac_contri = np.empty((self.Nf, self.Ndm), dtype=np.int)
self.demod_vac_contri_phi = np.empty((self.Nf, self.Ndm), dtype=np.int)
self.demod_vac_contri = np.empty((self.Nf, self.Ndm), dtype=int)
self.demod_vac_contri_phi = np.empty((self.Nf, self.Ndm), dtype=int)
self.demod_f = np.full(self.Nf, np.nan, dtype=np.float64)
self.demod_phi = np.full(self.Ndm, np.nan, dtype=np.float64)
......
......@@ -46,8 +46,8 @@ def construct_segment_beat_matrix(
2D Complex matrix of size (N, N) with N=len(mode_index_map) or a
CSRMatrix if requested.
Example
-------
Examples
--------
>>> pdtypes.construct_segment_beat_matrix(model.mode_index_map, pdtype.YSPLIT)
array([[0. +0.j, 0.79788456+0.j, 0. +0.j],
[0.79788456+0.j, 0. +0.j, 0. +0.j],
......
......@@ -1814,7 +1814,7 @@ class Model:
The frequency to add.
"""
if freq in self.__frequencies:
LOGGER.warn(f"Frequency {freq.name} already added to model")
LOGGER.warning(f"Frequency {freq.name} already added to model")
else:
self.__frequencies.add(freq)
......
......@@ -453,7 +453,7 @@ cdef class Parameter:
raise ModelParameterSelfReferenceError(value, self)
if self.__is_tunable and self.state == ParameterState.Symbolic:
LOGGER.warn(
LOGGER.warning(
f"{repr(self)}: Setting `is_tunable` to False as new "
"value is symbolic, is_tunable was previously True."
)
......
......@@ -127,7 +127,7 @@ def ws_phase_space(
if fig is None:
fig = plt.figure()
if ax is not None:
LOGGER.warn(
LOGGER.warning(
"Axes have been specified without figure. Axes will be ignored."
)
ax = None
......@@ -136,7 +136,7 @@ def ws_phase_space(
if len(fig.axes) == 0:
ax = fig.add_subplot()
else:
LOGGER.warn("Axes not specified, using first pair.")
LOGGER.warning("Axes not specified, using first pair.")
ax = fig.axes[0]
if contour_kwargs is None:
......
......@@ -54,7 +54,7 @@ def init(mode="display", dpi=None, fmts=None):
try:
ipy.magic("matplotlib qt")
except KeyError:
LOGGER.warn(
LOGGER.warning(
"Could not set matplotlib backend. Tried inline and Qt."
)
finally:
......@@ -79,9 +79,10 @@ def init(mode="display", dpi=None, fmts=None):
mpl.rcParams.update({"savefig.dpi": dpi})
# always apply white background and remove transparency. Make plots readable
# when using dark background/themes in jupyter notebooks and such
mpl.rcParams.update({
"figure.facecolor": (1.0, 1.0, 1.0, 1.0), # red with alpha = 30%
})
mpl.rcParams.update(
{"figure.facecolor": (1.0, 1.0, 1.0, 1.0),} # red with alpha = 30%
)
def add_colorbar(im, **kwargs):
"""Adds a vertical color bar to an image plot.
......
......@@ -192,7 +192,9 @@ class KatParser:
raise KatParserError(errors, text)
for warning in lexer.warnings:
LOGGER.warn(f"{warning[1]}:{find_column(text, warning[2])}: {warning[0]}")
LOGGER.warning(
f"{warning[1]}:{find_column(text, warning[2])}: {warning[0]}"
)
return parser
......@@ -1743,9 +1745,9 @@ class _KatYACC(Parser):
@_("FTBLOCK_START STRING")
def instruction(self, p):
if self.block is not None:
LOGGER.warn(f"Already in FTblock {self.block}")
LOGGER.warning(f"Already in FTblock {self.block}")
if p.STRING in self.blocks:
LOGGER.warn(f"Duplicate FTblock {p.STRING}")
LOGGER.warning(f"Duplicate FTblock {p.STRING}")
self.block = p.STRING
self.blocks[self.block] = self._default_components()
......
......@@ -1004,13 +1004,13 @@ cdef class CarrierSignalMatrixSimulation:
# refs = gc.get_referrers(_ref)
# Nref = len(refs)
# if Nref > 0:
# LOGGER.warn(
# LOGGER.warning(
# f"Something other than the Simulation object (N={Nref}) is keeping"
# f" a reference to the matrix object {repr(self._M)}."
# " Could lead to excessive memory usage if not released."
# )
# for _ in refs:
# LOGGER.warn(f" - {repr(_)}")
# LOGGER.warning(f" - {repr(_)}")
#del _ref
......
......@@ -63,40 +63,34 @@ def names_to_nodes(model, names, default_hints=()):
- `input` : try and select a singular input node
- `output` : try and select a singular output node
Example
-------
An example selecting various nodes from a model with and without
hinting.
import finesse
from finesse.utilities.components import names_to_nodes
model = finesse.Model()
model.parse('''
l l1 P=100k
mod mod1 f=10M midx=0.1 order=1 mod_type=pm
m m1 R=1-&m1.T T=0.014 Rc=-1984 xbeta=0n
m m2 R=1 T=0 Rc=2245 xbeta=0n phi=0
link(l1, mod1, m1, 3994, m2)
dof DARM m1.dofs.z -1 m2.dofs.z +1
''')
names_to_nodes(model, ('m1.p1', 'DARM.AC'), default_hints=('output'))
>>> [<OpticalNode m1.p1.o @ 0x7f92c9a5c880>,
Examples
--------
Selecting various nodes from a model with and without hinting:
>>> import finesse
>>> from finesse.utilities.components import names_to_nodes
>>> model = finesse.Model()
>>> model.parse('''
... l l1 P=100k
... mod mod1 f=10M midx=0.1 order=1 mod_type=pm
... m m1 R=1-&m1.T T=0.014 Rc=-1984 xbeta=0n
... m m2 R=1 T=0 Rc=2245 xbeta=0n phi=0
... link(l1, mod1, m1, 3994, m2)
... dof DARM m1.dofs.z -1 m2.dofs.z +1
... ''')
>>> names_to_nodes(model, ('m1.p1', 'DARM.AC'), default_hints=('output'))
[<OpticalNode m1.p1.o @ 0x7f92c9a5c880>,
<ElectricalNode DARM.AC.o @ 0x7f92c9a0e5b0>]
names_to_nodes(model, ('m1.p1', 'DARM.AC'), default_hints=('input'))
>>> [<OpticalNode m1.p1.i @ 0x7f92c9a5ca60>,
>>> names_to_nodes(model, ('m1.p1', 'DARM.AC'), default_hints=('input'))
[<OpticalNode m1.p1.i @ 0x7f92c9a5ca60>,
<ElectricalNode DARM.AC.i @ 0x7f92c9a0e460>]
names_to_nodes(model, ('m1.p1.o', 'DARM.AC.i'))
>>> [<OpticalNode m1.p1.o @ 0x7f92c9a5c880>,
>>> names_to_nodes(model, ('m1.p1.o', 'DARM.AC.i'))
[<OpticalNode m1.p1.o @ 0x7f92c9a5c880>,
<ElectricalNode DARM.AC.i @ 0x7f92c9a0e460>]
Hints do not insist on a particular output, for example this is valid
Hints do not insist on a particular output. For example, this is valid:
names_to_nodes(model, ('m1.p1.o', ('DARM.AC.o', "input")))
>>> names_to_nodes(model, ('m1.p1.o', ('DARM.AC.o', "input")))
"""
from finesse.components import Port, Node, DegreeOfFreedom
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment