167
Python Kontrol Library Release 1.1.0 Mar 31, 2022

Python Kontrol Library

  • Upload
    others

  • View
    13

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Python Kontrol Library

Python Kontrol LibraryRelease 1.1.0

Mar 31, 2022

Page 2: Python Kontrol Library
Page 3: Python Kontrol Library

Contents:

1 Features 31.1 Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.2.1 Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2.1.1 Required . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2.1.2 Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.2.2 Install from source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2.3 Tutorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.3 Tutorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.3.1 Complementary Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.3.1.1 Complementary Filter Synthesis using ℋ∞ methods . . . . . . . . . . . . . . . . . 51.3.1.2 Complementary Filter Design Typical KAGRA LVDTs and Geophones using ℋ∞

Synthesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3.2 Spectral Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

1.3.2.1 Noise Estimation using Correlation Methods . . . . . . . . . . . . . . . . . . . . . 111.3.2.1.1 Two-channel method . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.3.2.1.2 Three-channel correlation method . . . . . . . . . . . . . . . . . . . . . 16

1.3.2.2 Time Series Simulation from an Amplitude Spectral Density . . . . . . . . . . . . 181.3.3 Sensing Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

1.3.3.1 Sensing Matrix Diagonalization . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221.3.3.2 Optical Lever Sensing Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

1.3.4 Foton Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311.3.4.1 Converting Python TransferFunction objects to Foton expression . . . . . . . . . . 31

1.3.5 Ezca Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341.3.5.1 Read and Write Matrices using Kontrol’s EZCA Wrapper . . . . . . . . . . . . . . 34

1.3.6 Curve Fitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351.3.6.1 Fitting a Polynomial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351.3.6.2 Fitting a transfer function with CurveFit and TransferFunctionModel . . . . . . . . 371.3.6.3 Fitting transfer function with a ZPK model . . . . . . . . . . . . . . . . . . . . . . 431.3.6.4 Fitting the transfer function of a coupled oscillator . . . . . . . . . . . . . . . . . . 471.3.6.5 Fitting the KAGRA Typical LVDT and and Geophone noise with Transfer Function 52

1.3.7 Control Regulator Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581.3.7.1 Critical Damping Regulator Design for Oscillator-like Systems . . . . . . . . . . . 581.3.7.2 Algorithmic Control Design for Oscillator-Like Systems . . . . . . . . . . . . . . . 651.3.7.3 Algorithmic Post-Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

1.4 Main Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

i

Page 4: Python Kontrol Library

1.4.1 Complementary Filter Synthesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711.4.2 Frequency Series Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731.4.3 Sensors and Actuators Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

1.4.3.1 Sensing Matrix Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761.4.3.2 Optical Lever Sensing Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761.4.3.3 Horizontal Optical Lever Sensing Matrices . . . . . . . . . . . . . . . . . . . . . . 791.4.3.4 Vertical Optical Lever Sensing Matrices . . . . . . . . . . . . . . . . . . . . . . . 801.4.3.5 Sensor Calibration Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

1.4.4 Spectral Analysis Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 831.4.5 Foton Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 851.4.6 Curve Fitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

1.4.6.1 Curve fitting class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871.4.6.2 Transfer function fitting class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 881.4.6.3 Models for Curve Fitting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

1.4.7 Control Regulator Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 941.4.7.1 Feedback Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 951.4.7.2 Regulator for Oscillatory Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . 981.4.7.3 Post Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 981.4.7.4 Predefined Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

1.5 Kontrol API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1021.5.1 Subpackages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

1.5.1.1 kontrol.core package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1021.5.1.1.1 kontrol.core.controlutils module . . . . . . . . . . . . . . . . . . . . . . 1021.5.1.1.2 kontrol.core.math module . . . . . . . . . . . . . . . . . . . . . . . . . . 1051.5.1.1.3 kontrol.core.spectral module . . . . . . . . . . . . . . . . . . . . . . . . 1051.5.1.1.4 kontrol.core.foton module . . . . . . . . . . . . . . . . . . . . . . . . . . 108

1.5.1.2 kontrol.complementary_filter package . . . . . . . . . . . . . . . . . . . . . . . . 1101.5.1.2.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101.5.1.2.2 Secondary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

1.5.1.3 kontrol.curvefit package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1141.5.1.3.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1141.5.1.3.2 Secondary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1171.5.1.3.3 Subpackages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

1.5.1.4 kontrol.frequency_series package . . . . . . . . . . . . . . . . . . . . . . . . . . . 1241.5.1.4.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1241.5.1.4.2 Secondary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

1.5.1.5 kontrol.sensact package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1301.5.1.5.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

1.5.1.6 kontrol.regulator package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1391.5.1.6.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1391.5.1.6.2 Secondary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

1.5.1.7 kontrol.transfer_function package . . . . . . . . . . . . . . . . . . . . . . . . . . . 1461.5.1.7.1 Primary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1461.5.1.7.2 Secondary modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

1.5.2 Submodules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.6 Contact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.7 For Developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

1.7.1 Standards and Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.7.1.1 Coding style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.7.1.2 CHANGELOG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.7.1.3 Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1481.7.1.4 Packaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1491.7.1.5 Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

1.7.2 How to Contribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

ii

Page 5: Python Kontrol Library

1.7.2.1 Pending . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1491.7.3 Cheat sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

2 Indices and tables 151

Python Module Index 153

Index 155

iii

Page 6: Python Kontrol Library

iv

Page 7: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Kontrol (also pronounced “control”) is a python package for KAGRA control system related work. It is intented forboth offline and real-time (via Ezca and maybe diaggui and nds2 later) usage. In principle, it should cover all controlrelated topics ranging from sensor/actuator diagonalization to system identification and control filter design.

Contents: 1

Page 8: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

2 Contents:

Page 9: Python Kontrol Library

CHAPTER 1

Features

• Complementary filter synthesis using ℋ∞ methods1.

– Synthesize optimal complementary filters in a 2-sensor configuration.

• Curve fitting

– Fit transfer functions, spectral densities, etc.

• Frequency series modeling (Soon deprecating. See Curve fitting).

– Model-based empirical fitting.

– Model frequency series as zero-pole-gain and transfer function models.

• Sensing/Actuation Matrices.

– Sensing/Actuation Matrices diagonalization with given coupling matrix.

– General optical lever, horizontal and vertical optical lever sensing matrices, using parameters defined inkagra-optical-lever.

• Spectral analysis

– Noise spectral density estimation using 2-channel method2

– Noise spectral density estimation using 3-channel method3

– Time series simulation of a given spectral density.

• Foton utilities.

– Convert Python transfer function objects to Foton expressions

– Support for translating transfer functions with higher than 20 order (the Foton limit).

1 T. T. L. Tsang, T. G. F. Li, T. Dehaeze, C. Collette. Optimal Sensor Fusion Method for Active Vibration Isolation Systems in Ground-BasedGravitational-Wave Detectors. https://arxiv.org/pdf/2111.14355.pdf

2 Aaron Barzilai, Tom VanZandt, and Tom Kenny. Technique for measurement of the noise of a sensor in the presence of large backgroundsignals. Review of Scientific Instruments, 69:2767–2772, 07 1998.

3 R. Sleeman, A. Wettum, and J. Trampert. Three-channel correlation analysis: A new technique to measure instrumental noise of digitizers andseismic sensors. Bulletin of the Seismological Society of America, 96:258–271, 2006.

3

Page 10: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• Easy Channel Access (EZCA) utilities (wrapper)

– Read and write matrices to EPICS record.

• Transfer Function

– Export transfer functions to foton expressions.

– Save TransferFunction objects to pickle files.

• Controller design

– Auto-design of PID controller for oscillatory systems (like pendulum suspensions)

– Auto-design of post-filters such as notch filters and low-pass filters.

Don’t hesitate to check out the tutorials!

• Documentation: https://kontrol.readthedocs.io/

• Repository: https://github.com/terrencetec/kontrol.git

1.1 Concept

Coming soon. . .

1.2 Getting Started

1.2.1 Dependencies

1.2.1.1 Required

• control>=0.9

• numpy

• matplotlib

• scipy

1.2.1.2 Optional

• ezca (Needed for accessing EPICs records/real-time model process variables. Use conda to install it.)

• vishack or dttxml (For extracting data from diaggui xml files.)

If you would like to install Kontrol on your local machine with, then pip should install the required dependenciesautomatically for you. However, if you use Kontrol in a Conda environment, you should install the dependenciesbefore installing Kontrol to avoid using pip. In Conda environment, simply type

conda install -c conda-forge numpy scipy matplotlib control ezca

4 Chapter 1. Features

Page 11: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.2.2 Install from source

For local usage, type

$ git clone https://github.com/terrencetec/kontrol.git$ cd kontrol$ pip install .

For k1ctr workstations, make sure a virtual environment is enabled before installing any packages.

1.2.3 Tutorials

Do check out Tutorials for some example usages inlcuding how to make very good complementary filters.

1.3 Tutorials

Be sure to check out the Main Utilities and Kontrol API sections for detailed documentation.

1.3.1 Complementary Filter

1.3.1.1 Complementary Filter Synthesis using ℋ∞ methods

Here, we will demonstrate how to use kontrol’s ComplementaryFilter method to synthesize filters that optimally com-plementary filters by minimize the super sensor noise.

But, it is not clear what it means by minimizing the noise. Norms, such as 2-norm and infinity-norm, are some wayto characterize the size of a spectrum, or, in fact, a transfer function. 2-norm is analogous to the expected root-mean-square value of a signal while the infinity-norm is analogous to the peak of the spectrum.

Minimizing these norms can be useful when the system is defined properly, and these methods are called H2 andH-infinity methods, which minimizes the corresponding system norms with feedback-controllers that are internallystable. Although these methods are mean for controller synthesis, it can be used to synthesize complementary filtersas well. For details, read Thomas Dehaeze’s Complementary Filters Shaping Using H-Infinity Synthesis.

In this tutorial, we will synthesize a pair of complementary filters using H-infinity methods. H2-methodscan be used but is quite buggy at this stage so we decided not to cover it. Feel free to use kon-trol.complementary_filter.synthesis.h2complementary if you want to. For comparision, we will compare the supersensor noise Sekiguchi’s filter which is part of the predefined filter in kontrol.

Here We will assume that we have modeled the amplitude spectral density of the sensor noises. For this kind ofmodeling, see another Kontrol tutorial Frequency Series Fitting (With Transfer Funtcion Part 2).

[1]: import kontrolimport controlimport numpy as npimport matplotlib.pyplot as pltimport kontrol.frequency_series.noise_modelsimport kontrol.complementary_filter.predefined

omega = np.logspace(-2,2,1000)f = omega/2/np.pi

(continues on next page)

1.3. Tutorials 5

Page 12: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

s = control.tf("s")tf_noise1 = (s/5 + 1) / (s/0.1 + 1)tf_noise2 = 10 * (s/10 + 1)**2 / (s/0.1 + 1)**2

# comp_h2 = kontrol.ComplementaryFilter(noise1=tf_noise1, noise2=tf_noise2, f=omega,→˓unit="omega")comp_hinf = kontrol.ComplementaryFilter(

noise1=tf_noise1, noise2=tf_noise2,weight1=1/tf_noise2, weight2=1/tf_noise1)

# comp_h2.h2synthesis()comp_hinf.hinfsynthesis()

## Let's compare with the Sekiguchi filter.

# The crossover is at around 1Hzsekiguchi_filter1, sekiguchi_filter2 = kontrol.complementary_filter.predefined.→˓sekiguchi([1])sekiguchi_noise_super = kontrol.core.math.quad_sum(

abs(sekiguchi_filter1(1j*omega))*abs(tf_noise1(1j*omega)),abs(sekiguchi_filter2(1j*omega))*abs(tf_noise2(1j*omega))

)

plt.figure(figsize=(12, 4))plt.subplot(121, title="Sensor Noise")plt.loglog(omega, abs(tf_noise1(1j*omega)), label="Noise 1")plt.loglog(omega, abs((tf_noise2)(1j*omega)), label="Noise 2")# plt.loglog(omega, comp_h2.noise_super)plt.loglog(omega, comp_hinf.noise_super(f), color="k", label="Super sensor noise (H-→˓infinity)")plt.loglog(omega, sekiguchi_noise_super, label="Super sensor noise (Sekiguchi)")plt.ylabel("Amplitude Spectral Density")plt.xlabel("Frequency [rad/s]")plt.legend(loc=0)plt.grid(which="both")plt.subplot(122, title="Complementary Filters")# plt.loglog(omega, abs(comp_h2.filter1(1j*omega)))# plt.loglog(omega, abs(comp_h2.filter2(1j*omega)))plt.loglog(omega, abs(comp_hinf.filter1(1j*omega)), label="Filter 1 (H-infinity)")plt.loglog(omega, abs(comp_hinf.filter2(1j*omega)), label="Filter 2 (H-infinity)")plt.loglog(omega, abs(sekiguchi_filter1(1j*omega)), label="Filter 1 (Sekiguchi)")plt.loglog(omega, abs(sekiguchi_filter2(1j*omega)), label="Filter 2 (Sekiguchi)")

plt.ylabel("Amplitude")plt.xlabel("Frequency [rad/s]")plt.legend(loc=0)plt.grid(which="both")

6 Chapter 1. Features

Page 13: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[2]: # Foton ready!comp_hinf.filter1.foton()

[2]: 'zpk([-5.04953;-10.000000+i*0.000286;-10.000000+i*-0.000286;-3.01306e+11],[-1.06242;-→˓4.86719;-5.11225;-3.97246e+07],6.24996e-06,"s")'

1.3.1.2 Complementary Filter Design Typical KAGRA LVDTs and Geophones using ℋ∞ Synthesis

In this tutorial, we will synthesize optimal complementary filters for blending an LVDT and a geophone. The noisemodels are modeled according to KAGRA’s sensors typical noise spectrum and details of the modeling procedurecan found in this tutorial: LVDT and geophone noise modeling here. The noise models are loaded with kontrol.load_transfer_function().

kontrol.ComplementaryFilter is a class for complementary filter synthesis using ℋ2 and ℋ∞ methods. Thedetailed of this method is described in the arXiv article Optimal Sensor Fusion Method for Active Vibration Iso-lation Systems in Ground-Based Gravitational-Wave Detectors. For the purpose of complementary filter synthesis,kontrol.ComplementaryFilter requires minimal specification of noise1 and noise2 in the initializa-tion stage. These are the tranfer function models for the sensor noises. Optional arguments include weight1 andweight2. These are the freqeuncy dependent specifications for noise1 and noise2 respectively. filter1and filter2 can be specified if synthesis function is not need. f can also be specified as the frequency axisin Hz. kontrol.ComplementaryFilter.h2synthesis() and kontrol.ComplementaryFilter.hinfsynthesis() are the corresponding methods for synthesizing the complementary filters of a generalizedplant as:

1.3. Tutorials 7

Page 14: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

noise1, noise2, weight1, and weight2 correspond to ��1(𝑠), ��2(𝑠), 𝑊1(𝑠), and 𝑊2(𝑠) in the figure. 𝐻1(𝑠)is the complementary filter we seek to optimize and it’s refered to filter1 in one of the attributes of kontrol.ComplementaryFilter when a synthesis method is called. The other complementary filter is simply filter2in the attribute. kontrol.ComplementaryFilter.noise_super() is a method that estimate the amplitudespectral density of the super sensor noise.

[1]: # Load and plot the noise models here.import numpy as npimport matplotlib.pyplot as plt

import kontrol

f = np.logspace(-2, 1, 1024)noise1 = kontrol.load_transfer_function("noise_lvdt.pkl")noise2 = kontrol.load_transfer_function("noise_geophone.pkl")

plt.rcParams["font.size"] = 12plt.figure(figsize=(6, 4))plt.loglog(f, abs(noise1(1j*2*np.pi*f)), lw=3, label="Sensor noise 1")plt.loglog(f, abs(noise2(1j*2*np.pi*f)), lw=3, label="Sensor noise 2")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude spectral density ($\mu \rm{m}/\sqrt{\rm{Hz}}$)")plt.xlabel("Frequency (Hz)")plt.show()

8 Chapter 1. Features

Page 15: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[2]: # Make complementary filters here

# Read the paper for details for why these weights are chosenweight1 = 1/noise2weight2 = 1/noise1

comp = kontrol.ComplementaryFilter(noise1, noise2, weight1, weight2, f=f)

# Alternatively, set the attributes directly# comp = kontrol.ComplementaryFilter()# comp.noise1 = noise1# comp.noise2 = noise2# comp.weight1 = 1/noise2# comp.weight2 = 1/noise1

_, _ = comp.hinfsynthesis()

[3]: # Plot the filters hereplt.figure(figsize=(6, 8))plt.subplot(211)plt.loglog(f, abs(comp.filter1(1j*2*np.pi*f)), lw=3, label="Complementary filter 1")plt.loglog(f, abs(comp.filter2(1j*2*np.pi*f)), lw=3, label="Complementary filter 2")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Magnitude")plt.xlabel("Frequency (Hz)")

plt.subplot(212)plt.semilogx(f, 180/np.pi*np.angle(comp.filter1(1j*2*np.pi*f)), lw=3, label=→˓"Complementary filter 1")plt.semilogx(f, 180/np.pi*np.angle(comp.filter2(1j*2*np.pi*f)), lw=3, label=→˓"Complementary filter 2")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (degree)")plt.xlabel("Frequency (Hz)")plt.show()

1.3. Tutorials 9

Page 16: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[4]: # Plot the predicted super sensor noise hereplt.figure(figsize=(6, 4))plt.loglog(f, abs(noise1(1j*2*np.pi*f)), lw=3, label="Sensor noise 1")plt.loglog(f, abs(noise2(1j*2*np.pi*f)), lw=3, label="Sensor noise 2")plt.loglog(f, comp.noise_super(), lw=3, label="Super sensor noise")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude spectral density ($\mu \rm{m}/\sqrt{\rm{Hz}}$)")plt.xlabel("Frequency (Hz)")plt.show()

10 Chapter 1. Features

Page 17: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[6]: # Output to Foton formatprint("Filter 1:\n", comp.filter1.foton(root_location="n"))print("")print("Filter 2:\n", comp.filter2.foton(root_location="n"))

Filter 1:zpk([0.018186+i*0.011488;0.018186+i*-0.011488;0.048464+i*0.049906;0.048464+i*-0.→˓049906;0.244541;0.343148+i*0.100487;0.343148+i*-0.100487;0.660487+i*0.655936;0.→˓660487+i*-0.655936;1.53144;2.75745;9.48737;1.219e+20],[0.0185509;0.024324+i*0.→˓073156;0.024324+i*-0.073156;0.063128+i*0.054238;0.063128+i*-0.054238;0.086811+i*0.→˓027231;0.086811+i*-0.027231;0.0941082;0.460347;0.460394;2.76016;2.76016;2.→˓71844e+07],0.966407,"n")

Filter 2:zpk([0.00560866;0.002725+i*0.009636;0.002725+i*-0.009636;0.0617431;0.0621602;0.→˓133397;0.095793+i*0.104738;0.095793+i*-0.104738;0.4589;0.460372;2.75933;2.76016;2.→˓7174e+07],[0.0185509;0.024324+i*0.073156;0.024324+i*-0.073156;0.063128+i*0.054238;0.→˓063128+i*-0.054238;0.086811+i*0.027231;0.086811+i*-0.027231;0.0941082;0.460347;0.→˓460394;2.76016;2.76016;2.71844e+07],0.0097132,"n")

1.3.2 Spectral Analysis

1.3.2.1 Noise Estimation using Correlation Methods

In this tutorial, we will demonstrate how to use 2-channel and 3-channel correlation methods,kontrol.spectral.two_channel_correlation() and kontrol.spectral.three_channel_correlation(), to esti-mate sensor self noise. Library reference is available here. Description of this method is available in the baselinemethod section of here. We will also use notations in the document.

Let’s say we have three sensors, with readouts 𝑦1(𝑡), 𝑦2(𝑡), and 𝑦3(𝑡). We place them in a position such that they sensea coherent signal

𝑥(𝑡) = ℜ(𝐴𝑒(𝜎+𝑖𝜔0𝑒

𝛾𝑡)𝑡)

,

where 𝑖 is the imaginary number, 𝐴 is 𝐴 is a real number, 𝜎 and 𝛾 are negative real numbers, and 𝜔0 is a positive realnumber.

1.3. Tutorials 11

Page 18: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

The first two sensors have dynamics

𝐻1(𝑠) = 𝐻2(𝑠) =𝑠2

𝑠2+2𝜁𝜔𝑛𝑠+𝜔2𝑛

,

where 𝜁 > 0 and 𝜔𝑛 > 0, and the third sensor has dynamics

𝐻3(𝑠) =𝜔𝑚

𝑠+𝜔𝑚.

The sensors have noise dynamics

𝑁𝑖(𝑠) = 𝐺𝑖(𝑠)𝑊𝑖(𝑠),

where 𝑖 = 1, 2, 3, 𝑊𝑖(𝑠) is white noise with unit amplitude, and 𝐺𝑖(𝑠) is the noise dynamics of the sensors. Here,𝑊𝑖(𝑠)s are uncorrelated. Let’s say

𝐺1(𝑠) = 𝐺2(𝑠) =𝑎1

𝑠+𝜖1and 𝐺3(𝑠) =

𝑎3

(𝑠+𝜖3)2,

where 𝑎1 and 𝑎3 real number, 𝜖1 and 𝜖3 are real numbers, and 𝜖1 ≈ 𝜖3 ≪ 𝜔0.

The readouts are then simply

𝑦𝑖(𝑡) = ℒ−1 {𝑋(𝑠)𝐻𝑖(𝑠) +𝑁𝑖(𝑠)}.

[1]: import controlimport numpy as npimport matplotlib.pyplot as plt

np.random.seed(123)

# Time axis and sampling frequencyfs = 128t0 = 0t_end = 512t = np.arange(t0, t_end, 1/fs)

# The coherent signalA = 1sigma = -.01gamma = -0.1omega_0 = 10*2*np.pix = A*np.exp((sigma + 1j*omega_0*np.exp(gamma*t)) * t).real

# The sensor dynamics.zeta = 1omega_n = 1*2*np.piomega_m = 10s = control.tf("s")H1 = s**2 / (s**2 + 2*zeta*omega_n*s + omega_n**2)H2 = H1H3 = omega_m / (s+omega_m)

# Signals sensed by the sensors._, x1 = control.forced_response(sys=H1, T=t, U=x)_, x2 = control.forced_response(sys=H2, T=t, U=x)_, x3 = control.forced_response(sys=H3, T=t, U=x)

# The noisesw1 = np.random.normal(loc=0, scale=1, size=len(t))w2 = np.random.normal(loc=0, scale=1, size=len(t))w3 = np.random.normal(loc=0, scale=1, size=len(t))

(continues on next page)

12 Chapter 1. Features

Page 19: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

a1 = 0.5a3 =5epsilon_1 = omega_0/100epsilon_3 = omega_0/200G1 = a1 / (s+epsilon_1)G2 = G1G3 = a3 / (s+epsilon_3)**2_, n1 = control.forced_response(sys=G1, T=t, U=w1)_, n2 = control.forced_response(sys=G2, T=t, U=w2)_, n3 = control.forced_response(sys=G3, T=t, U=w3)

# The readoutsy1 = x1 + n1y2 = x2 + n2y3 = x3 + n3

plt.figure(figsize=(15, 5))plt.subplot(121)plt.plot(t, x, label="Coherent signal $x(t)$", lw=3)plt.plot(t, y1, "--", label="Readout $y_1(t)$", lw=1)plt.plot(t, y2, "--", label="Readout $y_2(t)$", lw=1)plt.plot(t, y3, "k--", label="Readout $y_3(t)$", lw=1)plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Ampitude (a.u.)")plt.xlabel("Time (s)")

plt.subplot(122, title="Noises")plt.plot(1,1) # just to shift the colors.plt.plot(t, n1, label="noise in $y_1$")plt.plot(t, n2, label="noise in $y_2$")plt.plot(t, n3, "k", label="noise in $y_3$")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Ampitude (a.u.)")plt.xlabel("Time (s)")

plt.show()

Let’s plot the PSDs

1.3. Tutorials 13

Page 20: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[2]: import scipy.signal

f, P_x = scipy.signal.welch(x, fs=fs)f, P_n1 = scipy.signal.welch(n1, fs=fs)f, P_n2 = scipy.signal.welch(n2, fs=fs)f, P_n3 = scipy.signal.welch(n3, fs=fs)f, P_y1 = scipy.signal.welch(y1, fs=fs)f, P_y2 = scipy.signal.welch(y2, fs=fs)f, P_y3 = scipy.signal.welch(y3, fs=fs)

plt.figure(figsize=(6, 4))plt.loglog(f, P_x, label="Signal $x(t)$", lw=3)plt.loglog(f, P_n1, label="Noise $n_1(t)$", lw=3)plt.loglog(f, P_n2, "--", label="Noise $n_2(t)$", lw=2)plt.loglog(f, P_n3, label="Noise $n_3(t)$", lw=3)plt.loglog(f, P_y1, "k--", label="Readout $y_1(t)$", lw=2)plt.loglog(f, P_y2, "g-.", label="Readout $y_2(t)$", lw=2)plt.loglog(f, P_y3, "b--", label="Readout $y_3(t)$", lw=2)plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-9, 1e-1)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.show()

1.3.2.1.1 Two-channel method

Sensor 1 and sensor 2 has the same dynamics and noise PSD. Let’s see if we can predict the two noises using thetwo-channel correlation method. Here, we will use Kontrol spectral analysis utilities.

[3]: import kontrol

_, coh12 = scipy.signal.coherence(y1, y2, fs=fs)_, coh21 = scipy.signal.coherence(y2, y1, fs=fs) # This is actually the same as coh21

(continues on next page)

14 Chapter 1. Features

Page 21: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

P_n1_2channel = kontrol.spectral.two_channel_correlation(psd=P_y1, coh=coh12)P_n2_2channel = kontrol.spectral.two_channel_correlation(psd=P_y2, coh=coh21)

plt.figure(figsize=(12, 4))plt.subplot(121)plt.loglog(f, P_n1, label="Sensor noise 1")plt.loglog(f, P_n1_2channel, label="Predicted using 2-channel correlation method")plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-7, 1e-3)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.subplot(122)plt.loglog(f, P_n2, label="Sensor noise 2")plt.loglog(f, P_n2_2channel, label="Predicted using 2-channel correlation method")plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-7, 1e-3)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.show()

As can be seen, the 2-channnel method works perfectly in predicting the sensor noises using only the readouts.

Just curious to see what happens if we use sensor 3, which is not the same as sensor 1 and 2, instead.

[4]: _, coh13 = scipy.signal.coherence(y1, y3, fs=fs)_, coh31 = scipy.signal.coherence(y3, y1, fs=fs) # This is actually the same as coh21

P_n1_2channel_from_n3 = kontrol.spectral.two_channel_correlation(psd=P_y1, coh=coh13)P_n3_2channel_from_n1 = kontrol.spectral.two_channel_correlation(psd=P_y3, coh=coh31)

plt.figure(figsize=(12, 4))plt.subplot(121)plt.loglog(f, P_n1, label="Sensor noise 1")plt.loglog(f, P_n1_2channel_from_n3, label="Predicted using 2-channel correlation→˓method but with non-identical sensor") (continues on next page)

1.3. Tutorials 15

Page 22: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.legend(loc=0)plt.grid(which="both")plt.ylim(1e-7, 1e-3)plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.subplot(122)plt.loglog(f, P_n3, label="Sensor noise 3")plt.loglog(f, P_n3_2channel_from_n1, label="Predicted using 2-channel correlation→˓method but with non-identical sensor")plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-7, 1e-3)plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.show()

Interesting, somehow gets the sensor 3 noise more accurately than that of sensor 1. But it could be just a fluke.

1.3.2.1.2 Three-channel correlation method

Now, let’s compute the sensors noise using the three-channel method.

[5]: _, csd12 = scipy.signal.csd(y1, y2, fs=fs)_, csd13 = scipy.signal.csd(y1, y3, fs=fs)_, csd21 = scipy.signal.csd(y2, y1, fs=fs)_, csd23 = scipy.signal.csd(y2, y3, fs=fs)_, csd31 = scipy.signal.csd(y3, y1, fs=fs)_, csd32 = scipy.signal.csd(y3, y2, fs=fs)

# Calculate all three estimations at the same time.# At least provide three independent cross-spectral densities.# But it's recommended to provide all cross-spectral densities.P_n1_3channel, P_n2_3channel, P_n3_3channel = kontrol.spectral.three_channel_→˓correlation(

psd1=P_y1, psd2=P_y2, psd3=P_y3,csd12=csd12, csd13=csd13,csd21=csd21, csd23=csd23,csd31=csd31, csd32=csd32)

(continues on next page)

16 Chapter 1. Features

Page 23: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

# # Alternatively, calculate each estimation one by one with the returnall=False tag.# # Note the changes in the cross-spectral density# P_n1_3channel = kontrol.spectral.three_channel_correlation(# psd1=P_y1, csd13=csd13, csd23=csd23, csd21=csd21, returnall=False)# P_n2_3channel = kontrol.spectral.three_channel_correlation(# psd1=P_y2, csd13=csd21, csd23=csd31, csd21=csd32, returnall=False)# P_n3_3channel = kontrol.spectral.three_channel_correlation(# psd1=P_y3, csd13=csd32, csd23=csd12, csd21=csd13, returnall=False)

plt.figure(figsize=(15, 10))plt.subplot(221)plt.loglog(f, P_y1, label="Readout 1")plt.loglog(f, P_n1, label="Sensor noise 1", lw=3)plt.loglog(f, P_n1_2channel, "--", label="Predicted using 2-channel correlation→˓method.", lw=2)plt.loglog(f, P_n1_3channel, "k-.", label="Predicted using 3-channel correlation→˓method.", lw=2, markersize=3)plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-7, 1e-2)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.subplot(222)plt.loglog(f, P_y2, label="Readout 2",)plt.loglog(f, P_n2, label="Sensor noise 2", lw=3)plt.loglog(f, P_n2_2channel, "--", label="Predicted using 2-channel correlation→˓method.", lw=2)plt.loglog(f, P_n2_3channel, "k-.", label="Predicted using 3-channel correlation→˓method.", lw=2, markersize=3)plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-7, 1e-2)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")

plt.subplot(223)plt.loglog(f, P_y3, label="Readout 3")plt.loglog(f, P_n3, label="Sensor noise 3", lw=3)plt.loglog(f, P_n3_2channel_from_n1, "--", label="2-channel correlation method with→˓sensor 1", lw=2)plt.loglog(f, P_n3_3channel, "k-.", label="Predicted using 3-channel correlation→˓method.", lw=2, markersize=3)plt.legend(loc=0)plt.grid(which="both")# plt.ylim(1e-9, 1e-1)# plt.xlim(0.5, 10)plt.ylabel("Power spectral density (a.u./Hz)")plt.xlabel("Frequency (Hz)")plt.show()

1.3. Tutorials 17

Page 24: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3.2.2 Time Series Simulation from an Amplitude Spectral Density

In this tutorial, we will demonstrate the time series simulation of an amplitude spectral density (ASD) using thefunction kontrol.spectral.asd2ts().

For the ASD, we will use the ASD of a colored noise. The “coloring” was done by passing the white noise through anIIR filter.

𝐻(𝑠) =(𝑠+ 1)2

𝑠2(𝑠+ 10). (1.1)

[1]: import controlimport numpy as npimport matplotlib.pyplot as pltimport scipy.signal

fs = 128 # Sampling frequency (Hz)t_final = 512 # Final time (s)t = np.arange(0, t_final, 1/fs)np.random.seed(123)white_noise = np.random.normal(loc=0, scale=1*np.sqrt(fs/2), size=len(t))

s = control.tf("s")color = (s+1)**2 / (s**2 * (s+10))

averages = 10

(continues on next page)

18 Chapter 1. Features

Page 25: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

window = np.hanning(int(len(white_noise)/averages))f, white_noise_psd = scipy.signal.welch(white_noise, fs=fs, window=window)white_noise_psd = white_noise_psd[f>0]f = f[f>0]white_noise_asd = white_noise_psd**0.5colored_noise_asd = abs(color(1j*2*np.pi*f)) * white_noise_asd# Alternatively, use control.forced_response# t, colored_noise= control.forced_response(color, U=white_noise, T=t)# _, colored_noise_psd = scipy.signal.welch(colored_noise, fs=fs, window=np.→˓hanning(int(len(white_noise)/10)))

plt.rcParams["font.size"] = 14plt.figure(figsize=(6, 4))plt.loglog(f, white_noise_psd**0.5, label="White noise")plt.loglog(f, abs(color(1j*2*np.pi*f)), label="IIR filter $H(j\omega)$")plt.loglog(f, colored_noise_asd, label="Colored noise")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude spectral density $(1/\sqrt{\mathrm{Hz}})$")plt.xlabel("Frequency (Hz)")plt.show()

[2]: import kontrol

# Defaultnp.random.seed(123)t_sim, time_series_sim = kontrol.spectral.asd2ts(colored_noise_asd, f=f)fs_sim = 1/(t_sim[1]-t_sim[0])

# Use a custom time axisnp.random.seed(123) # Use a fixed seed so the time series is the same as the→˓previous onet_new = np.arange(0, t_sim[-1]*10, 1/fs) # Use ten times the lengtht_new, time_series_new = kontrol.spectral.asd2ts(colored_noise_asd, f=f, t=t_new)fs_new = 1/(t_new[1]-t_new[0])

(continues on next page)

1.3. Tutorials 19

Page 26: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

# Calculated the ASD of the simulated time seriesf_sim, psd_sim = scipy.signal.welch(time_series_sim, fs=fs_sim, window=np.→˓hanning(len(t_sim)))f_new, psd_new = scipy.signal.welch(time_series_new, fs=fs_new, window=np.→˓hanning(len(t_sim)))

psd_sim = psd_sim[f_sim>0]psd_new = psd_new[f_new>0]f_sim = f_sim[f_sim>0]f_new = f_new[f_new>0]asd_sim = psd_sim**0.5asd_new = psd_new**0.5

plt.figure(figsize=(8, 12))plt.subplot(211)plt.title("Time series")plt.plot(t_new, time_series_new, color="C1", label="Simulated time series (Custom→˓time axis)")plt.plot(t_sim, time_series_sim, color="C0", label="Simulated time series")

plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Time (s)")

plt.subplot(212)plt.title("Amplitude spectral density")plt.loglog(f_sim, asd_sim, label="ASD from time series")plt.loglog(f_new, asd_new, label="ASD from time series (custom time axis)")plt.loglog(f, colored_noise_asd, label="Original colored noise")# Note that the second one is less noisy because of Welch averaging.

plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude spectral density $1/\sqrt{\rm{Hz}}$")plt.xlabel("Frequency (Hz)")plt.show()

20 Chapter 1. Features

Page 27: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3. Tutorials 21

Page 28: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3.3 Sensing Matrices

1.3.3.1 Sensing Matrix Diagonalization

In this tutorial, we will demonstration the use of kontrol.SensingMatrix class to diagonalize a pair of coupledsensors.

Here, suppose we have two displacements 𝑥1 and 𝑥2, and we have sensing readouts 𝑠1 and 𝑠2. We kicked the systemand let it resonates. 𝑥1 is a damped oscillation at 1 Hz and 𝑥2 is a damped oscillation at 3 Hz. We hard code sensingcoupling 𝑠1 = 𝑥1+0.1𝑥2 and 𝑠2 = −0.2𝑥1+𝑥2. For simplicity, let’s assume that these sesning readouts are obtained

using an initial sensing matrix of Csensing,initial =

[1 00 1

].

We will estimate the coupling ratios from the spectra of 𝑠1 and 𝑠2, and let’s see if we can recover a sensing matrixCsensing such that [𝑥1, 𝑥2]

𝑇= Csensing [𝑠1, 𝑠2]

𝑇 .

[1]: import numpy as npimport matplotlib.pyplot as plt

fs = 1024t_ini = 0t_end = 100t = np.linspace(0, 100, (t_end-t_ini)*fs)np.random.seed(123)x_1_phase = np.random.uniform(0, np.pi)x_2_phase = np.random.uniform(0, np.pi)x_1 = np.real(1.5 * np.exp((-0.1+(2*np.pi*1)*1j) * t + x_1_phase*1j))x_2 = np.real(3 * np.exp((-0.2+(2*np.pi*3)*1j) * t + x_2_phase*1j))s_1 = x_1 + 0.1*x_2s_2 = -0.2*x_1 + x_2

[2]: plt.rcParams.update({"font.size": 14})plt.figure(figsize=(15,5))

plt.subplot(121)plt.plot(t, x_1, label="$x_1$")plt.plot(t,x_2, label="$x_2$")plt.ylabel("Amplitude")plt.xlabel("time [s]")plt.legend(loc=0)

plt.subplot(122)plt.plot(t, s_1, label="$s_1$")plt.plot(t, s_2, label="$s_2$")plt.ylabel("Amplitude")plt.xlabel("time [s]")plt.legend(loc=0)

[2]: <matplotlib.legend.Legend at 0x7f08f60b56d0>

22 Chapter 1. Features

Page 29: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Now, let’s obtain various spectra of the sensor readouts, like how we would use diaggui to obtain spectral densitiesand transfer functions.

[3]: import scipy.signalfs = 1/(t[1]-t[0])f, psd_s_1 = scipy.signal.welch(s_1, fs=fs, nperseg=int(len(s_1)/5))f, psd_s_2 = scipy.signal.welch(s_2, fs=fs, nperseg=int(len(s_2)/5))f, csd_s_12 = scipy.signal.csd(s_1, s_2, fs=fs, nperseg=int(len(s_1)/5))

mask = f>0f = f[mask]psd_s_1 = psd_s_1[mask]psd_s_2 = psd_s_2[mask]csd_s_12 = csd_s_12[mask]

[4]: plt.figure(figsize=(15, 10))

plt.subplot(221)plt.loglog(f, abs(csd_s_12/psd_s_2), label="Transfer function $|s_1/s_2|$")plt.loglog(f, abs(csd_s_12/psd_s_1), label="Transfer function $|s_2/s_1|$")plt.ylabel("Amplitude")plt.xlabel("Frequency [Hz]")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(222)plt.loglog(f, psd_s_1, label="$s_1$")plt.loglog(f, psd_s_2, label="$s_2$")plt.ylabel("Power spectral density [1/Hz]")plt.xlabel("Frequency [Hz]")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(223)plt.semilogx(f, np.angle(csd_s_12/psd_s_2), label=r"Transfer function $\angle\left(s_→˓1/s_2\right)$")plt.semilogx(f, np.angle(csd_s_12/psd_s_1), label=r"Transfer function $\angle\left(s_→˓2/s_1\right)$")plt.ylabel("Phase [rad]")plt.xlabel("Frequency [Hz]")

(continues on next page)

1.3. Tutorials 23

Page 30: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.legend(loc=0)plt.grid(which="both")

Now, we know that the resonance frequencies are at 1 Hz and 3 Hz, so we can safely assume that these frequencies arepurely 𝑥1 and 𝑥2 motion respectively. We see that the transfer functions 𝑠1/𝑠2 and 𝑠2/𝑠1 have flat response at thesefrequencies. These correspond to coupling ratios 𝑠1/𝑥2 (at 3 Hz) and 𝑠2/𝑥1 (at 3 Hz). From the phase response, wesee that the phase between 𝑥2 and 𝑠1 is 0, and that between 𝑥1 and 𝑠2 is at −𝜋, this correspond to a minus sign in thecoupling ratio. Let’s inspect further.

[5]: # f_1hz = f[(f>0.9) & (f<1.1)]# f_3hz = f[(f>2.9) & (f<3.1)]print(r"Coupling ratio $s_1/x_2$", np.mean(abs(csd_s_12/psd_s_2)[(f>2.9) & (f<3.1)]))print(r"Coupling ratio $s_2/x_1$", np.mean(abs(csd_s_12/psd_s_1)[(f>0.9) & (f<1.1)]))print(r"Phase $s_1/x_2$", np.angle(csd_s_12/psd_s_2)[(f>2.9) & (f<3.1)])print(r"Phase $s_2/x_1$", np.angle(csd_s_12/psd_s_1)[(f>0.9) & (f<1.1)])

Coupling ratio $s_1/x_2$ 0.10001284705931585Coupling ratio $s_2/x_1$ 0.19997798568604752Phase $s_1/x_2$ [-8.05189656e-05 -8.56070819e-06 8.71342862e-05 -1.62118769e-04]Phase $s_2/x_1$ [ 3.14151356 -3.14157211 -3.14157723 3.14037366]

Indeed, we find coupling ratios 0.100013 and -0.199978.

Now, we assume the follow:

Ccoupling [𝑥1, 𝑥2]𝑇= Csensing,initial [𝑠1, 𝑠2]

𝑇 , so the coupling matrix Ccoupling is[

1 0.100013−0.199978 1

].

And now let’s use kontrol.SensingMatrix to compute a new sensing matrix.

24 Chapter 1. Features

Page 31: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[6]: import kontrol

c_sensing_initial = np.array([[1, 0], [0, 1]])c_coupling = np.array([[1, 0.100013], [-0.199978, 1]])

sensing_matrix = kontrol.SensingMatrix(matrix=c_sensing_initial, coupling_matrix=c_→˓coupling)## Alternatively,## sensing_matrix = kontrol.SensingMatrix(matrix=c_sensing_initial)## sensing_matrix.coupling_matrix = c_coupling

## Now diagonalizec_sensing = sensing_matrix.diagonalize()## Alternatively## c_sensing = sensing_matrix.diagonalize(coupling_matrix=c_coupling)

print(c_sensing)

[[ 0.98039177 -0.09805192][ 0.19605679 0.98039177]]

Now let’s test the new matrix.

We compute the new sensing readout [𝑠1,new, 𝑠2,new]𝑇

= Csensing [𝑠1, 𝑠2]𝑇 , and then compute the power spectral

densities and compare it with the old ones.

[7]: s_new = c_sensing @ np.array([s_1, s_2])s_1_new = s_new[0]s_2_new = s_new[1]

f, psd_s_1_new = scipy.signal.welch(s_1_new, fs=fs, nperseg=int(len(s_1_new)/5))f, psd_s_2_new = scipy.signal.welch(s_2_new, fs=fs, nperseg=int(len(s_2_new)/5))f, csd_s_12_new = scipy.signal.csd(s_1_new, s_2_new, fs=fs, nperseg=int(len(s_1_new)/→˓5))

mask = f>0f = f[mask]psd_s_1_new = psd_s_1_new[mask]psd_s_2_new = psd_s_2_new[mask]csd_s_12_new = csd_s_12_new[mask]

[8]: plt.figure(figsize=(15, 5))

plt.subplot(121)plt.loglog(f, psd_s_1, label="$s_1$ before")plt.loglog(f, psd_s_1_new, label="$s_1$ diagonalized")plt.ylabel("Power spectral density [1/Hz]")plt.xlabel("Frequency [Hz]")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(122)plt.loglog(f, psd_s_2, label="$s_2$ before")plt.loglog(f, psd_s_2_new, label="$s_2$ diagonalized")plt.ylabel("Power spectral density [1/Hz]")plt.xlabel("Frequency [Hz]")plt.legend(loc=0)

(continues on next page)

1.3. Tutorials 25

Page 32: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.grid(which="both")

As we can see, the couplings have been reduced by many many orders of magnitudes, while the diagonal readoutremains the same.

By the way. kontrol.SensingMatrix class inherit numpy.ndarray, so you can do any numpy array opera-tion on it. For example,

[9]: sensing_matrix + np.random.random(np.shape(sensing_matrix))

[9]: SensingMatrix([[1.22685145, 0.55131477],[0.71946897, 1.42310646]])

1.3.3.2 Optical Lever Sensing Matrices

In this tutorial, we will demonstrate the use of kontrol.OpticalLeverSensingMatrix class, whichinherit kontrol.SensingMatrix. kontrol.OpticalLeverSensingMatrix is a general sens-ing matrix for optical levers with optical lever beams that has tilted incidence plane with respectto the horizontal/vertical plane. Realistically, we won’t be needing this general matrix since opticallevers (in KAGRA) are roughly horizontal or vertical. So, we provide reduced version of kontrol.OpticalLeverSensingMatrix, namely, kontrol.HorizontalOpticalLeverSensingMatrix andkontrol.VerticalOpticalLeverSensingMatrix classes, which are more useful practically.

The derivation of the optical lever sensing matrix is more involved. For details do check out kontrol documentationas well as the documentation of the optical lever sensing matrix. Here, we will briefly introduce the optical leversystem in KAGRA as well as some parameters involved, in a step-by-step manner.

Optical lever in KAGRA can be divided into two parts, tilt-sensing and length-sensing. The two uses the same beamand in reality, there is a beamsplitter that divides the beam into two. But, we can assume that they are two separatebeams with some common parameters, such as 𝛼ℎ, 𝛼𝑣 , 𝛿𝑥, 𝛿𝑦 and the direction of ��, as shown in the figures below.

26 Chapter 1. Features

Page 33: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3. Tutorials 27

Page 34: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

28 Chapter 1. Features

Page 35: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

The goal of this sensing matrix is to convert QPD readouts 𝑥tilt, 𝑦tilt, 𝑥len, and 𝑦len to the optics’ longitudinal 𝑥𝐿,pitch 𝜃𝑃 , and yaw 𝜃𝑌 displacements.

The tilt-sensing optical lever can be defined using a single parameter, the lever arm �� between optics (in fact, the it’sthe beam spot at the optics but we will use optics to refer the beam spot so we don’t have to say it over and over again)and the tilt-sensing QPD. �� itself is already enough but it’s not convenient to put it into a matrix. We instead use forparameters 𝑟ℎ, 𝑟𝑣 , 𝛼ℎ, and 𝛼𝑣 , which are the lever arm and the angle of incidences projected on the horizontal andvertical planes respectively.

Here’s let’s begin by defining some numbers. For demonstration, let’s use parameters from the BS optical lever. hereand here. The BS optical lever is a vertical optical lever, so 𝑟𝑣 = (996 + 120 + 185)/1000, 𝛼𝑣 = 36.9𝜋/180,𝑟ℎ = 𝑟𝑣 cos𝛼𝑣 , 𝛼ℎ = 0. These 4 parameters are sufficient to obtain an initial sensing matrix using kontrol.OpticalLeverSensingMatrix.

[1]: import numpy as np

r_v = (976+120+185) / 1000alpha_v = 36.9*np.pi/180r_h = r_v*np.cos(alpha_v)alpha_h = 0

import kontrol

ol_sensing_matrix = kontrol.OpticalLeverSensingMatrix(r_h=r_h, r_v=r_v, alpha_h=alpha_→˓h, alpha_v=alpha_v)ol_sensing_matrix

[1]: OpticalLeverSensingMatrix([[0. , 0. , 0. , 0. ],[0.39032 , 0. , 0. , 0. ],[0. , 0.488092, 0. , 0. ]])

Now we have the initial matrix for converting tilting sensing readout to pitch and yaw. Note that the matrix definitionin KAGRA is a map from [𝑦tilt, 𝑥tilt, 𝑦len, 𝑥len]

𝑇 to [𝑥𝐿, 𝜃𝑃 , 𝜃𝑌 ]𝑇 . It is different from what is writting in the optical

lever documentation where the QPD readouts are [𝑥tilt, 𝑦tilt, 𝑥len, 𝑦len]𝑇 . If we want to use the definition from the

document, we can do so by setting the format argument:

[2]: ol_sensing_matrix = kontrol.OpticalLeverSensingMatrix(r_h, r_v, alpha_h, alpha_v,→˓format="xy")print("xy\n", ol_sensing_matrix)ol_sensing_matrix = kontrol.OpticalLeverSensingMatrix(r_h, r_v, alpha_h, alpha_v,→˓format="OL2EUL")print("OL2EUL\n", ol_sensing_matrix)

## ol_sensing_matrix.format is a property, we can set it on the fly:ol_sensing_matrix.format = "xy"print("xy again\n", ol_sensing_matrix)ol_sensing_matrix.format = "OPLEV2EUL" # OL2EUL and OPLEV2EUL both works.print("OPLEV2EUL\n", ol_sensing_matrix)

xy[[0. 0. 0. 0. ][0. 0.39032 0. 0. ][0.488092 0. 0. 0. ]]

OL2EUL[[0. 0. 0. 0. ][0.39032 0. 0. 0. ][0. 0.488092 0. 0. ]]

xy again[[0. 0. 0. 0. ]

(continues on next page)

1.3. Tutorials 29

Page 36: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

[0. 0.39032 0. 0. ][0.488092 0. 0. 0. ]]

OPLEV2EUL[[0. 0. 0. 0. ][0.39032 0. 0. 0. ][0. 0.488092 0. 0. ]]

Note that in here, the matrix values are

⎡⎣ 0 0 0 01.76971116 0 0 0

0 1.98311972 0 0

⎤⎦ because this is a matrix that converts from

QPD counts to longitudinal, pitch, and yaw, whereas kontrol.OpticalLeverSensingMatrix is a matrix thatconverts from QPD beam spot displacement. To verify, let’s multiply the matrix by the calibration factors from countsto displacements.

[3]: print(ol_sensing_matrix @ np.diag(np.array([0.004534, 0.004063, 0, 0])*1000))

[[0. 0. 0. 0. ][1.76971088 0. 0. 0. ][0. 1.9831178 0. 0. ]]

This matches almost perfectly with the old matrix.

Now, let’s grab some length-sensing optical lever parameters from here and here, and some misalignment parametersfrom here.

We have 𝑟𝑣 = (996 + 120 + 185)/1000, 𝑟lens,𝑣 = (996 + 120 + 65 + 45)/1000, 𝑓 = 300/1000, 𝑑𝑣 =𝑟lens,𝑣𝑓

(𝑟lens,𝑣−𝑓) ,𝜑tilt = 2.5 * 𝜋/180, 𝜑len = 3.5 * 𝜋/180, 𝛿𝑥 = −0.083, and 𝛿𝑦 = 0.02.

Note that there’s a typo in here. Beam offsets 0.083 and 0.02 are in meters already, not centimeters, and minus signbecause +𝑇 is in the −𝑥 direction, see picture above.

[4]: ol_sensing_matrix.r_v = (996+120+185)/1000ol_sensing_matrix.r_h = ol_sensing_matrix.r_v*np.cos(ol_sensing_matrix.alpha_v)ol_sensing_matrix.r_lens_v = (996+120+65+45)/1000ol_sensing_matrix.f = 300/1000ol_sensing_matrix.d_v = ol_sensing_matrix.r_lens_v*ol_sensing_matrix.f/(ol_sensing_→˓matrix.r_lens_v-ol_sensing_matrix.f)ol_sensing_matrix.phi_tilt = 2.5*np.pi/180ol_sensing_matrix.phi_len = 3.5*np.pi/180ol_sensing_matrix.delta_x = -0.083ol_sensing_matrix.delta_y = 0.02print(ol_sensing_matrix)## Alternatively,# r_v = (996+120+185)/1000# r_h = r_v*np.cos(alpha_v)# r_lens_v = (996+120+65+45)/1000# f = 300/1000# d_v = r_lens_v*f/(r_lens_v-f)# phi_tilt = 2.5*np.pi/180# phi_len = 3.5*np.pi/180# delta_x = -0.083# delta_y = 0.02# ol_sensing_matrix = kontrol.OpticalLeverSensingMatrix(# r_h=r_h, r_v=r_v, alpha_h=alpha_h, alpha_v=alpha_v, r_lens_v=r_lens_v, f=f, d_→˓v=d_v,# phi_tilt=phi_tilt, phi_len=phi_len, delta_x=delta_x, delta_y=delta_y)# print(ol_sensing_matrix)

30 Chapter 1. Features

Page 37: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[[-0.00593916 0.0401862 -2.58930873 0.15836891][ 0.38395421 -0.0167638 1.18405437 -0.07241987][ 0.020963 0.48013159 0. 0. ]]

Again, let’s multiple it by calibration factors to see if it matches that in here.⎡⎣−0.02692811 0.16327656 −6.39818178 0.388162181.74084729 −0.06811129 2.92579829 −0.177501090.09504626 1.95077516 0 0

⎤⎦.

[5]: print(ol_sensing_matrix @ np.diag(np.array([0.004534, 0.004063, 0.002471, 0.→˓002451])*1000))

[[-0.02692813 0.16327652 -6.39818188 0.38816219][ 1.7408484 -0.06811133 2.92579836 -0.1775011 ][ 0.09504623 1.95077463 0. 0. ]]

Again, it matches almost perfectly.

Now, because this is a vertical optical lever, we can use kontrol.VerticalOpticalLeverSensingMatrixdirectly. See a self-explanatory demostration below.

[6]: r = (996+120+185)/1000 # Lever arm from optics to tilt-sensing QPD.alpha_v = 36.9*np.pi/180 # Angle of incidence on the vertical plane.r_lens = (996+120+65+45)/1000 # Lever arm from optics to lens.f = 300/1000 # Focal length of the convex lens.phi_tilt = 2.5*np.pi/180 # Angle between the tilt-sensing QPD frame and the yaw-→˓pitch frame.phi_len = 3.5*np.pi/180 # Angle between the length-sensing QPD frame and the yaw-→˓pitch frame.delta_x = -0.083 # Beam spot offset from the yaw rotational axis.delta_y = 0.02 # Beam spot offset from the pitch rotational axis.

ol_sensing_matrix = kontrol.VerticalOpticalLeverSensingMatrix(r=r, alpha_v=alpha_v, r_lens=r_lens, f=f,phi_tilt=phi_tilt, phi_len=phi_len, delta_x=delta_x, delta_y=delta_y)

print(ol_sensing_matrix) ## Match the matrix above.

[[-0.00593916 0.0401862 -2.58930873 0.15836891][ 0.38395421 -0.0167638 1.18405437 -0.07241987][ 0.020963 0.48013159 0. 0. ]]

1.3.4 Foton Utilities

1.3.4.1 Converting Python TransferFunction objects to Foton expression

In this tutorial, we will demonstrate how to use kontrol.foton to convert transfer functions defined in Python toFoton expressions like zpk([1;2;3],[4;5;6+i*7;6-i*7],8).

We will use the following transfer function for an example

𝑠(𝑠+ 1)

𝑠2 + 0.01𝜋𝑠+ 𝜋2. (1.2)

For this transfer function, we should expect to see two zeros at 0 and 1 rad/s and two complex poles at 𝜋 rad/s.

Update 2021-10-22: * kontrol.TransferFunction has a method kontrol.TransferFunction.foton() and it calls kontrol.foton.tf2foton(). * kontrol.foton.tf2foton() now works withtransfer functions with order higher than 20.

1.3. Tutorials 31

Page 38: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[1]: import controlimport numpy as np

s = control.tf("s")tf = s*(s+1) / (s**2 + 0.01*np.pi*s + np.pi**2)tf

[1]: 𝑠2 + 𝑠

𝑠2 + 0.03142𝑠+ 9.87

[2]: import kontrol

## By default, it uses zpk([...],[...],...,"s") expression.foton_expression = kontrol.foton.tf2foton(tf)print("Default expression:")print(foton_expression)print("")

# We can use other format as well.zpk_s_expression = kontrol.foton.tf2foton(tf, root_location="s")zpk_f_expression = kontrol.foton.tf2foton(tf, root_location="f")zpk_n_expression = kontrol.foton.tf2foton(tf, root_location="n")print("s format:")print(zpk_s_expression)print("")print("f format:")print(zpk_f_expression)print("")print("n format:")print(zpk_n_expression)

Default expression:zpk([0.0;-1.0],[-0.015707963267948967+i*3.1415533834361833;-0.015707963267948967+i*-3.→˓1415533834361833],1.0,"s")

s format:zpk([0.0;-1.0],[-0.015707963267948967+i*3.1415533834361833;-0.015707963267948967+i*-3.→˓1415533834361833],1.0,"s")

f format:zpk([0.0;-0.15915494309189535],[-0.0025000000000000005+i*0.49999374996093704;-0.→˓0025000000000000005+i*-0.49999374996093704],1.0,"f")

n format:zpk([-0.0;0.15915494309189535],[0.0025000000000000005+i*0.49999374996093704;0.→˓0025000000000000005+i*-0.49999374996093704],0.6366197723675814,"n")

[3]: # rpoly expressions are also supported.rpoly_expression = kontrol.foton.tf2foton(tf, expression="rpoly")print("rpoly expression:")print(rpoly_expression)

rpoly expression:rpoly([1.0;1.0;0.0],[1.0;0.031415926535897934;9.869604401089358],1.0)

[4]: ## kontrol.TransferFunction.foton calls kontrol.foton.tf2foton as well.

(continues on next page)

32 Chapter 1. Features

Page 39: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

kontrol_tf = kontrol.TransferFunction(tf)kontrol_tf.foton()

[4]: 'zpk([0.0;-1.0],[-0.015707963267948967+i*3.1415533834361833;-0.015707963267948967+i*-→˓3.1415533834361833],1.0,"s")'

[5]: ## Here's what would happen if we have a transfer function that has 50 order.tf50 = control.ss2tf(control.rss(50)) # a transfer function with 50 orderkontrol_tf50 = kontrol.TransferFunction(tf50)print(kontrol_tf50.foton()) # I had to use print() because of the "\n" character.

17:15 Kontrol WARNING : The transfer function has order higher than 20. This is not→˓supported by KAGRA's Foton software. The Foton expression is splitted into multiple→˓expressions with less order.

zpk([-2.5232781398584216+i*-1.6578552610252055;-2.5232781398584216+i*1.→˓6578552610252055;-3.08899987170562;-3.1305747149188345+i*-1.3328021454772787;-3.→˓1305747149188345+i*1.3328021454772787;-2.4578294531318092+i*-2.621198727295867;-2.→˓4578294531318092+i*2.621198727295867;-4.397592333503301;-2.486324679538306+i*-5.→˓128581461306449;-2.486324679538306+i*5.128581461306449;-1.3431047499781774+i*-5.→˓92379606895593;-1.3431047499781774+i*5.92379606895593;-6.495046864962012+i*3.→˓362376271247261;-6.495046864962012+i*-3.362376271247261;-0.33652724968526526+i*-8.→˓322380374882975;-0.33652724968526526+i*8.322380374882975;8.712291884892245;-1.→˓6814969149217118+i*-21.19084243355869;-1.6814969149217118+i*21.19084243355869],[-2.→˓500503530286038+i*-1.553952860252672;-2.500503530286038+i*1.553952860252672;-1.→˓727690252288154+i*2.862072391649727;-1.727690252288154+i*-2.862072391649727;-3.→˓3283972440772964+i*0.6111308684269028;-3.3283972440772964+i*-0.6111308684269028;-3.→˓4912706485895413;-3.2698535096923904+i*1.2721826414281387;-3.2698535096923904+i*-1.→˓2721826414281387;-3.242178746285609+i*1.9580469409934114;-3.242178746285609+i*-1.→˓9580469409934114;-1.8614465171553298+i*5.392114058025007;-1.8614465171553298+i*-5.→˓392114058025007;-0.7335496281463658+i*6.344379182298116;-0.7335496281463658+i*-6.→˓344379182298116;-8.002607517624055;-0.5805231819050503+i*-8.181847421712298;-0.→˓5805231819050503+i*8.181847421712298;-0.15834892755829594+i*-17.446193819871674;-0.→˓15834892755829594+i*17.446193819871674],-5.780892575645753,"s")

zpk([-0.8739357214111139+i*-0.1392801468961625;-0.8739357214111139+i*0.→˓1392801468961625;-0.41357350037156704+i*-0.8001291238721325;-0.→˓41357350037156704+i*0.8001291238721325;-1.0454068191343315+i*-0.2940905095536843;-1.→˓0454068191343315+i*0.2940905095536843;-0.8652618296485356+i*0.9417424590792935;-0.→˓8652618296485356+i*-0.9417424590792935;-1.2719900596330618+i*0.48060233083714443;-1.→˓2719900596330618+i*-0.48060233083714443;-1.689455577848595;-1.5849116262047915+i*0.→˓732604937149256;-1.5849116262047915+i*-0.732604937149256;-1.7678307145549357+i*1.→˓0581264346548764;-1.7678307145549357+i*-1.0581264346548764;-2.285895014964026+i*-0.→˓974496775394504;-2.285895014964026+i*0.974496775394504;-2.8918900148104845+i*-0.→˓5670722918281813;-2.8918900148104845+i*0.5670722918281813],[-0.→˓10581552605457377+i*0.7365524321584936;-0.10581552605457377+i*-0.7365524321584936;-→˓0.8046928914520258+i*-0.18663276619070576;-0.8046928914520258+i*0.18663276619070576;→˓-1.0167045842236258+i*-0.06615688617090629;-1.0167045842236258+i*0.→˓06615688617090629;-1.0132075291444036+i*-0.39426302730475454;-1.→˓0132075291444036+i*0.39426302730475454;-0.9454354544243626+i*-0.8842732060091496;-0.→˓9454354544243626+i*0.8842732060091496;-1.2444970761952148+i*0.6655945059474119;-1.→˓2444970761952148+i*-0.6655945059474119;-1.4599785832209897+i*0.9537914374060632;-1.→˓4599785832209897+i*-0.9537914374060632;-1.9996186001182188;-1.7969739271834022+i*-1.→˓0348398411268747;-1.7969739271834022+i*1.0348398411268747;-2.4184174535006457+i*-1.→˓2270125614293188;-2.4184174535006457+i*1.2270125614293188],0.9999999999999996,"s")

zpk([-0.1568765386692976+i*0.005939423033734596;-0.1568765386692976+i*-0.→˓005939423033734596;-0.1961515696207522;-0.21192004686466182;-0.2901688223277827;-0.→˓30849945886679175;-0.4402149752774941+i*0.01777131711622888;-0.4402149752774941+i*-→˓0.01777131711622888;-0.5187034676512221;-0.7324418426101729+i*0.02510872775750683;-→˓0.7324418426101729+i*-0.02510872775750683],[-0.08905845021584304;-0.→˓1593575865865395;-0.19601582228771403;-0.2007364391312313;-0.2121933180602774;-0.→˓30695297537610594;-0.31111400804563527;-0.4179591149625443;-0.44111646392203147;-0.→˓6514863285403999+i*0.05733964679335237;-0.6514863285403999+i*-0.05733964679335237],→˓1.0,"s")

(continues on next page)

1.3. Tutorials 33

Page 40: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

1.3.5 Ezca Utilities

1.3.5.1 Read and Write Matrices using Kontrol’s EZCA Wrapper

We often want to get a matrix from the EPICS record, manipulate it, and then write it back to the EPICS record. But,this can be difficult since the Easy Channel Access (EZCA) only have functions to read and write one single elementat a time.

Here, Kontrol provides a EZCA wrapper to allow reading and writing matrices more easily. We will demonstrate usinga trivial matrix "K1:VIS-SRM_BF_SEISALIGN_{1,2,3}_{1,2,3}".

Here’s the original matrix and let’s access it.

[1]: import kontrolimport numpy as np

# Define an Ezca instancesrm = kontrol.Ezca("VIS-SRM") # Argument is the prefix after "K1:".

# Get matrix. Make sure you're doing this at the k1ctr workstations, or else you will→˓get a Null array.bf_seisalign = srm.get_matrix("BF_SEISALIGN")bf_seisalign

[1]: array([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]])

Now, let’s manipulate it and put it back

[2]: decouple_matrix = np.array([[1, 0.1, 0.01], [-0.01, 1, 0.1], [0.01, -0.1, 1]])new_bf_seisalign = bf_seisalign @ decouple_matrix # here, @ is the matrix→˓multiplication operator, just in case you don't know.new_bf_seisalign

[2]: array([[ 1. , 0.1 , 0.01],[-0.01, 1. , 0.1 ],[ 0.01, -0.1 , 1. ]])

Now put it back

34 Chapter 1. Features

Page 41: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[3]: srm.put_matrix(new_bf_seisalign, "BF_SEISALIGN")

K1:VIS-SRM_BF_SEISALIGN_1_2 => 0.1K1:VIS-SRM_BF_SEISALIGN_1_3 => 0.01K1:VIS-SRM_BF_SEISALIGN_2_1 => -0.01K1:VIS-SRM_BF_SEISALIGN_2_3 => 0.1K1:VIS-SRM_BF_SEISALIGN_3_1 => 0.01K1:VIS-SRM_BF_SEISALIGN_3_2 => -0.1

TA-DA!

1.3.6 Curve Fitting

1.3.6.1 Fitting a Polynomial

In this tutorial, we will show how to use the generic curve fitting class kontrol.curvefit.CurveFit to fit apolynomial.

kontrol.curvefit.CurveFit is a low-level class for curve fitting. It uses optimization to minimize a costfunction, e.g. mean squared error, to fit a curve. It requires at least 5 specifications,

• xdata: the independent variable data,

• ydata: the dependent variable data,

• model: The model,

• cost: the cost function, and

• optimizer: the optimization algorithm.

In addition, keyword arguments can be specified to the model and optimizer as model_kwargs andoptimizer_kwargs.

The functions model, cost, and optimizer takes a specific format. See documentation or tutorial below on howto construct them, or simply use the predefined ones in kontrol.

Here, we will create the data to be fitted, which is a simple polynomial.

𝑦 =∑𝑖=0

𝑎𝑖𝑥𝑖 (1.3)

1.3. Tutorials 35

Page 42: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[1]: # Prepare the dataimport numpy as npimport matplotlib.pyplot as plt

xdata = np.linspace(-1, 1, 1024)np.random.seed(123)random_args = np.random.random(5)*2 - 1 # Generate some random args to be fitted.def polynomial(x, args, **kwargs):

"""Parameters----------x : array

x axisargs : array

A list of coefficients of the polynomial

Returns-------array

args[0]*x**0 + args[1]*x**1 ... args[len(args)-1]*x**(len(args)-1)."""poly = np.sum([args[i]*x**i for i in range(len(args))], axis=0)return poly

ydata = polynomial(xdata, random_args)print(random_args)

[ 0.39293837 -0.42772133 -0.54629709 0.10262954 0.43893794]

We see that the coefficients are

𝑎𝑖 =[0.39293837 −0.42772133 −0.54629709 0.10262954 0.43893794

](1.4)

Now let’s see if we can recover it.

[2]: import kontrol.curvefitimport scipy.optimize

a = kontrol.curvefit.CurveFit()a.xdata = xdataa.ydata = ydataa.model = polynomial

error_func = kontrol.curvefit.error_func.mse ## Mean square errora.cost = kontrol.curvefit.Cost(error_func=error_func)

# If we know the boundary of the coefficients,# scipy.optimize.differential_evolution would be a suitable optimizer.a.optimizer = scipy.optimize.differential_evolutiona.optimizer_kwargs = {"bounds": [(-1, 1)]*5, "workers": -1, "updating": "deferred"} #→˓# workers=1 will use all available CPU cores.a.fit()de_args = a.optimized_argsde_fit = a.yfitprint(de_args)

[ 0.39293837 -0.42772133 -0.54629709 0.10262954 0.43893794]

36 Chapter 1. Features

Page 43: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[3]: # If we know the inital guess instead,# scipy.optimizer.minimize can be used.# In this case, we choose the Powell algorithm.# We also intentionally fit with 6th-order polynomial instead of 5th-order one.a.optimizer = scipy.optimize.minimizea.optimizer_kwargs = {"x0": [0]*6, "method": "Powell"} ## Start from [0, 0, 0, 0, 0]a.fit()pw_args = a.optimized_argspw_fit = a.yfitprint(pw_args)

[ 3.92938371e-01 -4.27721330e-01 -5.46297093e-01 1.02629538e-014.38937940e-01 -5.90492751e-14]

In both cases we see the parameters are recovered well. Now let’s look at some plots.

[4]: ## Plotplt.figure(figsize=(10, 5))plt.plot(xdata, ydata, "-", label="Data", lw=5)plt.plot(xdata, de_fit, "--", label="Fit with differetial evolution", lw=3)plt.plot(xdata, pw_fit, "-.", label="Fit with Powell")plt.xlabel("x")plt.ylabel("y")plt.legend()plt.grid(which="both")

1.3.6.2 Fitting a transfer function with CurveFit and TransferFunctionModel

In this tutorial, we will use kontrol.curvefit.CurveFit to fit a measured transfer function. As is mentionedin previous tutorial, kontrol.curvefit.CurveFit requires a few things, the independent variable data xdata,the dependent variable ydata, the model model, the cost function cost, and the optimizer. They have the followingsignature:

1.3. Tutorials 37

Page 44: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

xdata : arrayydata : arraymodel : func(x: array, *args, **kwargs) -> arraycost : func(args: array, model: func, xdata: array, ydata: array, model_kwargs: dict)→˓-> floatoptimizer : func(cost: func, **kwargs) -> scipy.optimize.OptimzeResult

To save you a lot of troubles, kontrol library provides various models classes kontrol.curvefit.model, costfunctions (error functions). And optimizers are readily available in scipy.optimize. So we typically just need toprepare xdata and ydata. Of course, knowing what model, error function, and optimizer to use is crucial in a curvefitting task.

This time, we will use the model kontrol.curvefit.model.TransferFunctionModel as our model thistime. This model can be defined by the number of zeros nzero and npole. The parameters are simply concatenatedcoefficients of numerator and denominator arranging from high to low order.

Here, let’s consider the transfer function

𝐻(𝑠) =𝑠2 + 3𝑠+ 2

𝑠3 + 12𝑠2 + 47𝑠+ 60(1.5)

So the parameters we would like to recover are the coefficients [1, 3, 2, 1, 12, 47, 60].

For the sake of demonstration, let’s assume that we know there are 2 zeros and 3 poles as this is required to define amodel.

We will use kontrol.curvefit.error_func.tf_error as the error function and it is defined as

𝐸tf_error (𝐻1(𝑓), 𝐻2(𝑓);𝑤(𝑓), 𝜖) =1

𝑁

𝑁∑𝑖=0

log10(|(𝐻1(𝑓𝑖)−𝐻2(𝑓𝑖) + 𝜖)𝑤(𝑓𝑖)|) , (1.6)

where 𝐻1(𝑓) and 𝐻2(𝑓) are the frequency responses values (complex array) of the measured system and the model,𝜖 is a small number to prevent log10 from exploding, 𝑤(𝑓) is a weighting function, and 𝑁 is the total number of datapoints.

[1]: import controlimport numpy as npimport matplotlib.pyplot as plt

f = np.logspace(-3, 3, 100000)s = 1j*2*np.pi*ftf = (s**2 + 3*s + 2) / (s**3 + 12*s**2 + 47*s + 60)

plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf))plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf))plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

38 Chapter 1. Features

Page 45: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Now, let’s everything that kontrol.curvefit.CurveFit needs, namely xdata, ydata, model, cost, andoptimizer.

[2]: import kontrol.curvefitimport scipy.optimize

xdata = fydata = tfmodel = kontrol.curvefit.model.TransferFunctionModel(nzero=2, npole=3, log_args=False)error_func = kontrol.curvefit.error_func.tf_errorcost = kontrol.curvefit.Cost(error_func=error_func)optimizer = scipy.optimize.minimize

Since we’re using scipy.optimize.minimize, it requires an initial guess. Let’s start with all ones.

[3]: x0 = np.ones(7) # There are 7 coefficients, 3 for numerator and 4 for denominatoroptimizer_kwargs = {"x0": x0}

Now let’s use kontrol.curvefit.CurveFit to fit the data.

[4]: a = kontrol.curvefit.CurveFit()a.xdata = xdataa.ydata = ydataa.model = modela.cost = costa.optimizer = optimizera.optimizer_kwargs = optimizer_kwargsres = a.fit()

[5]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf), label="Data")plt.loglog(f, abs(a.model(f, x0)), label="Initial guess")plt.loglog(f, abs(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf), label="Data")

(continues on next page)

1.3. Tutorials 39

Page 46: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess")plt.semilogx(f, np.angle(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

[6]: res.x/res.x[0]

[6]: array([ 1. , 1.78627675, 0.84976028, 1.00034299, 11.024541 ,35.31604716, 25.492815 ])

Looks great, right? But, this is not typically that easy. This above case was easy because I have set the frequency arrayto logspace whereas we typically get a linspace array from fourier transform of linearly spaced time domain data!

Now let’s see what happens if we switch back to linspace.

[7]: f = np.linspace(0.001, 1000, 100000)s = 1j*2*np.pi*ftf = (s**2 + 3*s + 2) / (s**3 + 12*s**2 + 47*s + 60)

xdata = fydata = tfmodel = kontrol.curvefit.model.TransferFunctionModel(nzero=2, npole=3, log_args=False)error_func = kontrol.curvefit.error_func.tf_errorcost = kontrol.curvefit.Cost(error_func=error_func)optimizer = scipy.optimize.minimize

x0 = np.ones(7) # There are 7 coefficients, 3 for numerator and 4 for denominatoroptimizer_kwargs = {"x0": x0}

a = kontrol.curvefit.CurveFit()a.xdata = xdataa.ydata = ydataa.model = modela.cost = costa.optimizer = optimizera.optimizer_kwargs = optimizer_kwargsres = a.fit()

40 Chapter 1. Features

Page 47: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[8]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf), label="Data")plt.loglog(f, abs(a.model(f, x0)), label="Initial guess")plt.loglog(f, abs(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf), label="Data")plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess")plt.semilogx(f, np.angle(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

Not great! Terrible!

It turns out that there’re a few tricks that can be used fit linspace transfer function data.

1. Weighing function (1/f).

2. Parameter scaling (log).

3. Use other optimizers (differential evolution, Nelder-Mead, Powell, etc).

4. Use better initial guess.

5. Tighter acceptable tolerance for convergence during minimization (e.g. ftol, xtol. Different optimizers usedifferent convention)

We will use all of them!

[9]: a = kontrol.curvefit.CurveFit()a.xdata = fa.ydata = tf# Use 1/f weightingweight = 1/ferror_func_kwargs = {"weight": weight}a.cost = kontrol.curvefit.Cost(error_func=error_func, error_func_kwargs=error_func_→˓kwargs)

(continues on next page)

1.3. Tutorials 41

Page 48: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

# Use log scaling. Models have an argument for enabling that.a.model = kontrol.curvefit.model.TransferFunctionModel(nzero=2, npole=3, log_→˓args=True)

# Use better initial guess and Nelder-Mead optimizer.np.random.seed(123)true_args = np.array([1, 3, 2, 1, 12, 47, 60]) # These are the true parametersnoise = np.random.normal(loc=0, scale=true_args/10, size=7)x0 = true_args + noise # Now the initial guess is assumed to be some deviation from→˓the true values.# x0 = np.ones(7)a.optimizer_kwargs = {"x0": np.log10(x0), "method": "powell", "options": {"xtol": 1e-→˓12, 'ftol': 1e-12}} # Note the inital guess is log.a.optimizer = scipy.optimize.minimizeres = a.fit()

[10]: 10**a.optimize_result.x

[10]: array([ 2.25124892, 3.66597884, 1.51466572, 2.25137845, 23.92566764,77.41975222, 45.43996825])

[11]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf), label="Data")plt.loglog(f, abs(a.model(f, np.log10(x0))), label="Initial guess")plt.loglog(f, abs(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf), label="Data")plt.semilogx(f, np.angle(a.model(f, np.log10(x0))), label="Initial guess")plt.semilogx(f, np.angle(a.yfit), label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

42 Chapter 1. Features

Page 49: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[12]: fitted_args = 10**a.optimized_argsfitted_args /= fitted_args[0]fitted_args

[12]: array([ 1. , 1.62842003, 0.6728113 , 1.00005754, 10.62773089,34.38968995, 20.18433765])

[13]: print("True arguments: ", true_args)print("Initial arguments: ", x0/x0[0])print("Arguments optained from fitting: ", fitted_args)

True arguments: [ 1 3 2 1 12 47 60]Initial arguments: [ 1. 3.70099497 2.30705685 0.95281056 12.68253445 61.→˓4308755850.97379581]

Arguments optained from fitting: [ 1. 1.62842003 0.6728113 1.00005754 10.→˓62773089 34.3896899520.18433765]

[14]: print("True transfer function")s = control.tf("s")tf = (s**2 + 3*s + 2) / (s**3 + 12*s**2 + 47*s + 60)tf

True transfer function

[14]: 𝑠2 + 3𝑠+ 2

𝑠3 + 12𝑠2 + 47𝑠+ 60

[15]: print("Fitted transfer function")a.model.args = a.optimized_argsa.model.tf.minreal()

Fitted transfer function

[15]: 0.9999𝑠2 + 1.628𝑠+ 0.6728

𝑠3 + 10.63𝑠2 + 34.39𝑠+ 20.18

The fit looks good. But, apparently it has found a set of different parameters. See further tutorials for alternative waysof fitting the same transfer function.

Nevertherless, we demonstrated the use of kontrol.curvefit.CurveFit for transfer function fitting.

1.3.6.3 Fitting transfer function with a ZPK model

In the last tutorial, we tried using generic transfer function model to fit a transfer function

𝐻(𝑠) =𝑠2 + 3𝑠+ 2

𝑠3 + 12𝑠2 + 47𝑠+ 60. (1.7)

Although we ended up with a transfer function that fits the data, the parameters were not correct.

If we factorize the the numerator and denominator, in fact, we get

𝐻(𝑠) =(𝑠+ 1)(𝑠+ 2)

(𝑠+ 3)(𝑠+ 4)(𝑠+ 5)(1.8)

which is a more desirable form (The first form has 7 parameters, and the latter one has 6! That means the solution isnot unique if we use the first form!).

1.3. Tutorials 43

Page 50: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Let’s try to fit the data with kontrol.curvefit.model.SimpleZPK instead.

kontrol.curvefit.model.SimpleZPK is defined as

𝐺(𝑠; 𝑧𝑖, 𝑝𝑗 , 𝑘) = 𝑘

∏𝑖 𝑠/𝑧𝑖 + 1∏𝑗 𝑠/𝑝𝑗 + 1

. (1.9)

So the parameters we are looking for are [1, 2, 3, 4, 5, 1]. But we typically work in unit of Hz instead of rad/s.

So, the true parameters are [0.15915494, 0.31830989, 0.47746483, 0.63661977, 0.79577472, 0.03333333]. Note thatfor the first two parameters (zeros), the order doesn’t matter, and same goes for

[1]: # Same as the previous tutorialimport controlimport numpy as npimport matplotlib.pyplot as plt

f = np.linspace(0.001, 1000, 100000)s = 1j*2*np.pi*ftf = (s**2 + 3*s + 2) / (s**3 + 12*s**2 + 47*s + 60)

plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf))plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf))plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

[2]: import kontrol.curvefitimport scipy.optimize

a = kontrol.curvefit.CurveFit()a.xdata = fa.ydata = tfa.model = kontrol.curvefit.model.SimpleZPK(nzero=2, npole=3, log_args=False)error_func = kontrol.curvefit.error_func.tf_errorweight = 1/f

(continues on next page)

44 Chapter 1. Features

Page 51: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

error_func_kwargs = {"weight": weight}a.cost = kontrol.curvefit.Cost(error_func=error_func, error_func_kwargs=error_func_→˓kwargs)a.optimizer = scipy.optimize.minimizenp.random.seed(123)true_args = np.array([0.15915494, 0.31830989, 0.47746483, 0.63661977, 0.79577472, 0.→˓03333333]) # These are the true parametersnoise = np.random.normal(loc=0, scale=true_args/10, size=6)x0 = true_args + noise # Now the initial guess is assumed to be some deviation from→˓the true values.

a.optimizer_kwargs = {"x0": x0,}

res = a.fit()

[3]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf), label="Data")plt.loglog(f, abs(a.model(f, x0)), label="Initial guess")plt.loglog(f, abs(a.yfit), ls="--", label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf), label="Data")plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess")plt.semilogx(f, np.angle(a.yfit), ls="--", label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

[4]: print("True parameters :", true_args)print("Parameters from fit: ", a.optimized_args)

True parameters : [0.15915494 0.31830989 0.47746483 0.63661977 0.79577472 0.03333333]Parameters from fit: [0.15889944 0.32540938 0.54057908 0.55856226 0.81765228 0.→˓03333333]

1.3. Tutorials 45

Page 52: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Let’s convert it into transfer function and obtain the transfer function representation.

[5]: print("True transfer function")s = control.tf("s")tf_ = (s**2 + 3*s + 2) / (s**3 + 12*s**2 + 47*s + 60)tf_

True transfer function

[5]: 𝑠2 + 3𝑠+ 2

𝑠3 + 12𝑠2 + 47𝑠+ 60

[6]: print("Fitted transfer function")a.model.args = a.optimized_argsa.model.tf.minreal()

Fitted transfer function

[6]: 𝑠2 + 3.043𝑠+ 2.041

𝑠3 + 12.04𝑠2 + 47.4𝑠+ 61.24

Much better!

Now, with the ZPK model, we don’t really have to use local minimization. We know the the location of poles andzeros are within the measured frequency band so we can put a bound on the model parameters. This means that wedon’t even need an initial guess.

[11]: bounds = [(0.1, 1)] * 5 # Say we know the features are between 0.01 and 10 Hz.bounds += [(abs(tf[0])/10, (abs(tf[0])*10))] # around the lower frequency valuea.optimizer = scipy.optimize.differential_evolutiona.optimizer_kwargs = {"bounds": bounds, "workers": -1, "updating": "deferred"}np.random.seed(123) # Fix random seed for reproducibilitya.fit()

[11]: fun: -0.11893392985453001message: 'Optimization terminated successfully.'

nfev: 14480nit: 156

success: Truex: array([0.24229786, 0.17012243, 0.7466101 , 0.74350342, 0.35457821,0.03333341])

[12]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf), label="Data")# plt.loglog(f, abs(a.model(f, x0)), label="Initial guess")plt.loglog(f, abs(a.yfit), ls="--", label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf), label="Data")# plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess")plt.semilogx(f, np.angle(a.yfit), ls="--", label="Fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")

(continues on next page)

46 Chapter 1. Features

Page 53: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.xlabel("Frequency (Hz)")plt.show()

[13]: print("Fitted transfer function")a.model.args = a.optimized_argsa.model.tf.minreal()

Fitted transfer function

[13]: 𝑠2 + 2.592𝑠+ 1.627

𝑠3 + 11.59𝑠2 + 42.77𝑠+ 48.82

1.3.6.4 Fitting the transfer function of a coupled oscillator

This section will be undoubtedly very useful for KAGRA’s suspension modeling, as the transfer functions of thesuspensions all behave like oscillator. Different from that in the previous tutorials, the transfer function of osciilatorshas complex poles and zeros instead of simple poles and zeros. Typically, they can be represented by the model

𝐺(𝑠; 𝑧𝑖, 𝑞𝑖, 𝑝𝑗 , 𝑄𝑗 , 𝑘) = 𝑘

∏𝑖

(1

(2𝜋𝑧𝑖)2𝑠2 + 1

2𝜋𝑧𝑖𝑞𝑖𝑠+ 1

)∏

𝑗

(1

(2𝜋𝑝𝑗)2𝑠2 + 1

2𝜋𝑝𝑗𝑄𝑗𝑠+ 1

) , (1.10)

where 𝑧𝑖 and 𝑝𝑗 are the resonance frequencies of the complex zeros and poles respectively, 𝑞𝑖 and 𝑄𝑗 are the qualityfactors of the complex zeros and poles respectively, and 𝑘 is the static gain. And because of the similarity to the simpleZPK model, we call this a complex ZPK model.

Typically it’s sufficient to consider the model

𝐻(𝑠; 𝑘𝑖, 𝑓𝑖, 𝑞𝑖) =∑𝑖

𝑘𝑖(2𝜋𝑓𝑖)

2

𝑠2 + 2𝜋𝑓𝑖𝑞𝑖

𝑠+ (2𝜋𝑓𝑖)2. (1.11)

This model has fewer parameters than 𝐺(𝑠), but it only works for second-order systems, i.e. input-output relationshipof a single stage. 𝐺(𝑠) is a more general representation so this is what we will use here.

In this tutorial, we will consider the following transfer function

𝐹 (𝑠) = 10(2𝜋)2

𝑠2 + 2𝜋10 𝑠+ (2𝜋)2

+ 1(10 * 2𝜋)2

𝑠2 + 10*2𝜋100 𝑠+ (10 * 2𝜋)2

. (1.12)

And, we will try to fit it with the model 𝐺(𝑠)

1.3. Tutorials 47

Page 54: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[1]: import controlimport numpy as npimport matplotlib.pyplot as plt

s = control.tf("s")tf = 10*(2*np.pi)**2/(s**2 + (1*2*np.pi)/10*s + (2*np.pi)**2) + 1*(10*2*np.pi)**2/→˓(s**2 + (10*2*np.pi)/10*s + (10*2*np.pi)**2)f = np.linspace(0.01, 100, 10000)tf_val = tf(1j*2*np.pi*f)

plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf_val))plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf_val))plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

Here will we show how we can obtain an inital guess for the fit.

Judging from the plot, we should expect two complex poles at around 1 and 10 Hz, and a complex zeros at around3 Hz. We will hand fit the transfer function as an initial guess. We start from lowest frequency, so it’s just a gain ataround 10.

𝐺0(𝑠) = 10 (1.13)

And then, we scan from low frequency to high frequency. The next thing we see is a peak, which correspond to a pairof complex poles. So, we add a pair of complex poles at 1 Hz and we fine tune the Q to match the peak, to which itbecomes

𝐺1(𝑠) = 𝐺0(𝑠)1

1(1*2𝜋)2 𝑠

2 + 11*2𝜋𝑄1

𝑠+ 1, (1.14)

wherer 𝑄1 is around 10.

Then, the next feature is a notch at 3 Hz, which corresponds to a pair of complex zeros. So we add a pair of complexzeros at 3 Hz, and tune the Q value to match the depth. So it becomes

𝐺2(𝑠) = 𝐺1(𝑠)

1(3*2𝜋)2 𝑠

2 + 13*2𝜋𝑞2 𝑠+ 1

1, (1.15)

48 Chapter 1. Features

Page 55: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

where we pick 𝑞2 = 10

The last feature is a peak at 10 Hz, so we will add a pair of complex poles at 10 Hz. And finally, the

𝐺3(𝑠) = 𝐺2(𝑠)1

1(10*2𝜋)2 𝑠

2 + 110*2𝜋𝑄3

𝑠+ 1, (1.16)

where we pick 𝑄3 to be 20.

[2]: import kontrol.curvefit

G0 = kontrol.curvefit.model.ComplexZPK(nzero_pairs=0, npole_pairs=0)G0_args = [10]

G1 = kontrol.curvefit.model.ComplexZPK(nzero_pairs=0, npole_pairs=1)G1_args = [1, 10, 10]

G2 = kontrol.curvefit.model.ComplexZPK(nzero_pairs=1, npole_pairs=1)G2_args = [3, 10, 1, 10, 10]

G3 = kontrol.curvefit.model.ComplexZPK(nzero_pairs=1, npole_pairs=2)G3_args = [3, 10, 1, 10, 10, 20, 10]

plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf_val), label="Original transfer function", lw=3)plt.loglog(f, abs(G0(f, G0_args)), label="$G_0(s)$", ls="--")plt.loglog(f, abs(G1(f, G1_args)), label="$G_1(s)$", ls="-.")plt.loglog(f, abs(G2(f, G2_args)), label="$G_2(s)$", ls=":")plt.loglog(f, abs(G3(f, G3_args)), label="$G_3(s)$", color="k", ls="--")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")

plt.subplot(122)plt.semilogx(f, np.angle(tf_val))plt.semilogx(f, np.angle(G0(f, G0_args)), label="$G_0(s)$")plt.semilogx(f, np.angle(G1(f, G1_args)), label="$G_1(s)$")plt.semilogx(f, np.angle(G2(f, G2_args)), label="$G_2(s)$")plt.semilogx(f, np.angle(G3(f, G3_args)), label="$G_3(s)$")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

1.3. Tutorials 49

Page 56: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

As can be seen, adding zeros/poles at higher frequencies doesn’t affect the features at lower frequencies. This is whywe started adding features from lower to higher frequencies.

The final initial guess we obtained is a kontrol.curvefit.model.ComplexZPK model, with parameters [3,10, 1, 10, 10, 20, 10]. The arguments are arranged in the form [z_i, q_i, p_j, Q_j, k]. Wespecified the number of complex zero pairs nzero_pairs and the number of complex pole pairs npole_pairsduring the model declaration so the program knows how to slices the parameters. So in this case, the first two valuescorrespond to a pair of complex zeros at 3 Hz with Q-value of 10. The second two values correspond to a pair ofcomplex poles at 1 Hz with Q-value of 10, and so on. But this initial guess is probably too good to start with, so weadd some noise to it to mimic a realistic fit.

Now we can proceed to the usual curve fitting routine.

[3]: import scipy.optimizeimport kontrol.curvefit

a = kontrol.curvefit.CurveFit()a.xdata = fa.ydata = tf_vala.model = kontrol.curvefit.model.ComplexZPK(nzero_pairs=1, npole_pairs=2)

error_func = kontrol.curvefit.error_func.tf_errorweight = 1/ferror_func_kwargs = {"weight": weight}a.cost = kontrol.curvefit.Cost(error_func=error_func, error_func_kwargs=error_func_→˓kwargs)a.optimizer = scipy.optimize.minimize

np.random.seed(123)noise = np.random.normal(0, np.array(G3_args)/10)# print(noise)x0 = np.array(G3_args) + noisea.optimizer_kwargs = {"x0": x0}res = a.fit()

[4]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf_val), label="Data")plt.loglog(f, abs(a.model(f, x0)), label="Initial guess", ls="--")plt.loglog(f, abs(a.yfit), label="Fit", ls="-.")plt.legend(loc=0)

(continues on next page)

50 Chapter 1. Features

Page 57: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf_val), label="Data")plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess", ls="--")plt.semilogx(f, np.angle(a.yfit), label="Fit", ls="-.")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

Again, because the parameters can be bounded, we can use global optimization without an initial guess. Let’s justdemonstrate once again.

[5]: # Using log scale this time.bounds = [(np.log10(min(f)), np.log10(max(f))), (np.log10(0.5), np.log10(1e3))]*3bounds += [(np.log10(abs(tf_val[0])/10), np.log10(abs(tf_val[0])*10))]

a.model = kontrol.curvefit.model.ComplexZPK(nzero_pairs=1, npole_pairs=2, log_→˓args=True)a.optimizer = scipy.optimize.differential_evolutiona.optimizer_kwargs = {"bounds": bounds, "workers": -1, "updating": "deferred"}

np.random.seed(123)res = a.fit()

[6]: plt.figure(figsize=(15, 5))plt.subplot(121)plt.loglog(f, abs(tf_val), label="Data")# plt.loglog(f, abs(a.model(f, x0)), label="Initial guess", ls="--")plt.loglog(f, abs(a.yfit), label="Fit", ls="-.")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.subplot(122)plt.semilogx(f, np.angle(tf_val), label="Data")# plt.semilogx(f, np.angle(a.model(f, x0)), label="Initial guess", ls="--")

(continues on next page)

1.3. Tutorials 51

Page 58: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.semilogx(f, np.angle(a.yfit), label="Fit", ls="-.")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.show()

And, it worked very well.

1.3.6.5 Fitting the KAGRA Typical LVDT and and Geophone noise with Transfer Function

For LVDT and geophone noise, we assume their amplitude spectral densities (ASDs) follows the empirical model

𝑁model(𝑓 ;𝑛𝑎, 𝑛𝑏, 𝑎, 𝑏) =

[(𝑛𝑎

𝑓𝑎

)2

+

(𝑛𝑏

𝑓 𝑏

)2] 1

2𝜇m√Hz

(1.17)

Here’re some typical values for LVDTs and Geophones

Sensor 𝑛𝑎 𝑛𝑏 𝑎 𝑏LVDT 1e-2.07 1e-2.3 0.5 0Geophone 1e-5.46 1e-5.23 3.5 1

In this tutorial, we will demonstrate the use of kontrol.curvefit.CurveFit for fitting a transfer functionfor noise amplitudes. The fitted noise models will be exported and be used in the complementary filter tutorial. ToFit the noise with a transfer function, we use a ZPK model kontrol.curvefit.model.SimpleZPK as anintermediate step. The benefit of using a ZPK model is that the bounds are specifiable, i.e. the bounds of the fre-quency axis, so a global optimization scheme, i.e. scipy.optimize.differential_evolution(), canbe used. After obtaining a ZPK model, it’s converted to the polynomial transfer function format and it’s numera-tor and denominator are used for a final local fit using a transfer function model kontrol.curvefit.model.TransferFunctionModel() and a local minimization scheme scipy.optimize.minimize("method"="Nelder-Mead", ...) to obtain the final parameters of the transfer functions. We negate any unstable zeros andpoles in the final transfer functions and they are exported to "noise_lvdt.pkl" and "noise_geophone.pkl", which can be read with kontrol.load_transfer_function().

[1]: # Let's define the sensor noisesimport numpy as npimport matplotlib.pyplot as plt

(continues on next page)

52 Chapter 1. Features

Page 59: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

def model(f, na, nb, a, b):return np.sqrt((na/f**a)**2 + (nb/f**b)**2)

# f = np.linspace(0.001, 100, 102400)# For the sake of demostration, use logspace. It's easierf = np.logspace(-3, 2, 10240)n_lvdt = model(f, na=1*10**-2.07, nb=1*10**-2.3, a=0.5, b=0)n_geophone = model(f, na=1*10**-5.46, nb=1*10**-5.23, a=3.5, b=1)n_lvdt_pad = np.array(n_lvdt)n_geophone_pad = np.array(n_geophone)

[2]: # Make the noise flat at lower and higher frequencies so it works with h-infinity→˓synthesis.# Pad before f_lower and after f_upperf_lower = 0.01f_upper = 10n_lvdt_lower_val = n_lvdt[f>f_lower][0]n_lvdt_upper_val = n_lvdt[f<f_upper][-1]n_lvdt_pad[f<f_lower] = n_lvdt_lower_valn_lvdt_pad[f>f_upper] = n_lvdt_upper_val

n_geophone_lower_val = n_geophone[f>f_lower][0]n_geophone_upper_val = n_geophone[f<f_upper][-1]n_geophone_pad[f<f_lower] = n_geophone_lower_valn_geophone_pad[f>f_upper] = n_geophone_upper_val

[3]: # Let's plot the noisesplt.figure(figsize=(6, 4))plt.rcParams["font.size"] = 12plt.loglog(f, n_lvdt, lw=3, label="LVDT noise")plt.loglog(f, n_lvdt_pad, "--", lw=3, label="LVDT noise padded")plt.loglog(f, n_geophone, lw=3, label="Geophone noise")plt.loglog(f, n_geophone_pad, "--", lw=3, label="Geophone noise padded")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude Spectral Density ($\mu \rm{m}/\sqrt{\rm{Hz}}$)")plt.xlabel("Frequency (Hz)")plt.show()

1.3. Tutorials 53

Page 60: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Define a cost funtion

𝐽(𝜃) =1

𝑀

𝑀∑𝑖=1

[log𝑁data(𝑓𝑖)− log|𝑁model(𝑓𝑖, 𝜃)|]2 (1.18)

[4]: def cost(args, model, xdata, ydata, *model_args, **kwargs):fit = abs(model(xdata, args))return np.mean((np.log10(ydata) - np.log10(fit))**2)

[5]: # Let's fit them with ZPK model using scipy.optimize.differential_evolution# Let's choose a third-order model for LVDT and a fourth-order model for geophoneimport scipy.optimizeimport kontrol.curvefit

log_args = True # Fit the logarithm of the arguments instead.lvdt_order = 3lvdt_model = kontrol.curvefit.model.SimpleZPK(nzero=lvdt_order, npole=lvdt_order, log_→˓args=log_args)

lvdt_fit = kontrol.curvefit.CurveFit()

lvdt_fit.xdata = flvdt_fit.ydata = n_lvdt_padlvdt_fit.cost = costlvdt_fit.optimizer = scipy.optimize.differential_evolutionlvdt_bounds = [(min(f), max(f))]*lvdt_order*2 # zeros and poleslvdt_bounds.append((min(n_lvdt_pad)/10, max(n_lvdt_pad)*10)) # gainif log_args:

lvdt_bounds = [(np.log10(min(f)), np.log10(max(f)))]*lvdt_order*2lvdt_bounds.append((np.log10(min(n_lvdt_pad)/10), np.log10(max(n_lvdt_pad)*10)))

→˓# gain

lvdt_fit.optimizer_kwargs = {"workers": -1,"updating": "deferred","bounds": lvdt_bounds,

}(continues on next page)

54 Chapter 1. Features

Page 61: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

lvdt_fit.model = lvdt_modelres_lvdt = lvdt_fit.fit()

[6]: log_args = True # Fit the logarithm of the arguments instead.geophone_order = 4geophone_model = kontrol.curvefit.model.SimpleZPK(nzero=geophone_order,→˓npole=geophone_order, log_args=log_args)

geophone_fit = kontrol.curvefit.CurveFit()

geophone_fit.xdata = fgeophone_fit.ydata = n_geophone_padgeophone_fit.cost = costgeophone_fit.optimizer = scipy.optimize.differential_evolutiongeophone_bounds = [(min(f), max(f))]*geophone_order*2 # zeros and polesgeophone_bounds.append((min(n_geophone_pad)/10, max(n_geophone_pad)*10)) # gainif log_args:

geophone_bounds = [(np.log10(min(f)), np.log10(max(f)))]*geophone_order*2geophone_bounds.append((np.log10(min(n_geophone_pad)/10), np.log10(max(n_geophone_

→˓pad)*10))) # gain

# print(1)geophone_fit.optimizer_kwargs = {

"workers": -1,"updating": "deferred","bounds": geophone_bounds,

}geophone_fit.model = geophone_modelres_geophone = geophone_fit.fit()

[7]: # Inspect the fitplt.figure(figsize=(6, 4))plt.loglog(f, n_lvdt_pad, lw=3, label="LVDT noise padded")plt.loglog(f, abs(lvdt_fit.model(f, lvdt_fit.optimized_args)), "--", lw=3, label=→˓"LVDT noise ZPK fit")plt.loglog(f, n_geophone_pad, lw=3, label="Geophone noise padded")plt.loglog(f, abs(geophone_fit.model(f, geophone_fit.optimized_args)), "--", lw=3,→˓label="Geophone noise ZPK fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude Spectral Density ($\mu \rm{m}/\sqrt{\rm{Hz}}$)")plt.xlabel("Frequency (Hz)")plt.show()

1.3. Tutorials 55

Page 62: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[8]: # Extract the numerator and denominators as inital guess for a transfer function fit.lvdt_num = lvdt_fit.model.tf.minreal().num[0][0]lvdt_den = lvdt_fit.model.tf.minreal().den[0][0]geophone_num = geophone_fit.model.tf.minreal().num[0][0]geophone_den = geophone_fit.model.tf.minreal().den[0][0]

lvdt_x0 = np.append(lvdt_num, lvdt_den)geophone_x0 = np.append(geophone_num, geophone_den)

[9]: lvdt_model = kontrol.curvefit.model.TransferFunctionModel(nzero=lvdt_order, npole=lvdt_order, log_args=log_args)

lvdt_fit.optimizer = scipy.optimize.minimize

lvdt_fit.optimizer_kwargs = {"method": "Nelder-Mead","x0": lvdt_x0 if not log_args else np.log10(lvdt_x0),"options": {

"maxiter": len(lvdt_x0)*1000,"adaptive": True,

}}

lvdt_fit.model = lvdt_modelres_lvdt = lvdt_fit.fit()

[10]: geophone_model = kontrol.curvefit.model.TransferFunctionModel(nzero=geophone_order, npole=geophone_order, log_args=log_args)

geophone_fit.optimizer = scipy.optimize.minimize

geophone_fit.optimizer_kwargs = {"method": "Nelder-Mead","x0": geophone_x0 if not log_args else np.log10(geophone_x0),"options": {

"maxiter": len(geophone_x0)*1000,"adaptive": True,

}

(continues on next page)

56 Chapter 1. Features

Page 63: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

}

geophone_fit.model = geophone_modelres_geophone = geophone_fit.fit()

[11]: # Inspect the fitplt.figure(figsize=(6, 4))plt.loglog(f, n_lvdt_pad, lw=3, label="LVDT noise padded")plt.loglog(f, abs(lvdt_fit.model(f, lvdt_fit.optimized_args)), "--", lw=3, label=→˓"LVDT noise TF fit")plt.loglog(f, n_geophone_pad, lw=3, label="Geophone noise padded")plt.loglog(f, abs(geophone_fit.model(f, geophone_fit.optimized_args)), "--", lw=3,→˓label="Geophone noise TF fit")plt.legend(loc=0)plt.grid(which="both")plt.ylabel(r"Amplitude Spectral Density ($\mu \rm{m}/\sqrt{\rm{Hz}}$)")plt.xlabel("Frequency (Hz)")plt.show()

[12]: # Get the fitted transfer functions and "stablize" the poles and zeros.lvdt_fit.model.args = lvdt_fit.optimized_argslvdt_tf = lvdt_fit.model.tfgeophone_fit.model.args = geophone_fit.optimized_argsgeophone_tf = geophone_fit.model.tf

lvdt_tf = kontrol.TransferFunction(lvdt_tf)geophone_tf = kontrol.TransferFunction(geophone_tf)

# Negate unstable zeors and poleslvdt_tf.stabilize()geophone_tf.stabilize()

# Exportlvdt_tf.save("noise_lvdt.pkl")geophone_tf.save("noise_geophone.pkl")

1.3. Tutorials 57

Page 64: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3.7 Control Regulator Design

1.3.7.1 Critical Damping Regulator Design for Oscillator-like Systems

In this tutorial, we will introduce the kontrol.regulator.feedback module, and use kontrol.regulator.feedback.critical_damping() and kontrol.regulator.feedback.proportional_derivative() functions to algorithmically generate feedback controller that criticallydamps the system.

For critical damping of a oscillator, derivative controllers, i.e.

𝐾derivative(𝑠) = 𝐾𝑑𝑠 (1.19)

where 𝐾𝑑 is a the derivative gain, is sufficient for making a critically damped system.

Consider the damped-oscillator plant

𝑃 (𝑠) =𝜔2𝑛

𝑠2 + 𝜔𝑛

𝑄 𝑠+ 𝜔2𝑛

(1.20)

where 𝜔𝑛 is the resonance frequency (rad/s) of the oscillator, and 𝑄 is the quality factor / Q-factor of the oscillator.

It’s easy to show that if we would like to actively damp the oscillator to some desired Q value 𝑄desired, then thecorresponding required derivative gain is exactly

𝐾𝑑 =

1𝑄desired

− 1𝑄

𝜔𝑛|𝑃 (0)|, (1.21)

for 𝑄desired ≥ 0.5.

kontrol.regulator.feedback.critical_damp() has argument method with options "calculated" and "optimized".

For critical damping, the desired Q value is 𝑄crit = 0.5. Furthermore, if we assume that the the Q value of the systemis much greater than 0.5, then the critical damping gain can simply be approximated by

𝐾𝑑 = 𝐾crit =2

𝜔𝑛|𝑃 (0)|, (1.22)

which is extremely easy to compute.

For coupled oscillators systems with more than one modes, the critical derivative gain can’t be calculated easily, andwe cannot achieve critical damping for all modes using only simple derivative damping. If we only aim to criticallydamp the dominant mode, then the critical gain approximation is still valid, as we shall see later.

To find the the exact critical derivative gain, the method="optimized" is a better approach. It algorithically findsthe derivative gain that makes the closed-loop system has one less complex pole pairs. And it finds the gain such thatthe complex pole pairs just becomes two simple poles, i.e. critically damped.

In this tutorial, we will design (algorithmically) the control system to damp a coupled oscillator system with twomodes, i.e.

𝑃 (𝑠) = 𝐺1𝜔21

𝑠2 + 𝜔1/𝑄1𝑠+ 𝜔21

+𝐺2𝜔22

𝑠2 + 𝜔2/𝑄2𝑠+ 𝜔22

, (1.23)

and we will compaire the open-loop plant with the closed-loop plant.

𝑃 (𝑠)

1 +𝐾(𝑠)𝑃 (𝑠)(1.24)

which is the transfer function from external disturbance to displacement.

58 Chapter 1. Features

Page 65: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[9]: import numpy as npimport controlimport matplotlib.pyplot as pltimport kontrol.regulator

s = control.tf("s")g1 = 100w1 = 1q1 = 100g2 = 10w2 = 3q2 = 50

plant = g1*w1**2 / (s**2 + w1/q1*s + w1**2) + g2*w2**2 / (s**2 + w2/q2*s + w2**2)

f = np.logspace(-2, 1, 1000)plt.figure(figsize=(10, 7))plt.subplot(211, title="Bode plot")plt.loglog(f, abs(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(212)plt.semilogx(f, np.angle(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

1.3. Tutorials 59

Page 66: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

[10]: plant

[10]: 190𝑠2 + 6.9𝑠+ 990

𝑠4 + 0.07𝑠3 + 10𝑠2 + 0.15𝑠+ 9

Here, we will make two feedback regulators from kontrol.regulator.feedback.critical_damping(),one for method="calculated", another one for method="optimized".

[11]: k = kontrol.regulator.feedback.critical_damping(plant)k

[11]: 0.01671𝑠

1

[2]: k_calculated = kontrol.regulator.feedback.critical_damping(plant, method="calculated")k_optimized = kontrol.regulator.feedback.critical_damping(plant) # "optimized" is→˓the default methodprint("k_calculated: ", k_calculated)print("k_optimized: ", k_optimized)

k_calculated:0.01818 s---------

1

k_optimized:(continues on next page)

60 Chapter 1. Features

Page 67: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

0.01671 s---------

1

As can be seen, the approximated critical gain is very close to the optimized one.

Now, we check the open-loop transfer functions.

[3]: oltf_calculated = k_calculated * plantoltf_optimized = k_optimized * plant

plt.figure(figsize=(10, 7))plt.subplot(211, title="Bode plot")plt.loglog(f, abs(oltf_calculated(1j*2*np.pi*f)), label="OLTF with calculated→˓derivative controller")plt.loglog(f, abs(oltf_optimized(1j*2*np.pi*f)), label="OLTF with optimized→˓derivative controller")plt.hlines(1, min(f), max(f), color="k", ls="--", label="Unity gain")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(212)plt.semilogx(f, np.angle(oltf_calculated(1j*2*np.pi*f)), label="OLTF with calculated→˓derivative controller")plt.semilogx(f, np.angle(oltf_optimized(1j*2*np.pi*f)), label="OLTF with optimized→˓derivative controller")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

1.3. Tutorials 61

Page 68: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

From now one we will consider the optimized version only.

Let’s compare the open-loop and closed-loop frequency response.

[12]: closed_loop = plant / (1+oltf_optimized)k_overdamp = 2 * k_optimizedk_underdamp = 1/2 * k_optimized

closed_loop_overdamp = plant / (1 + k_overdamp*plant)closed_loop_underdamp = plant / (1 + k_underdamp*plant)

plt.figure(figsize=(10, 7))plt.subplot(211, title="Bode plot")plt.loglog(f, abs(plant(1j*2*np.pi*f)), label="Open loop")plt.loglog(f, abs(closed_loop_overdamp(1j*2*np.pi*f)), label="Closed loop, overdamping→˓")plt.loglog(f, abs(closed_loop_underdamp(1j*2*np.pi*f)), label="Closed loop,→˓underdamping")plt.loglog(f, abs(closed_loop(1j*2*np.pi*f)), label="Closed loop, critical damping")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(212)

(continues on next page)

62 Chapter 1. Features

Page 69: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.semilogx(f, np.angle(plant(1j*2*np.pi*f)), label="Open loop")plt.semilogx(f, np.angle(closed_loop_overdamp(1j*2*np.pi*f)), label="Closed loop,→˓overdamping")plt.semilogx(f, np.angle(closed_loop_underdamp(1j*2*np.pi*f)), label="Closed loop,→˓underdamping")plt.semilogx(f, np.angle(closed_loop(1j*2*np.pi*f)), label="Close loop")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

Let’s show the root locus from 0 to k_optimized and you shall see how it works.

[5]: k_max = k_optimized.num[0][0][0]for k in np.linspace(0, k_max, 10):

plt.plot((plant/(1+k*s*plant)).minreal().pole().real, (plant/(1+k*s*plant)).→˓minreal().pole().imag, "kx")

1.3. Tutorials 63

Page 70: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Here we compare the poles as well to show that one of the modes is critically damped. (Look at the last two poles)

[6]: plant.pole()

[6]: array([-0.03 +2.99985j , -0.03 -2.99985j , -0.005+0.9999875j,-0.005-0.9999875j])

[7]: closed_loop.minreal().pole()

[7]: array([-0.45683853+2.5338248j, -0.45683853-2.5338248j,-1.16528417+0.j , -1.16510385+0.j ])

Now let’s show the step-response. We will also compare the cases that are slightly off critical gain.

[14]: t = np.linspace(0, 32, 1024)

_, step_open_loop = control.step_response(plant, T=t)_, step_closed_loop_overdamp = control.step_response(closed_loop_overdamp, T=t)_, step_closed_loop_underdamp = control.step_response(closed_loop_underdamp, T=t)

_, step_closed_loop = control.step_response(closed_loop, T=t)

plt.title("Step response")plt.plot(t, step_open_loop, label="Open loop")plt.plot(t, step_closed_loop_overdamp, label="Closed loop, overdamping")plt.plot(t, step_closed_loop_underdamp, "--", label="Closed loop, underdamping")plt.plot(t, step_closed_loop, label="Closed loop, critical damping")plt.legend(loc=0)plt.xlabel("Time (s)")plt.show()

64 Chapter 1. Features

Page 71: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.3.7.2 Algorithmic Control Design for Oscillator-Like Systems

In this tutorial, we will use kontrol.regulator.oscillator submodule to make control regulators foroscillator-like systems.

kontrol.regulator.oscillator.pid_oscillator() is a function that makes PID-like controllers algo-rithmically based on simple critiera:

1. It returns a derivative controller that critically damps the osciallator.

2. Then it adds proportional/integral (or both) controller such that they match the first UGF of the derivative control.Alternatively, DC gain or integrator time constants can be specified to override the function.

In this tutorial, we will make PID, PD, D control regulators to control the plant

𝑃 (𝑠) = 𝐺1𝜔21

𝑠2 + 𝜔1/𝑄1𝑠+ 𝜔21

+𝐺2𝜔22

𝑠2 + 𝜔2/𝑄2𝑠+ 𝜔22

, (1.25)

where 𝐺s are DC gains, 𝜔s are resonance frequencies, and 𝑄s are quality factors.

The regulator is defined as𝐾PID(𝑠) = 𝐾𝑝 +𝐾𝑖/𝑠+𝐾𝑑𝑠 , (1.26)

where 𝐾𝑝, 𝐾𝑖, and 𝐾𝑑 are the proportional, integral, and derivative gains respectively.

[4]: import numpy as npimport controlimport matplotlib.pyplot as pltimport kontrol.regulator

## define the plant heres = control.tf("s")g1 = 100w1 = 1q1 = 100g2 = 10w2 = 3q2 = 50

(continues on next page)

1.3. Tutorials 65

Page 72: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plant = g1*w1**2 / (s**2 + w1/q1*s + w1**2) + g2*w2**2 / (s**2 + w2/q2*s + w2**2)

f = np.logspace(-2, 1, 1000)plt.figure(figsize=(6, 4))plt.subplot(211, title="Bode plot")plt.loglog(f, abs(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

plt.subplot(212)plt.semilogx(f, np.angle(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

[2]: regulator_pid = kontrol.regulator.oscillator.pid(plant, regulator_type="PID")regulator_pd = kontrol.regulator.oscillator.pid(plant, regulator_type="PD")# regulator_pi = kontrol.regulator.oscillator.pid(plant, regulator_type="PI")# regulator_i = kontrol.regulator.oscillator.pid(plant, regulator_type="I")regulator_d = kontrol.regulator.oscillator.pid(plant, regulator_type="D")

oltf_pid = regulator_pid * plantoltf_pd = regulator_pd * plant# oltf_pi = regulator_pi * plant# oltf_i = regulator_i * plantoltf_d = regulator_d * plant

plt.title("Open Loop Transfer Function")plt.loglog(f, abs(oltf_pid(1j*2*np.pi*f)), label="PID control")plt.loglog(f, abs(oltf_pd(1j*2*np.pi*f)), label="PD control")# plt.loglog(f, abs(oltf_pi(1j*2*np.pi*f)), label="PI control")# plt.loglog(f, abs(oltf_i(1j*2*np.pi*f)), label="I control")

(continues on next page)

66 Chapter 1. Features

Page 73: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.loglog(f, abs(oltf_d(1j*2*np.pi*f)), label="D control")plt.hlines(1, min(f), max(f), color="k", ls="--", label="Unity Gain")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Magnitude")plt.xlabel("Frequency (Hz)")

[2]: Text(0.5, 0, 'Frequency (Hz)')

[3]: t = np.linspace(0, 64, 1024)

# u = np.zeros_like(t)u = t > 32u = u.astype(float)

cltf_pid = oltf_pid / (1+oltf_pid)cltf_pd = oltf_pd / (1+oltf_pd)# cltf_pi = oltf_pi / (1+oltf_pi)# cltf_i = oltf_i / (1+oltf_i)cltf_d = oltf_d / (1+oltf_d)

_, step_plant = control.forced_response(plant, U=u, T=t)_, step_pid = control.forced_response(cltf_pid, U=u, T=t)_, step_pd = control.forced_response(cltf_pd, U=u, T=t)# _, step_pi = control.forced_response(cltf_pi, U=u, T=t)# _, step_i = control.forced_response(cltf_i, U=u, T=t)_, step_d = control.forced_response(cltf_d, U=u, T=t)plt.title("Step response")

# plt.plot(t, step_plant, label="Plant")plt.plot(t, step_pid, label="Closed loop, PID control")plt.plot(t, step_pd, label="Closed loop, PD control")# plt.plot(t, step_pi, label="Closed loop, PI control")# plt.plot(t, step_i, label="Closed loop, I control")plt.plot(t, step_d, label="Closed loop, D control")plt.plot(t, u, "k--", label="Setpoint")

(continues on next page)

1.3. Tutorials 67

Page 74: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.legend(loc=0)plt.xlabel("Time (s)")plt.show()

1.3.7.3 Algorithmic Post-Filtering

In this tutorial, we will demonstrate the use of kontrol.regulator.post_filter to add notch filters andlow-pass filters as post-filters for post-regulator designs.

We will inherit the a similar oscillatory plant from the “Algorithmic Control Design for Oscillator-Like Systems”tutorial. In the plant, there’re two oscillatory mode, one at 0.1 Hz and another at 1 Hz.

[1]: import numpy as npimport controlimport matplotlib.pyplot as pltimport kontrol.regulator

## define the plant heres = control.tf("s")g1 = 100w1 = 0.1 * (2*np.pi)q1 = 100g2 = 1w2 = 1 * (2*np.pi)q2 = 50

plant = g1*w1**2 / (s**2 + w1/q1*s + w1**2) + g2*w2**2 / (s**2 + w2/q2*s + w2**2)

f = np.logspace(-2, 1, 1000)plt.figure(figsize=(6, 4))plt.subplot(211, title="Bode plot")plt.loglog(f, abs(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Amplitude")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

(continues on next page)

68 Chapter 1. Features

Page 75: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.subplot(212)plt.semilogx(f, np.angle(plant(1j*2*np.pi*f)), label="Plant")plt.ylabel("Phase (rad)")plt.xlabel("Frequency (Hz)")plt.legend(loc=0)plt.grid(which="both")

Suppose we don’t want to damp the 3 rad/s mode and we would like to add a second-order low-pass filter to filter highfrequency noise (Which doesn’t exist in this tutorial but may exist in real systems).

We can use kontrol.regulator.post_filter.post_notch() to create a list of notch filters that com-pletely cancels the poles that we don’t want to damp. We need to specify the plant, the regulator,and notch_peaks_above parameters. notch_peaks_above specifies the frequency threshold (in Hz).kontrol.regulator.post_filter.post_notch() will return a list of notch filters to cancel modes abovenotch_peaks_above.

We can use kontrol.regulator.post_filter.post_low_pass() optimize a low-pass filter according toa specified phase margin (default 45 degrees). We need to specify the plant, the regulator, any post_filterssuch as the notches, and optionally, ignore_ugf_above parameters. The function will optimize a cutoff frequencyof a low-pass filter to make all unity gain frequencies (UGFs) lower than ignore_ugf_above to meet the spec-ified phase margin. If this is not specified, all UGFs are taken into account. UGFs that are originally lower thanthe phase margin will be ignored. The low-pass filter is defaulted to be kontrol.regulator.predefined.low_pass(), which is a simple 𝑛th-order low-pass filter.

[2]: # Make regulator hereregulator = kontrol.regulator.oscillator.pid(plant, regulator_type="PID")

notch_peaks_above = 2 / (2*np.pi) # We want to notch the 3 rad/s modeignore_ugf_above = None # Let's just not ignore anything.

notch_list = kontrol.regulator.post_filter.post_notch(plant, regulator, notch_peaks_above=notch_peaks_above)

notch = np.prod(notch_list) # Combine all notch filters

kwargs = {"order": 2} # keyword arguments for kontrol.regulator.predefined.low_pass.→˓We want 2nd-order low-pass.

(continues on next page)

1.3. Tutorials 69

Page 76: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

low_pass = kontrol.regulator.post_filter.post_low_pass(plant, regulator, post_filter=notch, ignore_ugf_above=ignore_ugf_above, **kwargs)

oltf = regulator * plantoltf_notch = regulator * plant * notch_, pms, _, _, ugf, _ = control.stability_margins(oltf_notch, returnall=True)# print(ugf/2/np.pi)# print(pms)oltf_notch_lp = regulator * plant * notch * low_pass

plt.figure(figsize=(12, 8))plt.subplot(221)plt.title("Open Loop Transfer Function")plt.loglog(f, abs(oltf(1j*2*np.pi*f)), label="PID only")plt.loglog(f, abs(oltf_notch(1j*2*np.pi*f)), label="PID + notch")plt.loglog(f, abs(oltf_notch_lp(1j*2*np.pi*f)), label="PID + notch + low-pass")plt.vlines(ugf/2/np.pi, 0.1, 10, color="k", label="Unity gain frequency")plt.hlines(1, min(f), max(f), ls="--", color="k", label="Unity gain")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Magnitude")plt.xlabel("Frequency (Hz)")

plt.subplot(222)plt.title("Regulator")plt.loglog(f, abs(regulator(1j*2*np.pi*f)), label="PID only")# plt.loglog(f, abs((notch)(1j*2*np.pi*f)), label="Notch")# plt.loglog(f, abs((low_pass)(1j*2*np.pi*f)), label="Low-pass")plt.loglog(f, abs((regulator*notch)(1j*2*np.pi*f)), label="PID + notch")plt.loglog(f, abs((regulator*notch*low_pass)(1j*2*np.pi*f)), label="PID Regulator")plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Magnitude")plt.xlabel("Frequency (Hz)")

plt.subplot(223)plt.semilogx(f, 180/np.pi*np.angle(oltf(1j*2*np.pi*f)), label="PID only")plt.semilogx(f, 180/np.pi*np.angle(oltf_notch(1j*2*np.pi*f)), label="PID + notch")plt.semilogx(f, 180/np.pi*np.angle(oltf_notch_lp(1j*2*np.pi*f)), label="PID + notch +→˓low-pass")plt.vlines(ugf/2/np.pi, -180, 180, color="k", label="Unity gain frequency")plt.hlines(-135, min(f), max(f), ls="--", color="r", label="Target phase margin")plt.hlines(-180, min(f), max(f), ls="--", color="k", label="-180 degrees")

plt.legend(loc=0)plt.grid(which="both")plt.ylabel("Phase")plt.xlabel("Frequency (Hz)")

plt.subplot(224)plt.semilogx(f, 180/np.pi*np.angle(regulator(1j*2*np.pi*f)), label="PID only")plt.semilogx(f, 180/np.pi*np.angle((regulator*notch)(1j*2*np.pi*f)), label="PID +→˓notch")plt.semilogx(f, 180/np.pi*np.angle((regulator*notch*low_pass)(1j*2*np.pi*f)), label=→˓"PID + notch + low-pass")plt.legend(loc=0)plt.grid(which="both")

(continues on next page)

70 Chapter 1. Features

Page 77: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

(continued from previous page)

plt.ylabel("Phase")plt.xlabel("Frequency (Hz)")

[2]: Text(0.5, 0, 'Frequency (Hz)')

1.4 Main Utilities

Be sure to check out Tutorials for example usages of these utilities!

1.4.1 Complementary Filter Synthesis

Optimal complementary filter synthesis using ℋ∞ methods.

class kontrol.ComplementaryFilter(noise1=None, noise2=None, weight1=None,weight2=None, filter1=None, filter2=None, f=None)

Bases: object

Complementary filter synthesis class.

Parameters

• noise1 (TransferFunction) – Sensor noise 1 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 1.

• noise2 (TransferFunction) – Sensor noise 2 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 2.

1.4. Main Utilities 71

Page 78: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• weight1 (TransferFunction, optional) – Weighting function 1. Frequency de-pendent specification for noise 1. Defaults None.

• weight2 (TransferFunction, optional) – Weighting function 2. Frequency de-pendent specification for noise 2. Defaults None.

• filter1 (TransferFunction, optional) – The complementary filter for noise 1.Defaults None.

• filter2 (TransferFunction, optional) – The complementary filter for noise 2.Defaults None.

• f (array, optional) – The frequency axis in Hz for evaluating the super sensor noise.Defaults None.

h2synthesis()Complementary filter synthesis that minimizes the ℋ2-norm of the super sensor noise. The noise must bespecified before using this method.

hinfsynthesis()Complementary filter synthesis that minimizes the ℋ∞-norm of the super sensor noise. The noise must bespecified before using this method.

Notes

This is a utility class for complementary filter synthesis, sensor noise estimation and analysis. To use synthesismethods, specify noise1 and noise2 as the transfer function modesl for the sensor noises, and specifyweight1 and weight2 as the frequency dependent specification for the two sensor noises.

References

fThe frequency axis in Hz.

filter1First complementary filter.

filter2Second complementary filter.

h2synthesis()Synthesize complementary filters using H2 synthesis.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise 1.

• filter2 (TransferFunction) – The complementary filter filtering noise 2.

hinfsynthesis()Synthesize complementary filters using H-inifinity synthesis.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise 1.

• filter2 (TransferFunction) – The complementary filter filtering noise 2.

noise1Transfer munction model of sensor noise 1.

72 Chapter 1. Features

Page 79: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

noise2Transfer munction model of sensor noise 2.

noise_super(f=None, noise1=None, noise2=None, filter1=None, filter2=None)Compute and return predicted the ASD of the super sensor noise

f [array, optional] The frequency axis in Hz. Use self.f if None. Defaults None.

noise1 [array, optional] The amplitude spectral density of noise 1. Use self.noise1 and self.f to estimate ifNone. Defaults None.

noise2 [array, optional] The amplitude spectral density of noise 2. Use self.noise1 and self.f to estimate ifNone. Defaults None.

filter1 [TransferFunction, optional] The complementary filter for filtering noise1 Use self.filter1 if notspecified. Defaults None

filter2 [TransferFunction, optional] The complementary filter for filtering noise1 Use self.filter2 if notspecified. Defaults None

Returns The amplitude spectral density of the super sensor noise.

Return type array

1.4.2 Frequency Series Class

Frequency domain and transfer function data modeling

class kontrol.FrequencySeries(f, x)Bases: object

Frequency series class

fFrequency axis

Type array

xFrequency series data.

Type array

x_empiricalFrequency series of the empirical model.

Type array or None

model_empiricalThe model function whose first argument is the frequency axis and the rest being an arbitrary number ofmodel parameters to be fit.

Type func(f, a, b, c, ..) -> array or None

args_empirical_modelThe optimized arguments passed to the model_empirical.

Type array or None

f_zpkThe frequency axis used during ZPK fitting.

Type array or None

1.4. Main Utilities 73

Page 80: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

x_zpkThe frequency series data used during ZPK fitting. Note: this is a complex array.

Type array or None

tf_zpkThe transfer function the best fit the data using ZPK model.

Type control.xferfcn.TransferFunction or None

args_zpk_modelA 1-D list of zeros, poles, and gain. Zeros and poles are in unit of Hz.

Type array or None

f_tfThe frequency series data used during transfer function fitting.

Type array or None

x_tfThe frequency series data used during transfer function fitting. Note: this is a complex array.

Type array or None

tfThe modeled transfer function.

Type control.xferfcn.TransferFunction or None

args_tf_modelA 1-D list of numerator and denominator coefficients, from higher order to lower order.

Type array or None

fit_empirical(model, x0=None, error_func=<function log_mse>, error_func_kwargs={}, opti-mizer=<function minimize>, optimizer_kwargs={})

Fit frequency series data with an empirical model. (Local optimize)

Parameters

• model (func(f, a, b, c, ..) -> array) – The model function whose firstargument is the frequency axis and the rest being an arbitrary number of model parametersto be fit.

• x0 (array, optional) – The initial parameters for fitting. Defaults None. If None,defaults to np.ones()

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.minimize.

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“method”: “Powell”, “x0”=x0} if optimizeris default.

Returns res – The result of the fitting (optimization)

Return type scipy.optimize.OptimizeResult

74 Chapter 1. Features

Page 81: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

fit_tf(x0=None, log_var=True, error_func=<function log_mse>, error_func_kwargs={}, opti-mizer=<function minimize>, optimizer_kwargs={})

Fit frequency series with a transfer function model

Parameters

• x0 (array or None, optional) – The initial parameters defining the initial trans-fer function. A 1-D list of numerator and denominator coefficients, from higher order tolower order. Defaults None. If None, will use the result from the ZPK fitting.

• log_var (boolean, optional) – Optimize the log of variables instead. Useful forvariables that have very large dynamic range Defaults True.

• error_func (func(x1: array, x2: array) -> float, optional) –The function that evaluate the error between arrays x1 and x2. Defaults to kon-trol.core.math.log_mse, which evaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.minimize.

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“method”: “Powell”, “x0”: x0} if optimizeris default.

Returns res – The result of the optimization.

Return type scipy.optimize.OptimizerResult

Note: Only use this after using fit_zpk().

fit_zpk(order, fit=’x_empirical’, padding=False, padding_order=1.0, error_func=<functionlog_mse>, error_func_kwargs={}, optimizer=<function differential_evolution>, opti-mizer_kwargs={})

Fit frequency series with a zpk model.

Parameters

• order (int) – The order of the ZPK model to be used.

• fit (str, optional) – Choose the data to be fit, choose from [“x”, “x_empirical”].

• padding (boolean, optional) – Pad the data by continuous white noise. DefaultsFalse.

• padding_order (float, optional) – The number of decades to be padded onboth sides of the series. Defaults to 1.

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.differential_evolution.

1.4. Main Utilities 75

Page 82: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“workers”: -1, “updating”:”deferred”} ifoptimizer is default.

Returns res – The result of the optimization.

Return type scipy.optimize.OptimizerResult

Note: For now, we support models with equal number of zeros and poles only.

Best use with log-space frequency series or after using fit_empirical().

1.4.3 Sensors and Actuators Utilities

1.4.3.1 Sensing Matrix Classes

Sensing matrix and diagonalization.

class kontrol.SensingMatrix(matrix, coupling_matrix=None, *args, **kwargs)Bases: kontrol.sensact.matrix.Matrix

Base class for sensing matrices

diagonalize(coupling_matrix=None)Diagonalize the sensing matrix, given a coupling matrix.

Parameters coupling_matrix (array, optional.) – The coupling matrix. If None,self.coupling_matrix will be used. Default None.

Returns The new sensing matrix.

Return type array

Notes

The coupling matrix has (i, j) elements as coupling ratios x_i/x_j. For example, consider the 2-sensorconfiguration: I have a coupled sensing readout 𝑥1,coupled that reads 𝑥1,coupled = 𝑥1 + 0.1𝑥2. and, I haveanother coupled sensing readout 𝑥2,coupled that reads 𝑥2,coupled = −0.2𝑥1+𝑥2. Then, the coupling matrixis [

1 0.1−0.2 1

].

1.4.3.2 Optical Lever Sensing Matrices

Sensing matrices for optical levers in KAGRA.

class kontrol.OpticalLeverSensingMatrix(*args, **kwargs)Bases: kontrol.sensact.matrix.SensingMatrix

Optical lever sensing matrix base class.

This is a sensing matrix that maps tilt-sensing QPD and length-sensing QPD (placed behind a lens) readouts tothe suspended optics’ longitudinal, pitch, and yaw displacements.

Parameters

76 Chapter 1. Features

Page 83: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• r_h (float) – Lever arm from the optics to the tilt-sensing QPD plane on the horizontalplane (amplifying yaw).

• r_v (float) – Lever arm from the optics to the tilt-sensing QPD plane on the verticalplane (amplifying pitch).

• alpha_h (float) – Angle of incidence on the horizontal plane.

• alpha_v (float) – Angle of incidence on the vertical plane.

• r_lens_h (float, optional) – Lever arm from optics to the lens on the horizontalplane. Defaults None. Specify if it exists.

• r_lens_v (float, optional) – Lever arm from optics to the lens on the verticalplane. Defaults None. Specify if it exists.

• d_h (float, optional) – Horizontal distance from the lens to the length-sensing QPD.Defaults None.

• d_v (float, optional) – Vertical distance from the lens to the length-sensing QPD.Defaults None.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane.

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Defaults None.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Defaults None.

• f_h (float, optional,) – Focal length of the lens projected to the horizontal plane.Defaults np.inf (no lens).

• f_v (float, optional,) – Focal length of the lens projected to the vertical plane.Defaults np.inf (no lens).

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

Notes

We’re using equation (29) from [1]_.

⎛⎝𝑥𝐿

𝜃𝑃𝜃𝑌

⎞⎠ = CmiscenterCalignCrotation

⎛⎜⎜⎝𝑥tilt

𝑦tilt𝑥len

𝑦len

⎞⎟⎟⎠ ,

where 𝑥𝐿 is the longitudinal displacement of the optics, 𝜃𝑃 is the pitch angular displacement of the optics, 𝜃𝑌is the yaw angular displacement of the optics, 𝑥tilt is the horizontal displacement of the beam spot at the tilt-sensing QPD plane, 𝑦tilt is the vertical displacement of the beam spot at the tilt-sensing QPD plane, 𝑥len is the

1.4. Main Utilities 77

Page 84: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

horizontal displacement of the beam spot at the length-sensing QPD plane, 𝑦len is the vertical displacement ofthe beam spot at the length-sensing QPD plane,

Crotation =

⎡⎢⎢⎣cos𝜑tilt sin𝜑tilt 0 0− sin𝜑tilt cos𝜑tilt 0 0

0 0 cos𝜑len sin𝜑len

0 0 − sin𝜑len cos𝜑len

⎤⎥⎥⎦ ,

Calign =

⎡⎢⎢⎢⎢⎣2 sin𝛼ℎ 0 2𝑟ℎ2 sin𝛼𝑣 2𝑟𝑣 0

2 sin𝛼ℎ

(1− 𝑑ℎ

𝑓ℎ

)0 2

[(1− 𝑑ℎ

𝑓ℎ

)𝑟lens,ℎ + 𝑑ℎ

]2 sin𝛼𝑣

(1− 𝑑𝑣

𝑓𝑣

)2[(

1− 𝑑𝑣

𝑓𝑣

)𝑟lens,𝑣 + 𝑑𝑣

]0

⎤⎥⎥⎥⎥⎦+

,

Cmiscenter =

⎡⎣1 𝛿𝑦 𝛿𝑥0 1 00 0 1

⎤⎦−1

,

𝜑tilt is the angle between the tilt-sensing QPD and the yaw-pitch frame, 𝜑len is the angle between the length-sensing QPD and the yaw-pitch frame, 𝑟ℎ is the lever arm on the horzontal plane, 𝑟𝑣 is the lever arm on thevertical plane, 𝛼ℎ is the angle of incidence on the horizontal plane, 𝛼𝑣 is the angle of incidence on the verticalplane, 𝑟lens,ℎ is the lever arm between the optics and the lens on the horizontal plane, 𝑟lens,𝑣 is the lever armbetween the optics and the lens on the vertical plane, 𝑑ℎ is the distance between the lens and the length-sensingQPD on the horizontal plane, 𝑑𝑣 is the distance between lens and the length-sensing QPD on the vertical plane,and 𝑓 is the focal length of the convex lens.

References

alpha_hAngle of incidence on the horizontal plane.

alpha_vAngle of incidence on the vertical plane.

d_hHorizontal distance from the lens to the length-sensing QPD.

d_vVertical distance from the lens to the length-sensing QPD.

delta_xHorizontal miscentering of the beam spot at the optics plane.

delta_yHorizontal miscentering of the beam spot at the optics plane.

f_hFocal length of the convex lens projected on the horizontal plane.

f_vFocal length of the convex lens projected on the vertical plane.

formatFormat of the sensing matrix.

Choose from

“OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen with input (TILT_PIT,TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal, pitch and yaw).

“xy”: Matrix as shown in [1]_.

78 Chapter 1. Features

Page 85: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

References

phi_lenAngle from the length-sensing QPD frame to the yaw-pitch frame.

phi_tiltAngle from the tilt-sensing QPD frame to the yaw-pitch frame.

r_hLever arm from optics to tilt-sensing QPD on the horizontal plane.

r_lens_hLever arm from optics to the lens on the horizontal plane.

r_lens_vLever arm from optics to the lens on the vertical plane.

r_vLever arm from optics to tilt-sensing QPD on the vertical plane.

update_matrices_decorator()Update matrices and self upon setting new parameters.

1.4.3.3 Horizontal Optical Lever Sensing Matrices

Sensing matrices for horizontal optical levers (Type-A, Type-Bp) in KAGRA.

class kontrol.HorizontalOpticalLeverSensingMatrix(*args, **kwargs)Bases: kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

Horizontal optical lever sensing matrix.

Parameters

• r (float) – Lever arm.

• alpha_h (float) – Angle of incidence on the horizontal plane.

• r_lens (float, optional) – Lever arm from the optics to the convex lens. Default0.

• f (float, optional) – Focal length of the convex lens. Default np.inf.

• alpha_v (float, optional) – Angle of incidence on the vertical plane. Default 0.

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Default 0.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Default 0.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane. Default 0.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane. Default 0.

• delta_d (float, optional) – Misplacement of the length-sensing QPD. Default 0.

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

1.4. Main Utilities 79

Page 86: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

• *args – Variable length arguments passed to OpticalLeverSensingMatrix.

• **kwargs – Keyword arguments passed to OpticalLeverSensingMatrix.

delta_dMisplacement of the length-sensing QPD.

fFocal length of the convex lens.

rLever arm.

r_lensLever arm from the optics to the convex lens.

1.4.3.4 Vertical Optical Lever Sensing Matrices

Sensing matrices for vertical optical levers (Type-B) in KAGRA.

class kontrol.VerticalOpticalLeverSensingMatrix(*args, **kwargs)Bases: kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

Vertical optical lever sensing matrix.

Parameters

• r (float) – Lever arm.

• alpha_v (float) – Angle of incidence on the vertical plane.

• r_lens (float, optional) – Lever arm from the optics to the convex lens. Default0.

• f (float, optional) – Focal length of the convex lens. Default np.inf.

• alpha_h (float, optional) – Angle of incidence on the horizontal plane. Default 0.

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Default 0.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Default 0.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane. Default 0.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane. Default 0.

• delta_d (float, optional) – Misplacement of the length-sensing QPD. Default 0.

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

80 Chapter 1. Features

Page 87: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• *args – Variable length arguments passed to OpticalLeverSensingMatrix.

• **kwargs – Keyword arguments passed to OpticalLeverSensingMatrix.

delta_dMisplacement of the length-sensing QPD.

fFocal length of the convex lens.

rLever arm.

r_lensLever arm from the optics to the convex lens.

1.4.3.5 Sensor Calibration Functions

Functions for calibrating sensor readouts.

Calibration library for calibrating sensors from sensors measurements

kontrol.sensact.calibration.calibrate(xdata, ydata, method=’linear’, **kwargs)Fit the measurement data and returns the slope of the fit.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• method (str, optional) –

The method of the fit. Choose from [“linear”, “erf”].

– ”linear”: use kontrol.sensact.calibration.calibrate_linear()

– ”erf”: use kontrol.sensact.calibration.calibrate_erf()

• **kwargs – Keyword arguments passed to the calibration methods.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The y-intercept of the fitted straight line.

• linear_range (float, optional) – The range of y where the sensor considered linear.

kontrol.sensact.calibration.calibrate_erf(xdata, ydata, nonlinearity=5, re-turn_linear_range=False, re-turn_model=False)

Fit an error function and return the 1st-order slope and intercept.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• nonlinearity (float, optional) – The specification of non-linearity (%). De-fined by the maximum deviation of the full_range. Default 5.

• return_linear_range (boolean, optional) – If True, return the linear range ofydata.

1.4. Main Utilities 81

Page 88: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• return_model (boolean, optional) – Return a kontrol.curvefit.model.Model ob-ject with the fitted parameters.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The 𝑦-intercept of the fitted straight line.

• linear_range (float, optional) – The range of x where the sensor considered linear.

Notes

Credits to Kouseki Miyo, the inventor of this method.

We fit the following function

𝑓(𝑥; 𝑎,𝑚, 𝑥0, 𝑦0) = 𝑎 erf(𝑚(𝑥− 𝑥0)) + 𝑦0

where 𝑎,𝑚, 𝑥0, 𝑦0 are some parameters to be found. The erf(𝑥) function is defined as

erf(𝑥) =2√𝜋

∫ 𝑥

0

𝑒−𝑥2

𝑑𝑥 .

If we taylor expand the exponential function and take the first-order approximation of the erf(𝑥) function, weget

erf(𝑥) ≈ 2𝑎𝑚√𝜋(𝑥− 𝑥0) + 𝑦0 .

So the (inverse) calibration factor is 2𝑎𝑚√𝜋

, and the 𝑦-intercept is 2𝑎𝑚√𝜋𝑥0 + 𝑦0.

kontrol.sensact.calibration.calibrate_linear(xdata, ydata, nonlinearity=5,full_range=None, start_index=None,return_linear_range=False, re-turn_model=False)

Fit a straight line to the data and returns the slope and intercept

This functions recursively fits a straight line to chosen data points and terminates when no linear data pointremains. It starts from 3 points closest to the middle of the full-range. After fitting a straight line to the 3 points,other data points that are within the linearity specification will be included to the dataset and this dataset will beuse for the next fit. The process repeats until no data points can be added anymore.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• nonlinearity (float, optional) – The specification of non-linearity (%). De-fined by the maximum deviation of the full_range. Default 5.

• full_range (float, optional) – The full output range of the sensor. If not speci-fied, it will be chosen to be max(ydata) - min(ydata)

• start_index (int, optional) – The index of the data point to start with. Ifnot specified, it will be chosen to be index of the ydata closest to (max(ydata) +min(ydata))/2.

• return_linear_range (boolean, optional) – If True, return the linear range ofydata.

82 Chapter 1. Features

Page 89: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• return_model (boolean, optional) – Return a kontrol.curvefit.model.Model ob-ject with the fitted parameters.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The y-intercept of the fitted straight line.

• linear_range (float, optional) – The range of x where the sensor considered linear.

• model (kontrol.curvefit.model.Model) – The fitted model.

1.4.4 Spectral Analysis Functions

Spectral analysis related functions library.

Spectral analysis related function library.

kontrol.core.spectral.asd2rms(asd, f=None, df=1.0, return_series=True)Calculate root-mean-squared value from amplitude spectral density

Parameters

• asd (array) – The amplitude spectral density

• f (array, optional) – The frequency axis. Defaults None.

• df (float, optional) – The frequency spacing. Defaults 1.

• return_series (bool, optional) – Returns the RMS as a frequency series, whereeach value is the integrated RMS from highest frequency. If False, returns a single RMSvalue for the whole spectrum. Defaults True.

Returns

• array – The integrated RMS series, if return_series==True.

• float – The integrated RMS value, if return_series==False.

Notes

The integrated RMS series is defined as

𝑥RMS(𝑓) =

∫ 𝑓

∞𝑥(𝑓 ′)2 𝑑𝑓 ′ ,

where 𝑥(𝑓) is the amplitude spectral density.

When return_series is False, only 𝑥RMS(0) is returned.

kontrol.core.spectral.asd2ts(asd, f=None, fs=None, t=None, window=None, zero_mean=True)Simulate time series from amplitude spectral density

Parameters

• asd (array) – The amplitude spectral density.

• f (array, optional) – The frequency axis of the amplitude spectral density. Thisis used to calculate the sampling frequency. If fs is not specified, f must be specified.Defaults None.

1.4. Main Utilities 83

Page 90: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• fs (float, optional) – The sampling frequency in Hz. If f is specified, the lastelement of f will be treated as the Nyquist frequency so the double of it would be thesampling frequency. Defaults None.

• t (array, optional) – The desired time axis for the time series. If t is specified, thenthe time series will be interpolated using numpy.interp() assuming that the time seriesis periodic. Default None.

• window (array, optional) – The FFT window used to compute the amplitude spec-tral density. Defaults None.

• zero_mean (boolean, optional) – Returns a time series with zero mean. DefaultsTrue.

Returns

• t (array) – Time axis.

• time_series (array) – The time series

Notes

Using a custom time axis is not recommended as interpolation leads to distortion of the signal. To extend thesignal in time, it’s recommended to repeat the original time series instead.

kontrol.core.spectral.three_channel_correlation(psd1, psd2=None, psd3=None,csd12=None, csd13=None,csd21=None, csd23=None,csd31=None, csd32=None, retur-nall=True)

Noise estimation from three sensors’ readouts.

Parameters

• psd1 (array) – The power spectral density of the readout of the first sensor.

• psd2 (array, optional) – The power spectral density of the readout of the secondsensor. Defaults None.

• psd3 (array, optional) – The power spectral density of the readout of the third sen-sor. Defaults None.

• csd12 (array, optional) – Cross power spectral density between readout 1 and 2. Ifnot specified, this will be estimated as psd1*psd2/csd21. Default None.

• csd13 (array, optional) – Cross power spectral density between readout 1 and 3. Ifnot specified, this will be estimated as psd1*psd3/csd31. Default None.

• csd21 (array, optional) – Cross power spectral density between readout 2 and 1. Ifnot specified, this will be estimated as psd2*psd1/csd12. Default None.

• csd23 (array, optional) – Cross power spectral density between readout 2 and 3. Ifnot specified, this will be estimated as psd2*psd3/csd32. Default None.

• csd31 (array, optional) – Cross power spectral density between readout 3 and 1. Ifnot specified, this will be estimated as psd1*psd3/csd13. Default None.

• csd32 (array, optional) – Cross power spectral density between readout 3 and 1. Ifnot specified, this will be estimated as psd3*psd2/csd23. Default None.

• returnall (boolean, optional) – If True, return all three noise estimations. IfFalse, return noise estimation of first sensor only. Defaults True.

84 Chapter 1. Features

Page 91: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Returns

• noise1 (array) – Power spectral density of the estimated noise in psd1.

• noise2 (array, optional) – Power spectral density of the estimated noise in psd2. Returnsonly if returnall==True

• noise3 (array, optional) – Power spectral density of the estimated noise in psd3. Returnsonly if returnall==True

Notes

The PSD of the noise in psd1 is then computed as

𝑃𝑛1𝑛1(𝑓) =

𝑃𝑥1𝑥1

(𝑓)− 𝑃𝑥1𝑥3(𝑓)

𝑃𝑥2𝑥3(𝑓)𝑃𝑥2𝑥1

,

If returnall is True, at least psd1, psd2, psd3, (csd13 or csd31), (csd23 or csd32), and (csd12and csd21) must be provided.

References

kontrol.core.spectral.two_channel_correlation(psd, coh)Noise estimation from two identical sensors’ readouts.

Parameters

• psd (array) – The power spectral density of the readout of the sensor

• coh (array) – The coherence between readout of the sensor and another identical sensor.

Returns noise – Power spectral density of the estimated noise.

Return type array

Notes

The PSD of the noise is computed as

𝑃𝑛𝑛(𝑓) = 𝑃𝑥1𝑥1(𝑓)

(1− 𝐶𝑥1𝑥2

(𝑓)12

),

where 𝑃𝑥1𝑥1(𝑓) is the power spectral density of the readout and 𝐶𝑥1𝑥2

(𝑓) is the coherence between the twosensor readouts.

References

1.4.5 Foton Utilities

KAGRA/LIGO Foton related utilities.

KAGRA/LIGO Foton related utilities.

kontrol.core.foton.notch(frequency, q, depth, significant_figures=6)Returns the foton expression of a notch filter.

Parameters

• frequency (float) – The notch frequency (Hz).

1.4. Main Utilities 85

Page 92: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• q (float) – The quality factor.

• depth (float) – The depth of the notch filter (magnitude).

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

Returns The foton representation of this notch filter.

Return type str

kontrol.core.foton.tf2foton(tf, expression=’zpk’, root_location=’s’, significant_figures=6,itol=1e-25, epsilon=1e-25)

Convert a single transfer function to foton expression.

Parameters

• tf (TransferFunction) – The transfer function object.

• expression (str, optional) – Format of the foton expression. Choose from [“zpk”,“rpoly”]. Defaults to “zpk”.

• root_location (str, optional) – Root location of the zeros and poles for expres-sion==”zpk”. Choose from [“s”, “f”, “n”]. “s”: roots in s-plane, i.e. zpk([. . . ], [. . . ], . . . ,“s”). “f”: roots in frequency plane, i.e. zpk([. . . ], [„,], . . . , “f”). “n”: roots in frequencyplane but negated and gains are normalized, i.e. real parts are positive zpk([. . . ], [. . . ], . . . ,“n”). Defaults to “s”.

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

• itol (float, optional) – Treating complex roots as real roots if the ratio of theimaginary part and the real part is smaller than this tolerance Defaults to 1e-25.

• epsilon (float, optional) – Small number to add to denominator to prevent divi-sion error. Defaults to 1e-25.

Returns foton_expression – The foton expression in selected format.

Return type str

kontrol.core.foton.tf2rpoly(tf, significant_figures=6)Convert a transfer function to foton rpoly expression.

Parameters

• tf (TransferFunction) – The transfer function object

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

Returns Foton express in foton rpoly expression.

Return type str

Notes

Only works for transfer functions with less than 20 orders.

kontrol.core.foton.tf2zpk(tf, root_location=’s’, significant_figures=6, itol=1e-25, epsilon=1e-25)Convert a single transfer function to foton zpk expression.

Parameters

• tf (TransferFunction) – The transfer function object.

86 Chapter 1. Features

Page 93: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• root_location (str, optional) – Root location of the zeros and poles. Choosefrom [“s”, “f”, “n”]. “s”: roots in s-plane, i.e. zpk([. . . ], [. . . ], . . . , “s”). “f”: roots infrequency plane, i.e. zpk([. . . ], [„,], . . . , “f”). “n”: roots in frequency plane but negated andgains are normalized, i.e. real parts are positive zpk([. . . ], [. . . ], . . . , “n”). Defaults to “s”.

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

• itol (float, optional) – Treating complex roots as real roots if the ratio of theimaginary part and the real part is smaller than this tolerance Defaults to 1e-25.

• epsilon (float, optional) – Small number to add to denominator to prevent divi-sion error. Defaults to 1e-25.

Returns The foton zpk expression in selected format.

Return type str

Notes

Only works for transfer functions with less than 20 orders.

1.4.6 Curve Fitting

Curve fitting

1.4.6.1 Curve fitting class

Base class for general curve fitting.

class kontrol.curvefit.CurveFit(xdata=None, ydata=None, model=None,model_kwargs=None, cost=None, optimizer=None, opti-mizer_kwargs=None)

Bases: object

Base class for curve fitting

Parameters

• xdata (array, ydata: array) -> float) – The independent variable data / dataon the x axis. Defaults to None.

• ydata (array or None, optional) – The dependent variable data / data on the yaxis. Defaults to None.

• model (func(x: array, args: array, **kwargs) -> array, or None, optional) – The model usedto fit the data. args in model is an array of parameters that define the model. Defaults toNone

• model_kwargs (dict or None, optional) – Keyword arguments passed to themodel. Defaults to None.

• cost (kontrol.curvefit.Cost or func(args, model, xdata,ydata) -> array) – Cost function.

• xdata – The cost function to be used to fit the data. First argument is a list of parametersthat will be passed to the model. This must be pickleable if multiprocessing is to be used.Defaults to None.

1.4. Main Utilities 87

Page 94: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• optimizer (func(func, **kwargs) -> OptimizeResult, or None, optional) – The optimiza-tion algorithm use for minimizing the cost function.

• optimizer_kwargs (dict or None, optional) – Keyword arguments passed tothe optimizer function. Defaults to None.

costThe cost function to be used to fit the data.

fit(model_kwargs=None, cost_kwargs=None, optimizer_kwargs=None)Fit the data

Sets self.optimized_args and self.optimize_result.

Parameters

• model_kwargs (dict or None, optional) – Overriding keyword arguments inself.model_kwargs. Defaults to None.

• cost_kwargs (dict or None, optional) – Overriding keyword arguments inself.cost_kwargs. Defaults to None.

• optimizer_kwargs (dict or None, optional) – Overriding keyword ar-gumetns in self.optimizer_kwargs. Defaults to None.

Returns

Return type scipy.optimizer.OptimizeResult

modelThe model used to fit the data.

model_kwargsKeyword arguments passed to the model.

optimize_resultOptimization Result

optimized_argsThe optimized arguments

optimizerThe optimizer function to be used to fit the data.

optimizer_kwargsKeyword arguments passed to the optimizer function.

xdataThe independent variable data.

ydataThe dependent variable data

yfitThe fitted y values

1.4.6.2 Transfer function fitting class

Curve fitting class for transfer function fitting.

88 Chapter 1. Features

Page 95: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

class kontrol.curvefit.TransferFunctionFit(xdata=None, ydata=None, model=None,model_kwargs=None, cost=None,weight=None, error_func_kwargs=None,optimizer=None, optimizer_kwargs=None,options=None, x0=None)

Bases: kontrol.curvefit.curvefit.CurveFit

Transfer function fitting class

This class is basically a CurveFit class with default cost functions and optimizer that is designed for fittinga transfer function. By default, the error function is kontrol.curvefit.error_func.tf_error().The default optimizer is scipy.optimize.minimize( ...,method="Nelder-Mead",...), withoptions = {"adaptive": True, "maxiter": N*1000}, where N is the number of variables. Allof these can be overridden if specified.

Parameters

• xdata (array or None, optional) – Independent variable data. Defaults to None.

• ydata (array or None, optional) – Transfer function frequency response in complexnumbers. Defaults to None.

• model (func(x: ``array, args: array, **kwargs)‘‘ -> array, or None, optional)– The model used to fit the data. args in model is an array of parameters that define themodel. Defaults to None

• model_kwargs (dict or None, optional) – Keyword arguments passed to the model.Defaults to None.

• cost (kontrol.curvefit.Cost or func(args, model, xdata, ydata) -> array, optional) – Cost function. The cost function to be used to fit the data. Firstargument is a list of parameters that will be passed to the model. This must be pickleable ifmultiprocessing is to be used. Defaults to None.

• weight (array or None, optional) – Weighting function. Defaults None.

• error_func_kwargs (dict or None, optional) – Keyword arguments the will bepassed to error_func, which is passed to the construct the cost function. Defaults toNone.

• optimizer (func(func, **kwargs) -> OptimizeResult, or None, optional) –The optimization algorithm use for minimizing the cost function.

• optimizer_kwargs (dict or None, optional) – Keyword arguments passed to the op-timizer function. Defaults to None.

• options (dict or None, optional) – The option arguments passed to the optimizer

• x0 (array, optional) – Inital guess. Defaults to None.

optionsOption arguments passed to optimizer

weightWeighting function

x0Initial guess

1.4.6.3 Models for Curve Fitting

These classes are designed to use with kontrol.curvefit

1.4. Main Utilities 89

Page 96: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

class kontrol.curvefit.model.Model(args=None, nargs=None, log_args=False)Bases: object

Model base class for curve fitting

argsModel parameters

nargsNumber of model parameters

class kontrol.curvefit.model.TransferFunctionModel(nzero, npole, args=None,log_args=False)

Bases: kontrol.curvefit.model.model.Model

Transfer function model class defined by numerator and denominator

Parameters

• nzero (int) – Number of zeros.

• npole (int) – Number of poles.

• args (array or None, optional.) – The model parameters. Structured as fol-lows: [b_n, b_n-1,. . . , b_1, b_0, a_m, a_m-1,. . . , a_1, a_0], where b and a are the coeffi-cients of the numerator and denominator respectively, ordered from higher-order to lower-order. Defaults to None.

tfThe last evaluted transfer function.

Type kontrol.TransferFunction

Notes

The transfer function model is defined as

𝐺(𝑠, 𝑏𝑛, 𝑏𝑛−1, ..., 𝑏1, 𝑏0, 𝑎𝑚, 𝑎𝑚−1, ..., 𝑎1, 𝑎0) =

∑𝑛𝑖=0 𝑏𝑖𝑠

𝑖∑𝑚𝑗=0 𝑎𝑗𝑠

𝑗

denDenominator array

npoleNumber of zeros

numNumerator array

nzeroNumber of zeros

tfThe Transfer Function object.

class kontrol.curvefit.model.DampedOscillator(args=None)Bases: kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

Transfer function model for a damped oscillator.

90 Chapter 1. Features

Page 97: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Parameters args (array or None, optional.) – The model parameters with three num-bers. Structured as follows: args = [k, fn, q], where k is the DC gain of the transferfunction, fn is the resonance frequency in Hz, and q is the Q-factor. Defaults to None.

Notes

The model is definded as

𝐺(𝑠; 𝑘, 𝜔𝑛, 𝑞) = 𝑘𝜔2𝑛

𝑠2 + 𝜔𝑛

𝑞 𝑠+ 𝜔2𝑛

where 𝑘 is the DC gain of the transfer function, 𝜔𝑛 is the resonance frequency of the oscillator, and 𝑞 is theQ-factor if the damped oscillator.

argsModel parameters

damped_oscillator_argsThe model parameters with three numbers [k, fn, q]

fnResonance frequency

gainDC gain

qQ factor

class kontrol.curvefit.model.SimpleZPK(nzero, npole, args=None, log_args=False)Bases: kontrol.curvefit.model.model.Model

ZPK model with simple poles and zeros.

Parameters

• nzero (int) – Number of simple zeros.

• npole (int) – Number of simple poles.

• args (array, optional) – The model parameters defined as args = [z1, z2,..., p1, p2,..., k] where z are the locations of the zeros in Hz, p are the locationsof the pole in Hz, and k is the static gain,

• log_args (boolean, optional) – If true, model parameters passed to the model areassumed to be passed through a log10() function. So, when the real parameters will beassumed to be 10**args instead. Defaults to False.

zeroList of zeros.

Type array

poleList of poles.

Type array

gainStatic gain.

Type float

1.4. Main Utilities 91

Page 98: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

tfThe transfer function representation of this ZPK model

Type kontrol.TranserFunction

Notes

The simple ZPK model is defined as

𝐺(𝑠; 𝑧1, 𝑧2, ..., 𝑝1, 𝑝2, ..., 𝑘) = 𝑘

∏𝑖(

𝑠2𝜋𝑧𝑖

+ 1)∏𝑗(

𝑠2𝜋𝑝𝑗

+ 1)

argsModel parameters in ZPK [z1, z2,. . . , p1, p2,. . . , k] format

gainStatic gain

npoleNumber of complex pole pairs

nzeroNumber of simple zeros

poleList of poles

tfReturns a TransferFunction object of this ZPK model

zeroList of zeros

class kontrol.curvefit.model.ComplexZPK(nzero_pairs, npole_pairs, args=None,log_args=False)

Bases: kontrol.curvefit.model.model.Model

ZPK model with complex poles and zeros.

Parameters

• nzero_pairs (int) – Number of complex zero pairs.

• npole_pairs (int) – Number of complex pole pairs.

• args (array, optional) – The model parameters defined as args = [f1, q1,f2, q2,..., fi, qi,..., fn, qn, k], where f are resonance frequencies ofthe complex zero/pole pairs, q are the quality factors, and k is the static gain, i is thenumber of complex zero pairs, and n-i is the number of of complex pole pairs.

• log_args (boolean, optional) – If true, model parameters passed to the model areassumed to be passed through a log10() function. So, when the real parameters will beassumed to be 10**args instead. Defaults to False.

fn_zeroList of resonance frequencies of the complex zeros.

Type array

fn_poleList of resonance frequencies of the complex poles.

92 Chapter 1. Features

Page 99: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Type array

q_zeroList of Q-factors of the complex zeros.

Type array

q_poleList of Q-factors of the complex poles.

Type array

Notes

The complex ZPK model is defined by:

𝐺(𝑠; 𝑓𝑖, 𝑞𝑖, 𝑘) = 𝑘

∏𝑖(

𝑠2

(2𝜋𝑓𝑖)2+ 1

2𝜋𝑓𝑖𝑞𝑖𝑠+ 1)∏

𝑗(𝑠2

(2𝜋𝑓𝑗)2+ 1

2𝜋𝑓𝑗𝑞𝑗𝑠+ 1)

argsModel parameters in ZPK [f1, q1, f2, q2,. . . , fn, qn, k] format

fn_poleList of resonance frequencies of complex poles.

fn_zeroList of resonance frequencies of complex zeros.

gainStatic gain.

npole_pairsNumber of complex pole pairs

nzero_pairsNumber of complex zero pairs

q_poleList of quality factors of the complex poles

q_zeroList of quality factors of the complex zeros

tfReturns a TransferFunction object of this ZPK model

class kontrol.curvefit.model.StraightLine(args=None)Bases: kontrol.curvefit.model.model.Model

Simply a straight line.

Parameters args (array, optional) – The model parameters structued as: [slope, y-intercept]. Defaults to None.

slopeThe slope of the line.

Type float

interceptThe y-intercept of the line.

1.4. Main Utilities 93

Page 100: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Type float

Notes

The straight line is defined as

𝑦(𝑥;𝑚, 𝑐) = 𝑚𝑥+ 𝑐 ,

where 𝑚 is the slope and 𝑐 is the y-intercept.

interceptThe y-intercept of the line

slopeThe slope of the line

class kontrol.curvefit.model.Erf(args=None)Bases: kontrol.curvefit.model.model.Model

The error function erf(x)

Parameters args (array, optional) – The model parameters structured as [amplitude, slope,x0, y0]. Defaults to None.

Notes

The function is defined as

𝑓(𝑥; 𝑎,𝑚, 𝑥0, 𝑦0) = 𝑎 erf(𝑚(𝑥− 𝑥0)) + 𝑦0 ,

where erf(𝑥) is the error function [1]_, 𝑎 is the peak to peak amplitude, 𝑚 is the slope at the inflection point, 𝑥0

is the 𝑥 offset , and 𝑦0 is the 𝑦 offset.

References

amplitudePeak to Peak amplitude of the error function.

slopeSlope at the inflection point.

x_offsetx offset

y_offsety offset

1.4.7 Control Regulator Design

Functions for algorithmic design of control regulators

94 Chapter 1. Features

Page 101: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.4.7.1 Feedback Control

Algorithmic designs for feedback control regulators.

kontrol.regulator.feedback.add_integral_control(plant, regulator=None, in-tegrator_ugf=None, integra-tor_time_constant=None, **kwargs)

Match and returns an integral gain.

This function finds an integral gain such that the UGF of the integral control matches that of the specified regu-lator. If integrator_ugf or integrator_time_constant is specified instead, these will be matchedinstead.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled.

• regulator (TransferFunction, optional) – The pre-regulator Use kontrol.regulator.feedback.proportional_derivative() or kontrol.regulator.feedback.critical_damping() to make one for oscillator-likesystems.

• integrator_ugf (float, optional) – The unity gain frequency (Hz) ofthe integral control. This is the inverse of the integration time constant. Ifintegrator_time_constant is not None, then this value will be ignored. Ifset to None, it’ll be set to match the first UGF of the derivative control. Defaults to None.

• integrator_time_constant (float, optional,) – The integration time con-stant (s) for integral control. Setting this will override the integrator_ugf argument.Defaults to None.

Returns ki – The integral control gain.

Return type float

kontrol.regulator.feedback.add_proportional_control(plant, regulator=None, dc-gain=None, **kwargs)

Match and returns proportional gain.

This function finds a proportional gain such that the proportional control UGF matches the first UGF of thespecified regulator (typically a derivative control regulator). If dcgain is specified, the DC gain is matchedinstead.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• regulator (TransferFunction, optional) – The pre-regulator. If not specified,then dcgain must be specified. Defaults to None.

• dcgain (float, optional) – The desired DC gain of the open-loop transfer func-tion. If not specified, the portional gain is tuned such that the portional control’s first UGFmatches that of the derivative control. Defaults to None.

Returns kp – The proportional control gain.

Return type float

kontrol.regulator.feedback.critical_damp_calculated(plant, nmode=1, **kwargs)Find dominant mode and returns the approximate critical regulator.

Parameters

1.4. Main Utilities 95

Page 102: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• nmode (int, optional) – The ‘‘nmode‘‘th dominant mode to be damped. This num-ber must be less than the number of modes in the plant. Defaults 1.

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

Notes

The plant must contain at least one complex pole pair. The plant must have a non-zero DC gain.

The critical regulator is approximated by

𝐾(𝑠) ≈ 2

𝜔𝑛𝐾DC,

where 𝜔𝑛 is the resonance frequency of the dominant mode and 𝐾DC is the sum of DC gains of the mode andthat of the other higher-frequency modes..

kontrol.regulator.feedback.critical_damp_optimize(plant, gain_step=1.1, ktol=1e-06,**kwargs)

Optimize derivative damping gain and returns the critical regulator

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• gain_step (float, optional) – The multiplicative factor of the gain for finding thegain upper bound. It must be greater than 1. Defaults to 1.1.

• ktol (float, optional) – The tolerance for the convergence condition. The conver-gence condition is (kd_max-kd_min)/kd_min > ktol. It must be greater than 0. Defaults to1e-6.

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

Notes

Update on 2021-12-04: Use carefully. It only critically damps the mode that has the highest peak when mul-tiplied by an differentiator. Note for myself: only 2 complex poles can become simple poles for plants withsecond-order rolloff.

Only works with plants that contain at least one complex pole pair. Works best with plants that only containcomplex zeros/poles. If it returns unreasonably high gain, try lowering gain_step.

The algorithm goes as follows.

1. Find the minimum damping gain k_min such that the open-loop transfer function k_min*s*plant hasmaxmimum gain at unity gain frequency.

2. Iterate i: k_i=k_min*i*gain_step for i=1,2,3. . .

3. Terminate when 1/(1+k_i*s*plant) has less complex pole pairs than the plant itself, i.e. one modehas been overdamped. Then, define k_max=k_i.

4. Iterate: Define k_mid as the logarithmic mean of k_max and k_min. If 1/(1+k_mid*s*plant) hasless complex pole pairs than plant, i.e. overdamps, then set k_max=k_mid. Otherwise, set k_min=k_mid.

96 Chapter 1. Features

Page 103: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

5. Terminate when (k_max - k_min)/k_min < ktol, i.e. converges.

kontrol.regulator.feedback.critical_damping(plant, method=’calculated’, **kwargs)Returns the critical damping derivative control gain.

This functions calls kontrol.regulator.feedback.critical_damp_calculated() orkontrol.regulator.feedback.cricical_damp_optimized() and returns the derivativecontrol gain.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• method (str, optional) – The method to be used for setting the gain. Choose from[“optimized”, “calculated”].

”optimized”: the gain is optimized until the dominant complex pole pairs become two sim-ple poles.

”calculated”: the gain is set to 2/𝜔𝑛/𝐾𝐷𝐶 , where 𝜔𝑛 is the resonance frequency in rad/s ofthe mode to be damped, and 𝐾𝐷𝐶 is the sum of DC gains of the mode and that of the otherhigh-frequency modes.

Both method assumes that the plant has at least one pair of complex poles.

Defaults to “calculated”.

• **kwargs (dict) – Method specific keyword arguments.

See:

– ”optimized”: kontrol.regulator.feedback.critical_damp_optimized

– ”calculated”: kontrol.regulator.feedback.critical_damp_calculated

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

kontrol.regulator.feedback.mode_composition(wn, q, k)Create a plant composed of many modes.

Parameters

• wn (array) –

• (rad/s) (Frequencies) –

• q (array) – Q factors.

• k (array) – Dcgains of the modes.

Returns The composed plant.

Return type TransferFunction

kontrol.regulator.feedback.mode_decomposition(plant)Returns a list of single mode transfer functions

Parameters plant (TransferFunction) – The transfer function with at list one pair of com-plex poles.

Returns

• wn (array) – Frequencies (rad/s).

• q (array) – Q factors.

1.4. Main Utilities 97

Page 104: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• k (array) – Dcgains of the modes.

1.4.7.2 Regulator for Oscillatory Systems

Control regulators designs for oscillator-like systems

kontrol.regulator.oscillator.pid(plant, regulator_type=’PID’, dcgain=None, integra-tor_ugf=None, integrator_time_constant=None, re-turn_gain=False, **kwargs)

PID-like controller design for oscillator-like systems

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator_type (str, optional) – The type of the contorl regulator. Choosefrom {"PID", "PD", "PI", "I", "D"} for proportional-integral-derivative,proportional-derivative, proportional-integral, or derivative (velocity) control respectively.Defaults to “PID”.

• dcgain (float, optional) – The DC gain of the OLTF of the proportional con-trol. If set to None, it will be set automatically depending on the type of the controller.If regulator_type=="PI", then dcgain will be set such that the proportional controlUGF matches that of the integral control. If regulator_type=="PD" or "PID",then the dcgain will be set such that it matches the UGF of the derivative control. Defaultsto None.

• integrator_ugf (float, optional) – The unity gain frequency (Hz) ofthe integral control. This is the inverse of the integration time constant. Ifintegrator_time_constant is not None, then this value will be ignored. If setto None, it’ll be set to match the UGF of the derivative control. For regulator_type=="I", this must be specified. Defaults to None.

• integrator_time_constant (float, optional,) – The integration time con-stant (s) for integral control. Setting this will override the integrator_ugf argument.Defaults to None.

• return_gain (boolean, optional) – Return the PID gain instead.

Returns

• regulator (TransferFunction, optional) – The regulator. Return only if return_gain isFalse.

• kp (float, optional) – Proportional gain. Return only if return_gain is ‘‘True”.

• ki (float, optional) – Integral gain. Return only if return_gain is True.

• kd (float, optional) – Derivative gain. Return only if return_gain is True.

1.4.7.3 Post Filtering

Functions for designing post-regulator filters

98 Chapter 1. Features

Page 105: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.regulator.post_filter.post_low_pass(plant, regulator, post_filter=None,ignore_ugf_above=None,decades_after_ugf=1, phase_margin=45,f_start=None, f_step=1.1, low_pass=None,mtol=1e-06, small_number=1e-06, oscilla-tory=True, **kwargs)

Add low-pass filter after regulator.

This function lowers/increase the the cutoff frequency of a low-pass filter until the phase margin at a dedicatedugf crosses the specified phase margin. Then, runs a bisection algorithm to poolish the cutoff frequency untilthe phase margin converges relative to the specified tolerance.

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator (TransferFunction) – The regulator.

• post_filter (TransferFunction, optional) – Any post filters that will be ap-plied on top of the regulator. Defaults None.

• ignore_ugf_above (float, optional) – Ignore unity gain frequencies higherthan ignore_ugf_above (Hz). If not specified, defaults to 1 decade higher than thelast UGF. This value can be overrided by the argument decades_after_ugf Note thatthere’s no guarantee that the UGF will be lower than this. The priority is to match the targetphase margin. Defaults to None.

• decades_after_ugf (float, optional) – Set ignore_ugf_above some decadeshigher than the UGF of the OLTF ignore_ugf_above is None. Defaults to 1.

• phase_margin (float, optional,) – The target phase margin (Degrees). Defaultsto 45.

• f_start (float, optional,) – The cutoff frequency to start iterating with. If notspecified, defaults to some decades higher than the highest UGF. “Some decade” is set bydecades_after_ugf. Defaults None.

• f_step (float, optional,) – The gain that is used to multiply (or divide) the cutofffrequency during a coarse search. Defaults 1.1

• low_pass (func(cutoff, order) -> TransferFunction, optional) –The low-pass filter. If not specified, kontrol.regulator.predefined.lowpass() with order 2 will be used. Defaults to None.

• mtol (float, optional) – Tolerance for convergence of phase margin. Defaults to1e-6.

• small_number (float, optional) – A small number as a delta f to detect whetherthe gain is a rising or lowering edge at the unity gain frequency. Defaults to 1e-6.

• oscillatory (boolean, optional) – Use the first mode of the oscillatory system toevaluate the phase margins to avoid having UGFs at steep phase slopes. The benefit of usingthis is to have a conservative phase margin estimate. The phase response of the first modeis the lower bound of the phase response. If False, use the plant itself to calculate phasemargins. If the plant does not contain any complex poles, this option will be overridden toFalse. Defaults True.

• **kwargs – Keyword arguments passed to low_pass.

Returns The low-pass filter.

Return type TransferFunction

1.4. Main Utilities 99

Page 106: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.regulator.post_filter.post_notch(plant, regulator=None, post_filter=None, tar-get_gain=None, notch_peaks_above=None,phase_margin=45, notch=None, **kwargs)

Returns a list of notch filters that suppress resonance peaks.

This functions finds the resonances peak of the plant/OLTF above certain frequencies and returns a list of notchfilters that suppress these peaks to the target gains.

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator (TransferFunction, optional) – The regulator. Defaults to None.

• post_filter (TransferFunction, optional) – Any post filters that will be ap-plied on top of the regulator. Defaults None.

• target_gain (float, optional) – The target open-loop gain for the suppressedpeak. To ensure a stable system, a value of less than 1 is recommended. If not specified, thenotch will fully suppress the peak. Default None.

• phase_margin (float, optional) – The target phase margin. Defaults to 45.

• notch_peaks_above (float, optional) – Notch modes that has freqeunciesabove notch_peaks_above. If not specified, defaults to the highest unity gain fre-quency that is above phase_margin Defaults to None.

• notch (func(frequency, q, depth) -> TransferFunction,optional) – The notch filter. If not specified, kontrol.Notch() will be used.Defaults to None.

• **kwargs – Keyword arguments passed to notch().

Returns A list of notch filters.

Return type list of TransferFunction

Notes

This operation does not guarantee stability. It only finds resonances peaking out of the unity gain above someunity gain frequency and make notch filters to suppress them to a target gain level lower than the unity gain. Thestability of the notched OLTF is not checked whatsoever.

1.4.7.4 Predefined Filters

Predefined regulator library.

kontrol.regulator.predefined.low_pass(cutoff, order=1, **kwargs)Simple low-pass filter

Parameters

• cutoff (float) – Cutoff frequency (Hz)

• order (int, optional) – The order of the filter. Defaults to be 1.

• **kwargs – Keyword arguments holder. Not passed to anywhere.

Returns The low-pass filter.

Return type TransferFunction

100 Chapter 1. Features

Page 107: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

The low-pass filter is defined as

𝐿(𝑠) =

(2𝜋𝑓𝑐

𝑠+ 2𝜋𝑓𝑐

)𝑛

,

where 𝑓𝑐 is the cutoff frequency (Hz), 𝑛 is the order of the filter.

kontrol.regulator.predefined.notch(frequency, q, depth=None, depth_db=None, **kwargs)Notch filter defined in Foton.

Parameters

• frequency (float) – The notch frequency (Hz).

• q (float) – The quality factor.

• depth (float, optional) – The depth of the notch filter (magnitude). If not specified,depth_db will be used. Defaults None.

• depth_db (float, optional) – The depth of the notch filter (decibel). If not speci-fied, depth will be used instead. Defaults None.

Returns The notch filter

Return type TransferFunction

Notes

The notch filter is defined by Foton, as

𝑁(𝑠) =𝑠2 + (2𝜋𝑓𝑛)/(𝑑𝑄/2)𝑠+ (2𝜋𝑓𝑛)

2

𝑠2 + (2𝜋𝑓𝑛)/(𝑄/2)𝑠+ (2𝜋𝑓𝑛)2,

where 𝑓𝑛 is the notch frequency, 𝑞 is the quality factor , and 𝑑 is the depth.

kontrol.regulator.predefined.pid(kp=0, ki=0, kd=0)Alias of proportional_integral_derivative()

kontrol.regulator.predefined.proportional_integral_derivative(kp=0, ki=0,kd=0)

PID control build on proportional_derivative().

Parameters

• kp (float, optional) – The proportional control gain. Defaults to 0.

• ki (float, optional) – The integral control gain. Defaults to 0.

• kd (float, optional) – Defaults to 0.

Returns The PID controller.

Return type TransferFunction

Notes

The PID controller is defined as

𝐾PID(𝑠) = 𝐾𝑝 +𝐾𝑖/𝑠+𝐾𝑑𝑠 .

1.4. Main Utilities 101

Page 108: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5 Kontrol API

1.5.1 Subpackages

1.5.1.1 kontrol.core package

1.5.1.1.1 kontrol.core.controlutils module

Utility function related to python-control library.

kontrol.core.controlutils.check_tf_equal(tf1, tf2, allclose_kwargs={}, minreal=True)Check if two transfer functions are approximatedly equal by np.allclose.

Parameters

• tf1 (control.xferfcn.TransferFunction) – Transfer function 1.

• tf2 (control.xferfcn.TrasnferFunction) – Transfer function 2.

• allclose_kwargs (dict, optional) – Keyword arguments passed to np.allclose(),which is used to compare the list of poles and zeros and dcgain. Defaults {}.

• minreal (boolean) – Use control.minreal to remove canceling zeros and poles beforecomparison.

Returns

Return type boolean

kontrol.core.controlutils.convert_unstable_tf(control_tf)Convert transfer function with unstable zeros and poles to tf without.

Parameters control_tf (control.xferfcn.TransferFunction) – The transfer func-tion to be converted.

Returns tf_new – control_tf with unstable zeros and poles flipped.

Return type control.xferfcn.TransferFunction

Note: Warning can occur when the order gets high, e.g. 100. This happens due to numerical accuracy, i.e.coefficients get astronomically high when order gets high.

kontrol.core.controlutils.generic_tf(zeros=None, poles=None, zeros_wn=None, ze-ros_q=None, poles_wn=None, poles_q=None, dc-gain=1.0, unit=’f’)

Construct a generic transfer function object.

Parameters

• zeros (array, optional) – List of zeros in frequencies. Defaults [].

• poles (array, optional) – List of poles. Defaults [].

• zeros_wn (array, optional) – List of natural frequencies of numerator second-order sections.

• zeros_q (array, optional.) – List of Q-value of numerator second-order sections.

• poles_wn (array, optional) – List of natural frequencies of denominator second-order sections.

102 Chapter 1. Features

Page 109: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• poles_q (array, optional.) – List of Q-value of denominator second-order sec-tions.

• dcgain (float, optional) – The DC gain of the transfer function. Defaults 1.

• unit (str, optional) – The unit of the zeros, poles and the natural frequencies.Choose from [“f”, “s”, “Hz”, “omega”]. Defaults “f”.

Returns tf – The transfer function.

Return type control.xferfcn.TransferFunction

Note: No negative sign is needed for negative zeros and poles. For instance, generic_tf(poles=[1], unit=”s”)means 1

𝑠+1 .

kontrol.core.controlutils.outlier_exists(tf, f, unit=’f’)Checks for zeros and poles outside the frequency range.

Parameters

• tf (control.xferfcn.TransferFunction) – The transfer function.

• f (array) – The frequency axis of interest.

• unit (str, optional) – The unit of the zeros, poles and the natural frequencies.Choose from [“f”, “s”, “Hz”, “omega”]. Defaults “f”.

Returns If there’s any zero or pole outside the frequency range.

Return type boolean

kontrol.core.controlutils.outliers(tf, f, unit=’f’)Returns a list of zeros and poles outside the frequency range.

Parameters

• tf (control.xferfcn.TransferFunction) – The transfer function.

• f (array) – The frequency axis of interest.

• unit (str, optional) – The unit of the zeros, poles and the natural frequencies.Choose from [“f”, “s”, “Hz”, “omega”]. Defaults “f”.

Returns

• outlier_zeros (array) – Zeros outside the frequency range.

• outlier_poles (array) – Poles outside the frequency range.

Note: We use the python-control package convention for the returned zeros and poles, so to preserve the typeand format as much as possible for further processes.

kontrol.core.controlutils.sos(natural_frequencies=None, quality_factors=None, gain=1.0,unit=’f’)

Second-order sections transfer fucntion.

Parameters

• natural_freuqnecies (array, optional) – List of natural frequencies. Defaults[].

• quality_factors (list, optional) – List of quality factors. Defaults [].

1.5. Kontrol API 103

Page 110: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• gain (float, optional) – The gain of the transfer function. Defaults 1.

• unit (str, optional) – The unit of the zeros, poles and the natural frequencies.Choose from [“f”, “s”, “Hz”, “omega”]. Defaults “f”.

Returns sos – The product of all second-order sections.

Return type control.xferfcn.TransferFunction

Note: 𝐾∏(

𝑠2 + 𝜔𝑛/𝑄𝑠+ 𝜔2𝑛

).

kontrol.core.controlutils.tf_order_split(tf, max_order=20)Split TransferFunction objects into multiple ones with fewer order.

Parameters

• tf (TransferFunction) – The transfer function

• max_order (int, optional) – The maxmium order of the split transfer functions.Defaults to 20 (the foton limit).

Returns The list of splitted transfer functions.

Return type list of TransferFunction.

kontrol.core.controlutils.tfmatrix2tf(sys)Convert a matrix of transfer functions to a MIMO transfer function.

Parameters sys (list of (list of control.xferfcn.TransferFunction)) –The transfer function matrix representing a MIMO system. sys[i][j] is the transfer functionfrom the i+1 input port to the j+1 output port.

Returns The transfer function of the MIMO system.

Return type control.xferfcn.TransferFunction

kontrol.core.controlutils.zpk(zeros, poles, gain, unit=’f’, negate=True)Zero-pole-gain definition of transfer function.

Parameters

• zeros (list of floats) – A list of the location of the zeros

• poles (list of floats) – A list of the location of the poles

• gain (float) – The static gain of the transfer function

• unit (str, optional) – The unit of the zeros, poles and the natural frequencies.Choose from [“f”, “s”, “Hz”, “omega”]. Defaults “f”.

• negate (boolean, optional) – Negate zeros and poles in specification so negativesign is not needed for stable zeros and poles. Default to be True.

Returns zpk_tf – The zpk defined transfer function

Return type control.xferfcn.TransferFunction

Notes

Refrain from specifying imaginary zeros and poles. Use kontrol.utils.sos() for second-order sections instead.The zero and poles are negated by default.

104 Chapter 1. Features

Page 111: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5.1.1.2 kontrol.core.math module

Common math library

kontrol.core.math.log_mse(x1, x2, weight=None, small_multiplier=1e-06)Logarithmic mean square error/Mean square log error

Parameters

• x1 (array) – Array

• x2 (array) – Array

• weight (array or None, optional) – weighting function. If None, defaults tonp.ones_like(x1)

• small_multiplier (float, optional) – x1 will be modified byx1‘+‘small_multiplier‘*‘np.min(x2) if 0 exists in x1. Same goes to x2. If 0 bothexists in x1 and x2, raise.

Returns Logarithmic mean square error between arrays x1 and x2.

Return type float

kontrol.core.math.mse(x1, x2, weight=None)Mean square error

Parameters

• x1 (array) – Array

• x2 (array) – Array

• weight (array or None, optional) – weighting function. If None, defaults tonp.ones_like(x1)

Returns Mean square error between arrays x1 and x2.

Return type float

kontrol.core.math.polyval(p, x)

kontrol.core.math.quad_sum(*spectra)Takes any number of same length spectrum and returns the quadrature sum.

Parameters *spectra – Variable length argument list of the spectra.

Returns qs – The quadrature sum of the spectra.

Return type np.ndarray

1.5.1.1.3 kontrol.core.spectral module

Spectral analysis related function library.

kontrol.core.spectral.asd2rms(asd, f=None, df=1.0, return_series=True)Calculate root-mean-squared value from amplitude spectral density

Parameters

• asd (array) – The amplitude spectral density

• f (array, optional) – The frequency axis. Defaults None.

• df (float, optional) – The frequency spacing. Defaults 1.

1.5. Kontrol API 105

Page 112: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• return_series (bool, optional) – Returns the RMS as a frequency series, whereeach value is the integrated RMS from highest frequency. If False, returns a single RMSvalue for the whole spectrum. Defaults True.

Returns

• array – The integrated RMS series, if return_series==True.

• float – The integrated RMS value, if return_series==False.

Notes

The integrated RMS series is defined as

𝑥RMS(𝑓) =

∫ 𝑓

∞𝑥(𝑓 ′)2 𝑑𝑓 ′ ,

where 𝑥(𝑓) is the amplitude spectral density.

When return_series is False, only 𝑥RMS(0) is returned.

kontrol.core.spectral.asd2ts(asd, f=None, fs=None, t=None, window=None, zero_mean=True)Simulate time series from amplitude spectral density

Parameters

• asd (array) – The amplitude spectral density.

• f (array, optional) – The frequency axis of the amplitude spectral density. Thisis used to calculate the sampling frequency. If fs is not specified, f must be specified.Defaults None.

• fs (float, optional) – The sampling frequency in Hz. If f is specified, the lastelement of f will be treated as the Nyquist frequency so the double of it would be thesampling frequency. Defaults None.

• t (array, optional) – The desired time axis for the time series. If t is specified, thenthe time series will be interpolated using numpy.interp() assuming that the time seriesis periodic. Default None.

• window (array, optional) – The FFT window used to compute the amplitude spec-tral density. Defaults None.

• zero_mean (boolean, optional) – Returns a time series with zero mean. DefaultsTrue.

Returns

• t (array) – Time axis.

• time_series (array) – The time series

Notes

Using a custom time axis is not recommended as interpolation leads to distortion of the signal. To extend thesignal in time, it’s recommended to repeat the original time series instead.

106 Chapter 1. Features

Page 113: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.core.spectral.three_channel_correlation(psd1, psd2=None, psd3=None,csd12=None, csd13=None,csd21=None, csd23=None,csd31=None, csd32=None, retur-nall=True)

Noise estimation from three sensors’ readouts.

Parameters

• psd1 (array) – The power spectral density of the readout of the first sensor.

• psd2 (array, optional) – The power spectral density of the readout of the secondsensor. Defaults None.

• psd3 (array, optional) – The power spectral density of the readout of the third sen-sor. Defaults None.

• csd12 (array, optional) – Cross power spectral density between readout 1 and 2. Ifnot specified, this will be estimated as psd1*psd2/csd21. Default None.

• csd13 (array, optional) – Cross power spectral density between readout 1 and 3. Ifnot specified, this will be estimated as psd1*psd3/csd31. Default None.

• csd21 (array, optional) – Cross power spectral density between readout 2 and 1. Ifnot specified, this will be estimated as psd2*psd1/csd12. Default None.

• csd23 (array, optional) – Cross power spectral density between readout 2 and 3. Ifnot specified, this will be estimated as psd2*psd3/csd32. Default None.

• csd31 (array, optional) – Cross power spectral density between readout 3 and 1. Ifnot specified, this will be estimated as psd1*psd3/csd13. Default None.

• csd32 (array, optional) – Cross power spectral density between readout 3 and 1. Ifnot specified, this will be estimated as psd3*psd2/csd23. Default None.

• returnall (boolean, optional) – If True, return all three noise estimations. IfFalse, return noise estimation of first sensor only. Defaults True.

Returns

• noise1 (array) – Power spectral density of the estimated noise in psd1.

• noise2 (array, optional) – Power spectral density of the estimated noise in psd2. Returnsonly if returnall==True

• noise3 (array, optional) – Power spectral density of the estimated noise in psd3. Returnsonly if returnall==True

Notes

The PSD of the noise in psd1 is then computed as

𝑃𝑛1𝑛1(𝑓) =

𝑃𝑥1𝑥1

(𝑓)− 𝑃𝑥1𝑥3(𝑓)

𝑃𝑥2𝑥3(𝑓)

𝑃𝑥2𝑥1

,

If returnall is True, at least psd1, psd2, psd3, (csd13 or csd31), (csd23 or csd32), and (csd12and csd21) must be provided.

1.5. Kontrol API 107

Page 114: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

References

kontrol.core.spectral.two_channel_correlation(psd, coh)Noise estimation from two identical sensors’ readouts.

Parameters

• psd (array) – The power spectral density of the readout of the sensor

• coh (array) – The coherence between readout of the sensor and another identical sensor.

Returns noise – Power spectral density of the estimated noise.

Return type array

Notes

The PSD of the noise is computed as

𝑃𝑛𝑛(𝑓) = 𝑃𝑥1𝑥1(𝑓)

(1− 𝐶𝑥1𝑥2

(𝑓)12

),

where 𝑃𝑥1𝑥1(𝑓) is the power spectral density of the readout and 𝐶𝑥1𝑥2

(𝑓) is the coherence between the twosensor readouts.

References

1.5.1.1.4 kontrol.core.foton module

KAGRA/LIGO Foton related utilities.

kontrol.core.foton.notch(frequency, q, depth, significant_figures=6)Returns the foton expression of a notch filter.

Parameters

• frequency (float) – The notch frequency (Hz).

• q (float) – The quality factor.

• depth (float) – The depth of the notch filter (magnitude).

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

Returns The foton representation of this notch filter.

Return type str

kontrol.core.foton.tf2foton(tf, expression=’zpk’, root_location=’s’, significant_figures=6,itol=1e-25, epsilon=1e-25)

Convert a single transfer function to foton expression.

Parameters

• tf (TransferFunction) – The transfer function object.

• expression (str, optional) – Format of the foton expression. Choose from [“zpk”,“rpoly”]. Defaults to “zpk”.

108 Chapter 1. Features

Page 115: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• root_location (str, optional) – Root location of the zeros and poles for expres-sion==”zpk”. Choose from [“s”, “f”, “n”]. “s”: roots in s-plane, i.e. zpk([. . . ], [. . . ], . . . ,“s”). “f”: roots in frequency plane, i.e. zpk([. . . ], [„,], . . . , “f”). “n”: roots in frequencyplane but negated and gains are normalized, i.e. real parts are positive zpk([. . . ], [. . . ], . . . ,“n”). Defaults to “s”.

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

• itol (float, optional) – Treating complex roots as real roots if the ratio of theimaginary part and the real part is smaller than this tolerance Defaults to 1e-25.

• epsilon (float, optional) – Small number to add to denominator to prevent divi-sion error. Defaults to 1e-25.

Returns foton_expression – The foton expression in selected format.

Return type str

kontrol.core.foton.tf2rpoly(tf, significant_figures=6)Convert a transfer function to foton rpoly expression.

Parameters

• tf (TransferFunction) – The transfer function object

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

Returns Foton express in foton rpoly expression.

Return type str

Notes

Only works for transfer functions with less than 20 orders.

kontrol.core.foton.tf2zpk(tf, root_location=’s’, significant_figures=6, itol=1e-25, epsilon=1e-25)Convert a single transfer function to foton zpk expression.

Parameters

• tf (TransferFunction) – The transfer function object.

• root_location (str, optional) – Root location of the zeros and poles. Choosefrom [“s”, “f”, “n”]. “s”: roots in s-plane, i.e. zpk([. . . ], [. . . ], . . . , “s”). “f”: roots infrequency plane, i.e. zpk([. . . ], [„,], . . . , “f”). “n”: roots in frequency plane but negated andgains are normalized, i.e. real parts are positive zpk([. . . ], [. . . ], . . . , “n”). Defaults to “s”.

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

• itol (float, optional) – Treating complex roots as real roots if the ratio of theimaginary part and the real part is smaller than this tolerance Defaults to 1e-25.

• epsilon (float, optional) – Small number to add to denominator to prevent divi-sion error. Defaults to 1e-25.

Returns The foton zpk expression in selected format.

Return type str

1.5. Kontrol API 109

Page 116: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

Only works for transfer functions with less than 20 orders.

1.5.1.2 kontrol.complementary_filter package

1.5.1.2.1 Primary modules

kontrol.complementary_filter.complementary_filter module

Complementary filter class for sythesis

class kontrol.complementary_filter.complementary_filter.ComplementaryFilter(noise1=None,noise2=None,weight1=None,weight2=None,fil-ter1=None,fil-ter2=None,f=None)

Bases: object

Complementary filter synthesis class.

Parameters

• noise1 (TransferFunction) – Sensor noise 1 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 1.

• noise2 (TransferFunction) – Sensor noise 2 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 2.

• weight1 (TransferFunction, optional) – Weighting function 1. Frequency de-pendent specification for noise 1. Defaults None.

• weight2 (TransferFunction, optional) – Weighting function 2. Frequency de-pendent specification for noise 2. Defaults None.

• filter1 (TransferFunction, optional) – The complementary filter for noise 1.Defaults None.

• filter2 (TransferFunction, optional) – The complementary filter for noise 2.Defaults None.

• f (array, optional) – The frequency axis in Hz for evaluating the super sensor noise.Defaults None.

h2synthesis()Complementary filter synthesis that minimizes the ℋ2-norm of the super sensor noise. The noise must bespecified before using this method.

hinfsynthesis()Complementary filter synthesis that minimizes the ℋ∞-norm of the super sensor noise. The noise must bespecified before using this method.

110 Chapter 1. Features

Page 117: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

This is a utility class for complementary filter synthesis, sensor noise estimation and analysis. To use synthesismethods, specify noise1 and noise2 as the transfer function modesl for the sensor noises, and specifyweight1 and weight2 as the frequency dependent specification for the two sensor noises.

References

fThe frequency axis in Hz.

filter1First complementary filter.

filter2Second complementary filter.

h2synthesis()Synthesize complementary filters using H2 synthesis.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise 1.

• filter2 (TransferFunction) – The complementary filter filtering noise 2.

hinfsynthesis()Synthesize complementary filters using H-inifinity synthesis.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise 1.

• filter2 (TransferFunction) – The complementary filter filtering noise 2.

noise1Transfer munction model of sensor noise 1.

noise2Transfer munction model of sensor noise 2.

noise_super(f=None, noise1=None, noise2=None, filter1=None, filter2=None)Compute and return predicted the ASD of the super sensor noise

f [array, optional] The frequency axis in Hz. Use self.f if None. Defaults None.

noise1 [array, optional] The amplitude spectral density of noise 1. Use self.noise1 and self.f to estimate ifNone. Defaults None.

noise2 [array, optional] The amplitude spectral density of noise 2. Use self.noise1 and self.f to estimate ifNone. Defaults None.

filter1 [TransferFunction, optional] The complementary filter for filtering noise1 Use self.filter1 if notspecified. Defaults None

filter2 [TransferFunction, optional] The complementary filter for filtering noise1 Use self.filter2 if notspecified. Defaults None

Returns The amplitude spectral density of the super sensor noise.

Return type array

1.5. Kontrol API 111

Page 118: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5.1.2.2 Secondary modules

kontrol.complementary_filter.predefined module

Some predefined complementary filters

kontrol.complementary_filter.predefined.lucia(coefs)Lucia Trozzo’s complementary filter

Parameters coefs (list of float or numpy.ndarray of floats) – Takes 7 pa-rameters, in specification order: 𝑝1, 𝑝2, 𝑧1, 𝑤1, 𝑞1, 𝑤2, 𝑞2 . See notes for the meaning ofthe parameters.

Returns

• lpf (control.xferfcn.TransferFunction) – Complementary low-pass filter

• hpf (control.xferfcn.Transferfunction) – Complementary high-pass filter

Notes

The modified Sekiguchi complemetary filter is structured as:

𝐻 = 𝐾𝑠3(𝑠+ 𝑧1)(𝑠

2 + 𝑤1/𝑞1 𝑠+ 𝑤21)(𝑠

2 + 𝑤2/𝑞2 𝑠+ 𝑤22)

(𝑠+ 𝑝1)5(𝑠+ 𝑝2)3

where 𝐾 is a constant normalizing the filter at high frequency.

kontrol.complementary_filter.predefined.modified_sekiguchi(coefs)Modified Sekiguchi Filter with guaranteed 4th-order high-pass.

Parameters coefs (list of (int or float) or numpy.ndarray) – 4 coefficientsdefining the modified Sekiguchi filter.

Returns

• lpf (control.xferfcn.TransferFunction) – Complementary low-pass filter

• hpf (control.xferfcn.Transferfunction) – Complementary high-pass filter

Notes

The modified Sekiguchi complemetary filter is structured as:

𝐻 =𝑠7 + 7𝑎1𝑠

6 + 21𝑎22𝑠5 + 35𝑎33𝑠

4

(𝑠+ 𝑎4)7

where 𝑎1, 𝑎2, 𝑎3, 𝑎4 are some parameters that defines the filter.

kontrol.complementary_filter.predefined.sekiguchi(coefs)4th-order complementary filters specified by the blending frequencies.

Parameters coefs (float) – Blending frequency of the filter in [rad/s]

Returns

• lpf (control.xferfcn.TransferFunction) – Complementary low pass-filter

• hpf (control.xferfcn.Transferfunction) – Complementary high pass-filter

112 Chapter 1. Features

Page 119: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

4th-order complemetary filter used in Sekiguchi’s thesis [1]_ whose high-pass is structured as:

𝐻 =𝑠7 + 7𝑤𝑏𝑠

6 + 21𝑤2𝑏𝑠

5 + 35𝑤3𝑏𝑠

4

(𝑠+ 𝑤𝑏)7

where 𝑤𝑏 is the blending frequency in [rad/s].

References

kontrol.complementary_filter.synthesis module

Filter synthesis functions.

kontrol.complementary_filter.synthesis.generalized_plant(noise1, noise2, weight1,weight2)

Return the generalized plant of a 2 complementary filter system

Parameters

• noise1 (TransferFunction) – Sensor noise 1 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 1.

• noise2 (TransferFunction) – Sensor noise 2 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 2.

• weight1 (TransferFunction) – Weighting function 1. Frequency dependent specifi-cation for noise 1.

• weight2 (TransferFunction) – Weighting function 2. Frequency dependent specifi-cation for noise 2.

Returns The plant.

Return type control.xferfcn.TransferFunction

kontrol.complementary_filter.synthesis.h2complementary(noise1, noise2,weight1=None,weight2=None)

H2 optimal complementary filter synthesis

Parameters

• noise1 (TransferFunction) – Sensor noise 1 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 1.

• noise2 (TransferFunction) – Sensor noise 2 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 2.

• weight1 (TransferFunction, optional) – Weighting function 1. Frequency de-pendent specification for noise 1. Defaults None.

• weight2 (TransferFunction, optional) – Weighting function 2. Frequency de-pendent specification for noise 2.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise1.

• filter2 (TransferFunction) – The complementary filter filtering noise2.

1.5. Kontrol API 113

Page 120: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

This function ultilizes control.robust.h2syn() which depends on the slycot module. If you are using under aconda virtual environment, the slycot module can be installed easily from conda-forge. Using pip to installslycot is a bit more involved (I have yet to suceed installing slycot in my Windows machine). Please refer to thepython-control package for further instructions.

It is possible that h2syn yields no solution for some tricky noise profiles . Try adjusting the noise profiles atsome irrelevant frequencies.

Thomas Dehaeze [1]_ had the idea first so credits goes to him. (Properly cite when the paper is published.)

References

kontrol.complementary_filter.synthesis.hinfcomplementary(noise1, noise2,weight1=None,weight2=None)

H-infinity optimal complementary filter synthesis

Parameters

• noise1 (TransferFunction) – Sensor noise 1 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 1.

• noise2 (TransferFunction) – Sensor noise 2 transfer function model. A transferfunction that has magnitude response matching the amplitude spectral density of noise 2.

• weight1 (TransferFunction, optional) – Weighting function 1. Frequency de-pendent specification for noise 1. Defaults None.

• weight2 (TransferFunction, optional) – Weighting function 2. Frequency de-pendent specification for noise 2.

Returns

• filter1 (TransferFunction) – The complementary filter filtering noise1.

• filter2 (TransferFunction) – The complementary filter filtering noise2.

Notes

This function ultilizes control.robust.hinfsyn() which depends on the slycot module. If you are using under aconda virtual environment, the slycot module can be installed easily from conda-forge. Using pip to installslycot is a bit more involved (I have yet to suceed installing slycot in my Windows machine). Please refer to thepython-control package for further instructions.

Thomas Dehaeze [1]_ had the idea first so credits goes to him. (Properly cite when the paper is published.)

References

1.5.1.3 kontrol.curvefit package

1.5.1.3.1 Primary modules

kontrol.curvefit.curvefit module

Base class for curve fitting

114 Chapter 1. Features

Page 121: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

class kontrol.curvefit.curvefit.CurveFit(xdata=None, ydata=None, model=None,model_kwargs=None, cost=None, opti-mizer=None, optimizer_kwargs=None)

Bases: object

Base class for curve fitting

Parameters

• xdata (array, ydata: array) -> float) – The independent variable data / dataon the x axis. Defaults to None.

• ydata (array or None, optional) – The dependent variable data / data on the yaxis. Defaults to None.

• model (func(x: array, args: array, **kwargs) -> array, or None, optional) – The model usedto fit the data. args in model is an array of parameters that define the model. Defaults toNone

• model_kwargs (dict or None, optional) – Keyword arguments passed to themodel. Defaults to None.

• cost (kontrol.curvefit.Cost or func(args, model, xdata,ydata) -> array) – Cost function.

• xdata – The cost function to be used to fit the data. First argument is a list of parametersthat will be passed to the model. This must be pickleable if multiprocessing is to be used.Defaults to None.

• optimizer (func(func, **kwargs) -> OptimizeResult, or None, optional) – The optimiza-tion algorithm use for minimizing the cost function.

• optimizer_kwargs (dict or None, optional) – Keyword arguments passed tothe optimizer function. Defaults to None.

costThe cost function to be used to fit the data.

fit(model_kwargs=None, cost_kwargs=None, optimizer_kwargs=None)Fit the data

Sets self.optimized_args and self.optimize_result.

Parameters

• model_kwargs (dict or None, optional) – Overriding keyword arguments inself.model_kwargs. Defaults to None.

• cost_kwargs (dict or None, optional) – Overriding keyword arguments inself.cost_kwargs. Defaults to None.

• optimizer_kwargs (dict or None, optional) – Overriding keyword ar-gumetns in self.optimizer_kwargs. Defaults to None.

Returns

Return type scipy.optimizer.OptimizeResult

modelThe model used to fit the data.

model_kwargsKeyword arguments passed to the model.

1.5. Kontrol API 115

Page 122: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

optimize_resultOptimization Result

optimized_argsThe optimized arguments

optimizerThe optimizer function to be used to fit the data.

optimizer_kwargsKeyword arguments passed to the optimizer function.

xdataThe independent variable data.

ydataThe dependent variable data

yfitThe fitted y values

kontrol.curvefit.transfer_function_fit module

Transfer function fitting class

class kontrol.curvefit.transfer_function_fit.TransferFunctionFit(xdata=None,ydata=None,model=None,model_kwargs=None,cost=None,weight=None,er-ror_func_kwargs=None,opti-mizer=None,opti-mizer_kwargs=None,op-tions=None,x0=None)

Bases: kontrol.curvefit.curvefit.CurveFit

Transfer function fitting class

This class is basically a CurveFit class with default cost functions and optimizer that is designed for fittinga transfer function. By default, the error function is kontrol.curvefit.error_func.tf_error().The default optimizer is scipy.optimize.minimize( ...,method="Nelder-Mead",...), withoptions = {"adaptive": True, "maxiter": N*1000}, where N is the number of variables. Allof these can be overridden if specified.

Parameters

• xdata (array or None, optional) – Independent variable data. Defaults to None.

• ydata (array or None, optional) – Transfer function frequency response in complexnumbers. Defaults to None.

• model (func(x: ``array, args: array, **kwargs)‘‘ -> array, or None, optional)– The model used to fit the data. args in model is an array of parameters that define themodel. Defaults to None

116 Chapter 1. Features

Page 123: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• model_kwargs (dict or None, optional) – Keyword arguments passed to the model.Defaults to None.

• cost (kontrol.curvefit.Cost or func(args, model, xdata, ydata) -> array, optional) – Cost function. The cost function to be used to fit the data. Firstargument is a list of parameters that will be passed to the model. This must be pickleable ifmultiprocessing is to be used. Defaults to None.

• weight (array or None, optional) – Weighting function. Defaults None.

• error_func_kwargs (dict or None, optional) – Keyword arguments the will bepassed to error_func, which is passed to the construct the cost function. Defaults toNone.

• optimizer (func(func, **kwargs) -> OptimizeResult, or None, optional) –The optimization algorithm use for minimizing the cost function.

• optimizer_kwargs (dict or None, optional) – Keyword arguments passed to the op-timizer function. Defaults to None.

• options (dict or None, optional) – The option arguments passed to the optimizer

• x0 (array, optional) – Inital guess. Defaults to None.

optionsOption arguments passed to optimizer

weightWeighting function

x0Initial guess

1.5.1.3.2 Secondary modules

kontrol.curvefit.cost module

Cost function base class

class kontrol.curvefit.cost.Cost(error_func, error_func_kwargs=None)Bases: object

Cost function base class.

error_funcThe error function.

error_func_kwargsKeyword arguments passed to error_func

kontrol.curvefit.error_func module

Error functions for curve fitting

kontrol.curvefit.error_func.log_mse(x1, x2, weight=None, small_multiplier=1e-06)Logarithmic mean square error/Mean square log error

Parameters

• x1 (array) – Array

1.5. Kontrol API 117

Page 124: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• x2 (array) – Array

• weight (array or None, optional) – weighting function. If None, defaults tonp.ones_like(x1)

• small_multiplier (float, optional) – x1 will be modified byx1‘+‘small_multiplier‘*‘np.min(x2) if 0 exists in x1. Same goes to x2. If 0 bothexists in x1 and x2, raise.

Returns Logarithmic mean square error between arrays x1 and x2.

Return type float

kontrol.curvefit.error_func.mse(x1, x2, weight=None)Mean square error

Parameters

• x1 (array) – Array

• x2 (array) – Array

• weight (array or None, optional) – weighting function. If None, defaults tonp.ones_like(x1)

Returns Mean square error between arrays x1 and x2.

Return type float

kontrol.curvefit.error_func.tf_error(tf1, tf2, weight=None, small_number=1e-06, norm=2)Mean log error between two transfer functions frequency response

Parameters

• tf1 (array) – Frequency response of transfer function 1, in complex values.

• tf2 (array) – Frequency response of transfer function 2, in complex values.

• weight (array or None, optional) – Weighting function. Defaults None.

• small_number (float, optional) – A small number to be added in before evaluat-ing np.log10 to prevent error. Defaults to 1e-6.

• norm (int, optional) – The type of norm used to estimate the error. Choose 1 for$mathcal{L}_1$ norm and 2 for $mathcal{L}_2$ norm. Other norms are are possible butnot encouraged unless there’s a specifiy reason. Defaults 2.

Returns Mean log error between two transfer function.

Return type float

Notes

$mathcal{L}_1$ norm is more robust while $mathcal{L}_2$ norm is more susceptible to outliers, but fits datawith large dynamic range well.

1.5.1.3.3 Subpackages

kontrol.curve.model subpackage

Model base class for curve fitting.

118 Chapter 1. Features

Page 125: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

class kontrol.curvefit.model.model.Model(args=None, nargs=None, log_args=False)Bases: object

Model base class for curve fitting

argsModel parameters

nargsNumber of model parameters

Transfer function models for transfer function fitting.

class kontrol.curvefit.model.transfer_function_model.ComplexZPK(nzero_pairs,npole_pairs,args=None,log_args=False)

Bases: kontrol.curvefit.model.model.Model

ZPK model with complex poles and zeros.

Parameters

• nzero_pairs (int) – Number of complex zero pairs.

• npole_pairs (int) – Number of complex pole pairs.

• args (array, optional) – The model parameters defined as args = [f1, q1,f2, q2,..., fi, qi,..., fn, qn, k], where f are resonance frequencies ofthe complex zero/pole pairs, q are the quality factors, and k is the static gain, i is thenumber of complex zero pairs, and n-i is the number of of complex pole pairs.

• log_args (boolean, optional) – If true, model parameters passed to the model areassumed to be passed through a log10() function. So, when the real parameters will beassumed to be 10**args instead. Defaults to False.

fn_zeroList of resonance frequencies of the complex zeros.

Type array

fn_poleList of resonance frequencies of the complex poles.

Type array

q_zeroList of Q-factors of the complex zeros.

Type array

q_poleList of Q-factors of the complex poles.

Type array

Notes

The complex ZPK model is defined by:

𝐺(𝑠; 𝑓𝑖, 𝑞𝑖, 𝑘) = 𝑘

∏𝑖(

𝑠2

(2𝜋𝑓𝑖)2+ 1

2𝜋𝑓𝑖𝑞𝑖𝑠+ 1)∏

𝑗(𝑠2

(2𝜋𝑓𝑗)2+ 1

2𝜋𝑓𝑗𝑞𝑗𝑠+ 1)

1.5. Kontrol API 119

Page 126: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

argsModel parameters in ZPK [f1, q1, f2, q2,. . . , fn, qn, k] format

fn_poleList of resonance frequencies of complex poles.

fn_zeroList of resonance frequencies of complex zeros.

gainStatic gain.

npole_pairsNumber of complex pole pairs

nzero_pairsNumber of complex zero pairs

q_poleList of quality factors of the complex poles

q_zeroList of quality factors of the complex zeros

tfReturns a TransferFunction object of this ZPK model

class kontrol.curvefit.model.transfer_function_model.CoupledOscillatorBases: kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

class kontrol.curvefit.model.transfer_function_model.DampedOscillator(args=None)Bases: kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

Transfer function model for a damped oscillator.

Parameters args (array or None, optional.) – The model parameters with three num-bers. Structured as follows: args = [k, fn, q], where k is the DC gain of the transferfunction, fn is the resonance frequency in Hz, and q is the Q-factor. Defaults to None.

Notes

The model is definded as

𝐺(𝑠; 𝑘, 𝜔𝑛, 𝑞) = 𝑘𝜔2𝑛

𝑠2 + 𝜔𝑛

𝑞 𝑠+ 𝜔2𝑛

where 𝑘 is the DC gain of the transfer function, 𝜔𝑛 is the resonance frequency of the oscillator, and 𝑞 is theQ-factor if the damped oscillator.

argsModel parameters

damped_oscillator_argsThe model parameters with three numbers [k, fn, q]

fnResonance frequency

gainDC gain

120 Chapter 1. Features

Page 127: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

qQ factor

class kontrol.curvefit.model.transfer_function_model.SimpleZPK(nzero, npole,args=None,log_args=False)

Bases: kontrol.curvefit.model.model.Model

ZPK model with simple poles and zeros.

Parameters

• nzero (int) – Number of simple zeros.

• npole (int) – Number of simple poles.

• args (array, optional) – The model parameters defined as args = [z1, z2,..., p1, p2,..., k] where z are the locations of the zeros in Hz, p are the locationsof the pole in Hz, and k is the static gain,

• log_args (boolean, optional) – If true, model parameters passed to the model areassumed to be passed through a log10() function. So, when the real parameters will beassumed to be 10**args instead. Defaults to False.

zeroList of zeros.

Type array

poleList of poles.

Type array

gainStatic gain.

Type float

tfThe transfer function representation of this ZPK model

Type kontrol.TranserFunction

Notes

The simple ZPK model is defined as

𝐺(𝑠; 𝑧1, 𝑧2, ..., 𝑝1, 𝑝2, ..., 𝑘) = 𝑘

∏𝑖(

𝑠2𝜋𝑧𝑖

+ 1)∏𝑗(

𝑠2𝜋𝑝𝑗

+ 1)

argsModel parameters in ZPK [z1, z2,. . . , p1, p2,. . . , k] format

gainStatic gain

npoleNumber of complex pole pairs

nzeroNumber of simple zeros

1.5. Kontrol API 121

Page 128: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

poleList of poles

tfReturns a TransferFunction object of this ZPK model

zeroList of zeros

class kontrol.curvefit.model.transfer_function_model.TransferFunctionModel(nzero,npole,args=None,log_args=False)

Bases: kontrol.curvefit.model.model.Model

Transfer function model class defined by numerator and denominator

Parameters

• nzero (int) – Number of zeros.

• npole (int) – Number of poles.

• args (array or None, optional.) – The model parameters. Structured as fol-lows: [b_n, b_n-1,. . . , b_1, b_0, a_m, a_m-1,. . . , a_1, a_0], where b and a are the coeffi-cients of the numerator and denominator respectively, ordered from higher-order to lower-order. Defaults to None.

tfThe last evaluted transfer function.

Type kontrol.TransferFunction

Notes

The transfer function model is defined as

𝐺(𝑠, 𝑏𝑛, 𝑏𝑛−1, ..., 𝑏1, 𝑏0, 𝑎𝑚, 𝑎𝑚−1, ..., 𝑎1, 𝑎0) =

∑𝑛𝑖=0 𝑏𝑖𝑠

𝑖∑𝑚𝑗=0 𝑎𝑗𝑠

𝑗

denDenominator array

npoleNumber of zeros

numNumerator array

nzeroNumber of zeros

tfThe Transfer Function object.

Basic mathematical models

class kontrol.curvefit.model.math_model.Erf(args=None)Bases: kontrol.curvefit.model.model.Model

The error function erf(x)

122 Chapter 1. Features

Page 129: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Parameters args (array, optional) – The model parameters structured as [amplitude, slope,x0, y0]. Defaults to None.

Notes

The function is defined as

𝑓(𝑥; 𝑎,𝑚, 𝑥0, 𝑦0) = 𝑎 erf(𝑚(𝑥− 𝑥0)) + 𝑦0 ,

where erf(𝑥) is the error function1, 𝑎 is the peak to peak amplitude, 𝑚 is the slope at the inflection point, 𝑥0 isthe 𝑥 offset , and 𝑦0 is the 𝑦 offset.

References

amplitudePeak to Peak amplitude of the error function.

slopeSlope at the inflection point.

x_offsetx offset

y_offsety offset

class kontrol.curvefit.model.math_model.StraightLine(args=None)Bases: kontrol.curvefit.model.model.Model

Simply a straight line.

Parameters args (array, optional) – The model parameters structued as: [slope, y-intercept]. Defaults to None.

slopeThe slope of the line.

Type float

interceptThe y-intercept of the line.

Type float

Notes

The straight line is defined as

𝑦(𝑥;𝑚, 𝑐) = 𝑚𝑥+ 𝑐 ,

where 𝑚 is the slope and 𝑐 is the y-intercept.

interceptThe y-intercept of the line

slopeThe slope of the line

1 https://en.wikipedia.org/wiki/Error_function

1.5. Kontrol API 123

Page 130: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5.1.4 kontrol.frequency_series package

1.5.1.4.1 Primary modules

kontrol.frequency_series.frequency_series module

Frequency series class.

class kontrol.frequency_series.frequency_series.FrequencySeries(f, x)Bases: object

Frequency series class

fFrequency axis

Type array

xFrequency series data.

Type array

x_empiricalFrequency series of the empirical model.

Type array or None

model_empiricalThe model function whose first argument is the frequency axis and the rest being an arbitrary number ofmodel parameters to be fit.

Type func(f, a, b, c, ..) -> array or None

args_empirical_modelThe optimized arguments passed to the model_empirical.

Type array or None

f_zpkThe frequency axis used during ZPK fitting.

Type array or None

x_zpkThe frequency series data used during ZPK fitting. Note: this is a complex array.

Type array or None

tf_zpkThe transfer function the best fit the data using ZPK model.

Type control.xferfcn.TransferFunction or None

args_zpk_modelA 1-D list of zeros, poles, and gain. Zeros and poles are in unit of Hz.

Type array or None

f_tfThe frequency series data used during transfer function fitting.

Type array or None

124 Chapter 1. Features

Page 131: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

x_tfThe frequency series data used during transfer function fitting. Note: this is a complex array.

Type array or None

tfThe modeled transfer function.

Type control.xferfcn.TransferFunction or None

args_tf_modelA 1-D list of numerator and denominator coefficients, from higher order to lower order.

Type array or None

fit_empirical(model, x0=None, error_func=<function log_mse>, error_func_kwargs={}, opti-mizer=<function minimize>, optimizer_kwargs={})

Fit frequency series data with an empirical model. (Local optimize)

Parameters

• model (func(f, a, b, c, ..) -> array) – The model function whose firstargument is the frequency axis and the rest being an arbitrary number of model parametersto be fit.

• x0 (array, optional) – The initial parameters for fitting. Defaults None. If None,defaults to np.ones()

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.minimize.

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“method”: “Powell”, “x0”=x0} if optimizeris default.

Returns res – The result of the fitting (optimization)

Return type scipy.optimize.OptimizeResult

fit_tf(x0=None, log_var=True, error_func=<function log_mse>, error_func_kwargs={}, opti-mizer=<function minimize>, optimizer_kwargs={})

Fit frequency series with a transfer function model

Parameters

• x0 (array or None, optional) – The initial parameters defining the initial trans-fer function. A 1-D list of numerator and denominator coefficients, from higher order tolower order. Defaults None. If None, will use the result from the ZPK fitting.

• log_var (boolean, optional) – Optimize the log of variables instead. Useful forvariables that have very large dynamic range Defaults True.

• error_func (func(x1: array, x2: array) -> float, optional) –The function that evaluate the error between arrays x1 and x2. Defaults to kon-trol.core.math.log_mse, which evaluates the logarithmic mean square error.

1.5. Kontrol API 125

Page 132: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.minimize.

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“method”: “Powell”, “x0”: x0} if optimizeris default.

Returns res – The result of the optimization.

Return type scipy.optimize.OptimizerResult

Note: Only use this after using fit_zpk().

fit_zpk(order, fit=’x_empirical’, padding=False, padding_order=1.0, error_func=<functionlog_mse>, error_func_kwargs={}, optimizer=<function differential_evolution>, opti-mizer_kwargs={})

Fit frequency series with a zpk model.

Parameters

• order (int) – The order of the ZPK model to be used.

• fit (str, optional) – Choose the data to be fit, choose from [“x”, “x_empirical”].

• padding (boolean, optional) – Pad the data by continuous white noise. DefaultsFalse.

• padding_order (float, optional) – The number of decades to be padded onboth sides of the series. Defaults to 1.

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Use this to specify weighting function {“weight”: weight_func}. Defaults to {}.

• optimizer (func, optional) – The optimization function which has the sig-nature func(func, args, bounds, **kw) -> scipy.optimize.OptimizeResult. Defaults toscipy.optimize.differential_evolution.

• optimizer_kwargs (dict, optional) – Keyword arguments passed to the opti-mization function. Defaults {} but will set to {“workers”: -1, “updating”:”deferred”} ifoptimizer is default.

Returns res – The result of the optimization.

Return type scipy.optimize.OptimizerResult

Note: For now, we support models with equal number of zeros and poles only.

Best use with log-space frequency series or after using fit_empirical().

126 Chapter 1. Features

Page 133: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5.1.4.2 Secondary modules

kontrol.frequency_series.conversion module

Conversion functions for frequency series fitting.

This is a lower level module which users are not expect to use.

kontrol.frequency_series.conversion.args2controltf(zpk_args)Convert a list of zeros, poles, and gain to control.tf

Parameters zpk_args (array) – A 1-D list of zeros, poles, and gain. Zeros and poles are in unitof Hz.

kontrol.frequency_series.conversion.args2tf(f, tf_args)Returns an array of transfer function evaluted at frequency f.

Parameters

• f (array) – The frequency axis.

• tf_args (array) – A 1-D list of numerator and denominator coefficients, from higherorder to lower order.

Returns A complex array.

Return type array

kontrol.frequency_series.conversion.args2zpk(f, zpk_args)Returns an array of ZPK defined transfer function evaluted at frequency f.

Parameters

• f (array) – The frequency axis.

• zpk_args (array) – A 1-D list of zeros, poles, and gain. Zeros and poles are in unit ofHz.

Returns zpk – A complex array.

Return type array

kontrol.frequency_series.conversion.tf2tf_args(tf)Returns an array of tf_args used for fitting.

Parameters tf (control.xferfcn.TransferFunction) – The transfer function.

Returns tf_args – A 1-D list of numerator and denominator coefficients, from higher order to lowerorder.

Return type array

kontrol.frequency_series.conversion.tf_args2tf(tf_args)Returns a transfer function from tf_args.

Parameters tf_args (array) – A 1-D list of numerator and denominator coefficients, fromhigher order to lower order.

Returns tf – The transfer function.

Return type control.xferfcn.TransferFunction

1.5. Kontrol API 127

Page 134: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.frequency_series.costs module

Cost functions for fitting

kontrol.frequency_series.costs.cost_empirical_fit(args, f, x, model, er-ror_func=<function log_mse>,error_func_kwargs={})

Cost function for frequency series empirical fitting.

Parameters

• args (array) – Empirical model parameters.

• f (array) – Frequency axis.

• x (array) – Frequency series data.

• model (func(f, a, b, c, ..) -> array) – The model function whose first ar-gument is the frequency axis and the rest being an arbitrary number of model parameters tobe fit.

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict) – Keyword arguments passed to the error function.

Returns cost – The cost.

Return type float

kontrol.frequency_series.costs.cost_tf_fit(tf_args, f, x, error_func=<function log_mse>,error_func_kwargs={}, log_var=True)

The cost funcion for gitting a frequency series with transfer function.

Parameters

• tf_args (array) – A 1-D list of numerator and denominator coefficients, from higherorder to lower order.

• f (array) – The frequency axis.

• x (array) – The frequecy series data.

• error_func (func(x1: array, x2: array) -> float, optional) – Thefunction that evaluate the error between arrays x1 and x2. Defaults to kon-trol.core.math.log_mse, which evaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Defaults {}.

• log_var (boolean, optional) – Optimize the log of variables instead. Useful forvariables that have very large dynamic range Defaults True.

Returns cost – The cost.

Return type float

kontrol.frequency_series.costs.cost_zpk_fit(zpk_args, f, x, error_func=<functionlog_mse>, error_func_kwargs={})

The cost function for fitting a frequency series with zero-pole-gain.

Parameters

• zpk_args (array) – A 1-D list of zeros, poles, and gain. Zeros and poles are in unit ofHz.

128 Chapter 1. Features

Page 135: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• f (array) – The frequency axis.

• x (array) – The frequecy series data.

• error_func (func(x1: array, x2: array) -> float) – The function thatevaluate the error between arrays x1 and x2. Defaults to kontrol.core.math.log_mse, whichevaluates the logarithmic mean square error.

• error_func_kwargs (dict, optional) – Keyword arguments passed to the errorfunction. Defaults {}.

Returns cost – The cost.

Return type float

kontrol.frequency_series.noise_models module

Noise models for empirical fitting.

kontrol.frequency_series.noise_models.geophone_noise(f, n0=2e-06, fc=0.9, exp1=-3.5,exp2=-1.0)

Geophone noise model.

Parameters

• f (array) – Frequency axis

• n0 (float, optional) – The noise level at 1 Hz with the first exponent. In um/rtHz.Defaults to the typical value 2e-6.

• fc (float, optional) – The corner frequency at which the exponent changes. De-faults to the typical value 0.9.

• exp1 (float, optional) – The exponent of the frequency dependency before the cor-ner frequency. Defaults -3.5.

• exp2 (float, optional) – The exponent of the frequency dependency after the cornerfrequency. Defaults -1.

Returns noise – The geophone noise frequency series.

Return type numpy.ndarray

Notes

The geophone noise noise typically has a 𝑓−3.5 dependency before the corner frequency and has a 𝑓−1 depen-dency after that.

kontrol.frequency_series.noise_models.lvdt_noise(f, n0=0.008, fc=4.5, exp1=-0.5,exp2=0.0)

LVDT noise model.

Parameters

• f (array) – Frequency axis.

• n0 (float, optional.) – The noise level at 1 Hz with the first exponent. In um/rtHz.Defaults to the typical value 8e-3.

• fc (float, optional.) – Defaults to the typical value 4.5 Hz. The corner frequencyat which the exponent changes.

1.5. Kontrol API 129

Page 136: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• exp1 (float, optional) – The exponent of the frequency dependency before the cor-ner frequency. Defaults -0.5.

• exp2 (float, optional) – The exponent of the frequency dependency after the cornerfrequency. Defaults 0.

Returns noise – The LVDT noise frequency series.

Return type numpy.ndarray

Notes

The LVDT noise noise typically has a 𝑓−0.5 dependency before the corner frequency and is flat after that.

kontrol.frequency_series.noise_models.piecewise_noise(f, n0, exp=[0], fc=[0])Piecewise noise specified corner frequencies and exponents

Parameters

• f (list of int/float or numpy.ndarray) – The frequency axis of the noise.

• n0 (int/float) – The noise level at 1 Hz with the first exponent.

• exp (list of int/float) – The list of exponents of each section of noise separatedby the corner frequencies.

• fc (list of int/float) – The list of corner frequencies in increaing order. Thelength of fc must be 1 less then the length of exp

Returns noise – The piecewise noise array.

Return type numpy.ndarray

1.5.1.5 kontrol.sensact package

1.5.1.5.1 Primary modules

kontrol.sensact.matrix module

Base class for matrices, sensing and actuation matices.

class kontrol.sensact.matrix.Matrix(*args, **kwargs)Bases: numpy.ndarray

Base class for matrices

class kontrol.sensact.matrix.SensingMatrix(matrix, coupling_matrix=None, *args,**kwargs)

Bases: kontrol.sensact.matrix.Matrix

Base class for sensing matrices

diagonalize(coupling_matrix=None)Diagonalize the sensing matrix, given a coupling matrix.

Parameters coupling_matrix (array, optional.) – The coupling matrix. If None,self.coupling_matrix will be used. Default None.

Returns The new sensing matrix.

Return type array

130 Chapter 1. Features

Page 137: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

The coupling matrix has (i, j) elements as coupling ratios x_i/x_j. For example, consider the 2-sensorconfiguration: I have a coupled sensing readout 𝑥1,coupled that reads 𝑥1,coupled = 𝑥1 + 0.1𝑥2. and, I haveanother coupled sensing readout 𝑥2,coupled that reads 𝑥2,coupled = −0.2𝑥1+𝑥2. Then, the coupling matrixis [

1 0.1−0.2 1

].

kontrol.sensact.optical_lever module

Optical lever sensing matrix

class kontrol.sensact.optical_lever.HorizontalOpticalLeverSensingMatrix(*args,**kwargs)

Bases: kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

Horizontal optical lever sensing matrix.

Parameters

• r (float) – Lever arm.

• alpha_h (float) – Angle of incidence on the horizontal plane.

• r_lens (float, optional) – Lever arm from the optics to the convex lens. Default0.

• f (float, optional) – Focal length of the convex lens. Default np.inf.

• alpha_v (float, optional) – Angle of incidence on the vertical plane. Default 0.

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Default 0.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Default 0.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane. Default 0.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane. Default 0.

• delta_d (float, optional) – Misplacement of the length-sensing QPD. Default 0.

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

• *args – Variable length arguments passed to OpticalLeverSensingMatrix.

• **kwargs – Keyword arguments passed to OpticalLeverSensingMatrix.

delta_dMisplacement of the length-sensing QPD.

1.5. Kontrol API 131

Page 138: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

fFocal length of the convex lens.

rLever arm.

r_lensLever arm from the optics to the convex lens.

class kontrol.sensact.optical_lever.OpticalLeverSensingMatrix(*args, **kwargs)Bases: kontrol.sensact.matrix.SensingMatrix

Optical lever sensing matrix base class.

This is a sensing matrix that maps tilt-sensing QPD and length-sensing QPD (placed behind a lens) readouts tothe suspended optics’ longitudinal, pitch, and yaw displacements.

Parameters

• r_h (float) – Lever arm from the optics to the tilt-sensing QPD plane on the horizontalplane (amplifying yaw).

• r_v (float) – Lever arm from the optics to the tilt-sensing QPD plane on the verticalplane (amplifying pitch).

• alpha_h (float) – Angle of incidence on the horizontal plane.

• alpha_v (float) – Angle of incidence on the vertical plane.

• r_lens_h (float, optional) – Lever arm from optics to the lens on the horizontalplane. Defaults None. Specify if it exists.

• r_lens_v (float, optional) – Lever arm from optics to the lens on the verticalplane. Defaults None. Specify if it exists.

• d_h (float, optional) – Horizontal distance from the lens to the length-sensing QPD.Defaults None.

• d_v (float, optional) – Vertical distance from the lens to the length-sensing QPD.Defaults None.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane.

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Defaults None.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Defaults None.

• f_h (float, optional,) – Focal length of the lens projected to the horizontal plane.Defaults np.inf (no lens).

• f_v (float, optional,) – Focal length of the lens projected to the vertical plane.Defaults np.inf (no lens).

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

132 Chapter 1. Features

Page 139: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

Notes

We’re using equation (29) from [1]_.

⎛⎝𝑥𝐿

𝜃𝑃𝜃𝑌

⎞⎠ = CmiscenterCalignCrotation

⎛⎜⎜⎝𝑥tilt

𝑦tilt𝑥len

𝑦len

⎞⎟⎟⎠ ,

where 𝑥𝐿 is the longitudinal displacement of the optics, 𝜃𝑃 is the pitch angular displacement of the optics, 𝜃𝑌is the yaw angular displacement of the optics, 𝑥tilt is the horizontal displacement of the beam spot at the tilt-sensing QPD plane, 𝑦tilt is the vertical displacement of the beam spot at the tilt-sensing QPD plane, 𝑥len is thehorizontal displacement of the beam spot at the length-sensing QPD plane, 𝑦len is the vertical displacement ofthe beam spot at the length-sensing QPD plane,

Crotation =

⎡⎢⎢⎣cos𝜑tilt sin𝜑tilt 0 0− sin𝜑tilt cos𝜑tilt 0 0

0 0 cos𝜑len sin𝜑len

0 0 − sin𝜑len cos𝜑len

⎤⎥⎥⎦ ,

Calign =

⎡⎢⎢⎢⎢⎣2 sin𝛼ℎ 0 2𝑟ℎ2 sin𝛼𝑣 2𝑟𝑣 0

2 sin𝛼ℎ

(1− 𝑑ℎ

𝑓ℎ

)0 2

[(1− 𝑑ℎ

𝑓ℎ

)𝑟lens,ℎ + 𝑑ℎ

]2 sin𝛼𝑣

(1− 𝑑𝑣

𝑓𝑣

)2[(

1− 𝑑𝑣

𝑓𝑣

)𝑟lens,𝑣 + 𝑑𝑣

]0

⎤⎥⎥⎥⎥⎦+

,

Cmiscenter =

⎡⎣1 𝛿𝑦 𝛿𝑥0 1 00 0 1

⎤⎦−1

,

𝜑tilt is the angle between the tilt-sensing QPD and the yaw-pitch frame, 𝜑len is the angle between the length-sensing QPD and the yaw-pitch frame, 𝑟ℎ is the lever arm on the horzontal plane, 𝑟𝑣 is the lever arm on thevertical plane, 𝛼ℎ is the angle of incidence on the horizontal plane, 𝛼𝑣 is the angle of incidence on the verticalplane, 𝑟lens,ℎ is the lever arm between the optics and the lens on the horizontal plane, 𝑟lens,𝑣 is the lever armbetween the optics and the lens on the vertical plane, 𝑑ℎ is the distance between the lens and the length-sensingQPD on the horizontal plane, 𝑑𝑣 is the distance between lens and the length-sensing QPD on the vertical plane,and 𝑓 is the focal length of the convex lens.

References

alpha_hAngle of incidence on the horizontal plane.

alpha_vAngle of incidence on the vertical plane.

d_hHorizontal distance from the lens to the length-sensing QPD.

d_vVertical distance from the lens to the length-sensing QPD.

1.5. Kontrol API 133

Page 140: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

delta_xHorizontal miscentering of the beam spot at the optics plane.

delta_yHorizontal miscentering of the beam spot at the optics plane.

f_hFocal length of the convex lens projected on the horizontal plane.

f_vFocal length of the convex lens projected on the vertical plane.

formatFormat of the sensing matrix.

Choose from

“OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen with input (TILT_PIT,TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal, pitch and yaw).

“xy”: Matrix as shown in [1]_.

References

phi_lenAngle from the length-sensing QPD frame to the yaw-pitch frame.

phi_tiltAngle from the tilt-sensing QPD frame to the yaw-pitch frame.

r_hLever arm from optics to tilt-sensing QPD on the horizontal plane.

r_lens_hLever arm from optics to the lens on the horizontal plane.

r_lens_vLever arm from optics to the lens on the vertical plane.

r_vLever arm from optics to tilt-sensing QPD on the vertical plane.

update_matrices_decorator()Update matrices and self upon setting new parameters.

class kontrol.sensact.optical_lever.VerticalOpticalLeverSensingMatrix(*args,**kwargs)

Bases: kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

Vertical optical lever sensing matrix.

Parameters

• r (float) – Lever arm.

• alpha_v (float) – Angle of incidence on the vertical plane.

• r_lens (float, optional) – Lever arm from the optics to the convex lens. Default0.

• f (float, optional) – Focal length of the convex lens. Default np.inf.

• alpha_h (float, optional) – Angle of incidence on the horizontal plane. Default 0.

134 Chapter 1. Features

Page 141: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• phi_tilt (float, optional) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame. Default 0.

• phi_len (float, optional) – Angle from the length-sensing QPD frame to the yaw-pitch frame. Default 0.

• delta_x (float, optional) – Horizontal miscentering of the beam spot at the opticsplane. Default 0.

• delta_y (float, optional) – Vertical miscentering of the beam spot at the opticsplane. Default 0.

• delta_d (float, optional) – Misplacement of the length-sensing QPD. Default 0.

• format (str, optional) – Format of the sensing matrix. Choose from

”OPLEV2EUL”: Default sensing matrix from KAGRA MEDM screen withinput (TILT_PIT, TILT_YAW, LEN_PIT, LEN_YAW), and output (longitudinal,pitch and yaw).

”xy”: Matrix as shown in [1]_.

• coupling_matrix (array, optional) – The coupling matrix. Default None.

• *args – Variable length arguments passed to OpticalLeverSensingMatrix.

• **kwargs – Keyword arguments passed to OpticalLeverSensingMatrix.

delta_dMisplacement of the length-sensing QPD.

fFocal length of the convex lens.

rLever arm.

r_lensLever arm from the optics to the convex lens.

kontrol.sensact.optical_lever.c_align(r_h, r_v, alpha_h, alpha_v, r_lens_h=0, r_lens_v=0,d_h=0, d_v=0, f_h=inf, f_v=inf, roundoff=6)

Return optical lever sensing matrix for a perfectly aligned case.

Parameters

• r_h (float) – Lever arm from the optics to the tilt-sensing QPD plane on the horizontalplane (amplifying yaw).

• r_v (float) – Lever arm from the optics to the tilt-sensing QPD plane on the verticalplane (amplifying pitch).

• alpha_h (float) – Angle of incidence on the horizontal plane.

• alpha_v (float) – Angle of incidence on the vertical plane.

• r_lens_h (float, optional) – Lever arm from optics to the lens on the horizontalplane. Defaults None. Specify if it exists.

• r_lens_v (float, optional) – Lever arm from optics to the lens on the verticalplane. Defaults None. Specify if it exists.

• d_h (float, optional) – Horizontal distance from the lens to the length-sensing QPD.Defaults None.

1.5. Kontrol API 135

Page 142: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• d_v (float, optional) – Vertical distance from the lens to the length-sensing QPD.Defaults None.

• f_h (float, optional,) – Focal length of the lens projected to the horizontal plane.Defaults np.inf (no lens).

• f_v (float, optional,) – Focal length of the lens projected to the vertical plane.Defaults np.inf (no lens).

• roundoff (int, optional) – How many decimal places to keep.

Returns The aligned optical lever sensing matrix

Return type array

Notes

See Calign from [1]_.

If f_h or f_v or (d_h and d_v) or (r_lens_h and r_lens_v) are not specified, then we assume no length-sensingQPD so first column, third row, and forth row will be 0. elif f_h and f_v is specified, (d_h or r_lens_h) are notspecified, then third row will be 0. elif f_h and f_v is specified, (d_v or r_lens_v) are not specified, then forthrow will be 0.

References

kontrol.sensact.optical_lever.c_miscenter(delta_x, delta_y)Returns the matrix for correcting miscentered optical lever beam.

Parameters

• delta_x (float) – Horizontal miscentering of the beam spot at the optics plane.

• delta_y (float) – Vertical miscentering of the beam spot at the optics plane.

Returns The miscentering correction matrix.

Return type array

kontrol.sensact.optical_lever.c_rotation(phi_tilt, phi_len)Returns the rotation transformation matrix for tilt and length QPDs.

Parameters

• phi_tilt (float) – Angle from the tilt-sensing QPD frame to the yaw-pitch frame.

• phi_len (float) – Angle from the length-sensing QPD frame to the yaw-pitch frame.

Returns Matrix that transform the QPD frames to the yaw-pitch frame.

Return type array

Notes

See Crotation from [1]_

136 Chapter 1. Features

Page 143: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

References

kontrol.sensact.calibration module

Calibration library for calibrating sensors from sensors measurements

kontrol.sensact.calibration.calibrate(xdata, ydata, method=’linear’, **kwargs)Fit the measurement data and returns the slope of the fit.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• method (str, optional) –

The method of the fit. Choose from [“linear”, “erf”].

– ”linear”: use kontrol.sensact.calibration.calibrate_linear()

– ”erf”: use kontrol.sensact.calibration.calibrate_erf()

• **kwargs – Keyword arguments passed to the calibration methods.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The y-intercept of the fitted straight line.

• linear_range (float, optional) – The range of y where the sensor considered linear.

kontrol.sensact.calibration.calibrate_erf(xdata, ydata, nonlinearity=5, re-turn_linear_range=False, re-turn_model=False)

Fit an error function and return the 1st-order slope and intercept.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• nonlinearity (float, optional) – The specification of non-linearity (%). De-fined by the maximum deviation of the full_range. Default 5.

• return_linear_range (boolean, optional) – If True, return the linear range ofydata.

• return_model (boolean, optional) – Return a kontrol.curvefit.model.Model ob-ject with the fitted parameters.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The 𝑦-intercept of the fitted straight line.

• linear_range (float, optional) – The range of x where the sensor considered linear.

1.5. Kontrol API 137

Page 144: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

Credits to Kouseki Miyo, the inventor of this method.

We fit the following function

𝑓(𝑥; 𝑎,𝑚, 𝑥0, 𝑦0) = 𝑎 erf(𝑚(𝑥− 𝑥0)) + 𝑦0

where 𝑎,𝑚, 𝑥0, 𝑦0 are some parameters to be found. The erf(𝑥) function is defined as

erf(𝑥) =2√𝜋

∫ 𝑥

0

𝑒−𝑥2

𝑑𝑥 .

If we taylor expand the exponential function and take the first-order approximation of the erf(𝑥) function, weget

erf(𝑥) ≈ 2𝑎𝑚√𝜋(𝑥− 𝑥0) + 𝑦0 .

So the (inverse) calibration factor is 2𝑎𝑚√𝜋

, and the 𝑦-intercept is 2𝑎𝑚√𝜋𝑥0 + 𝑦0.

kontrol.sensact.calibration.calibrate_linear(xdata, ydata, nonlinearity=5,full_range=None, start_index=None,return_linear_range=False, re-turn_model=False)

Fit a straight line to the data and returns the slope and intercept

This functions recursively fits a straight line to chosen data points and terminates when no linear data pointremains. It starts from 3 points closest to the middle of the full-range. After fitting a straight line to the 3 points,other data points that are within the linearity specification will be included to the dataset and this dataset will beuse for the next fit. The process repeats until no data points can be added anymore.

Parameters

• xdata (array) – The independent variable, e.g. the displacement of the sensor.

• ydata (array) – The dependent variable, e.g. the sensor readout.

• nonlinearity (float, optional) – The specification of non-linearity (%). De-fined by the maximum deviation of the full_range. Default 5.

• full_range (float, optional) – The full output range of the sensor. If not speci-fied, it will be chosen to be max(ydata) - min(ydata)

• start_index (int, optional) – The index of the data point to start with. Ifnot specified, it will be chosen to be index of the ydata closest to (max(ydata) +min(ydata))/2.

• return_linear_range (boolean, optional) – If True, return the linear range ofydata.

• return_model (boolean, optional) – Return a kontrol.curvefit.model.Model ob-ject with the fitted parameters.

Returns

• slope (float) – The slope of the fitted straight line.

• intercept (float) – The y-intercept of the fitted straight line.

• linear_range (float, optional) – The range of x where the sensor considered linear.

• model (kontrol.curvefit.model.Model) – The fitted model.

138 Chapter 1. Features

Page 145: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.5.1.6 kontrol.regulator package

1.5.1.6.1 Primary modules

kontrol.regulator.feedback module

Algorithmic designs for feedback control regulators.

kontrol.regulator.feedback.add_integral_control(plant, regulator=None, in-tegrator_ugf=None, integra-tor_time_constant=None, **kwargs)

Match and returns an integral gain.

This function finds an integral gain such that the UGF of the integral control matches that of the specified regu-lator. If integrator_ugf or integrator_time_constant is specified instead, these will be matchedinstead.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled.

• regulator (TransferFunction, optional) – The pre-regulator Use kontrol.regulator.feedback.proportional_derivative() or kontrol.regulator.feedback.critical_damping() to make one for oscillator-likesystems.

• integrator_ugf (float, optional) – The unity gain frequency (Hz) ofthe integral control. This is the inverse of the integration time constant. Ifintegrator_time_constant is not None, then this value will be ignored. Ifset to None, it’ll be set to match the first UGF of the derivative control. Defaults to None.

• integrator_time_constant (float, optional,) – The integration time con-stant (s) for integral control. Setting this will override the integrator_ugf argument.Defaults to None.

Returns ki – The integral control gain.

Return type float

kontrol.regulator.feedback.add_proportional_control(plant, regulator=None, dc-gain=None, **kwargs)

Match and returns proportional gain.

This function finds a proportional gain such that the proportional control UGF matches the first UGF of thespecified regulator (typically a derivative control regulator). If dcgain is specified, the DC gain is matchedinstead.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• regulator (TransferFunction, optional) – The pre-regulator. If not specified,then dcgain must be specified. Defaults to None.

• dcgain (float, optional) – The desired DC gain of the open-loop transfer func-tion. If not specified, the portional gain is tuned such that the portional control’s first UGFmatches that of the derivative control. Defaults to None.

Returns kp – The proportional control gain.

1.5. Kontrol API 139

Page 146: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Return type float

kontrol.regulator.feedback.critical_damp_calculated(plant, nmode=1, **kwargs)Find dominant mode and returns the approximate critical regulator.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• nmode (int, optional) – The ‘‘nmode‘‘th dominant mode to be damped. This num-ber must be less than the number of modes in the plant. Defaults 1.

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

Notes

The plant must contain at least one complex pole pair. The plant must have a non-zero DC gain.

The critical regulator is approximated by

𝐾(𝑠) ≈ 2

𝜔𝑛𝐾DC,

where 𝜔𝑛 is the resonance frequency of the dominant mode and 𝐾DC is the sum of DC gains of the mode andthat of the other higher-frequency modes..

kontrol.regulator.feedback.critical_damp_optimize(plant, gain_step=1.1, ktol=1e-06,**kwargs)

Optimize derivative damping gain and returns the critical regulator

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• gain_step (float, optional) – The multiplicative factor of the gain for finding thegain upper bound. It must be greater than 1. Defaults to 1.1.

• ktol (float, optional) – The tolerance for the convergence condition. The conver-gence condition is (kd_max-kd_min)/kd_min > ktol. It must be greater than 0. Defaults to1e-6.

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

Notes

Update on 2021-12-04: Use carefully. It only critically damps the mode that has the highest peak when mul-tiplied by an differentiator. Note for myself: only 2 complex poles can become simple poles for plants withsecond-order rolloff.

Only works with plants that contain at least one complex pole pair. Works best with plants that only containcomplex zeros/poles. If it returns unreasonably high gain, try lowering gain_step.

The algorithm goes as follows.

1. Find the minimum damping gain k_min such that the open-loop transfer function k_min*s*plant hasmaxmimum gain at unity gain frequency.

140 Chapter 1. Features

Page 147: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

2. Iterate i: k_i=k_min*i*gain_step for i=1,2,3. . .

3. Terminate when 1/(1+k_i*s*plant) has less complex pole pairs than the plant itself, i.e. one modehas been overdamped. Then, define k_max=k_i.

4. Iterate: Define k_mid as the logarithmic mean of k_max and k_min. If 1/(1+k_mid*s*plant) hasless complex pole pairs than plant, i.e. overdamps, then set k_max=k_mid. Otherwise, set k_min=k_mid.

5. Terminate when (k_max - k_min)/k_min < ktol, i.e. converges.

kontrol.regulator.feedback.critical_damping(plant, method=’calculated’, **kwargs)Returns the critical damping derivative control gain.

This functions calls kontrol.regulator.feedback.critical_damp_calculated() orkontrol.regulator.feedback.cricical_damp_optimized() and returns the derivativecontrol gain.

Parameters

• plant (TransferFunction) – The transfer function representation of the system to befeedback controlled. The plant must contain at least one pair of complex poles.

• method (str, optional) – The method to be used for setting the gain. Choose from[“optimized”, “calculated”].

”optimized”: the gain is optimized until the dominant complex pole pairs become two sim-ple poles.

”calculated”: the gain is set to 2/𝜔𝑛/𝐾𝐷𝐶 , where 𝜔𝑛 is the resonance frequency in rad/s ofthe mode to be damped, and 𝐾𝐷𝐶 is the sum of DC gains of the mode and that of the otherhigh-frequency modes.

Both method assumes that the plant has at least one pair of complex poles.

Defaults to “calculated”.

• **kwargs (dict) – Method specific keyword arguments.

See:

– ”optimized”: kontrol.regulator.feedback.critical_damp_optimized

– ”calculated”: kontrol.regulator.feedback.critical_damp_calculated

Returns kd – The derivative gain for critically damping the dominant mode.

Return type float

kontrol.regulator.feedback.mode_composition(wn, q, k)Create a plant composed of many modes.

Parameters

• wn (array) –

• (rad/s) (Frequencies) –

• q (array) – Q factors.

• k (array) – Dcgains of the modes.

Returns The composed plant.

Return type TransferFunction

kontrol.regulator.feedback.mode_decomposition(plant)Returns a list of single mode transfer functions

1.5. Kontrol API 141

Page 148: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Parameters plant (TransferFunction) – The transfer function with at list one pair of com-plex poles.

Returns

• wn (array) – Frequencies (rad/s).

• q (array) – Q factors.

• k (array) – Dcgains of the modes.

kontrol.regulator.oscillator module

Control regulators designs for oscillator-like systems

kontrol.regulator.oscillator.pid(plant, regulator_type=’PID’, dcgain=None, integra-tor_ugf=None, integrator_time_constant=None, re-turn_gain=False, **kwargs)

PID-like controller design for oscillator-like systems

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator_type (str, optional) – The type of the contorl regulator. Choosefrom {"PID", "PD", "PI", "I", "D"} for proportional-integral-derivative,proportional-derivative, proportional-integral, or derivative (velocity) control respectively.Defaults to “PID”.

• dcgain (float, optional) – The DC gain of the OLTF of the proportional con-trol. If set to None, it will be set automatically depending on the type of the controller.If regulator_type=="PI", then dcgain will be set such that the proportional controlUGF matches that of the integral control. If regulator_type=="PD" or "PID",then the dcgain will be set such that it matches the UGF of the derivative control. Defaultsto None.

• integrator_ugf (float, optional) – The unity gain frequency (Hz) ofthe integral control. This is the inverse of the integration time constant. Ifintegrator_time_constant is not None, then this value will be ignored. If setto None, it’ll be set to match the UGF of the derivative control. For regulator_type=="I", this must be specified. Defaults to None.

• integrator_time_constant (float, optional,) – The integration time con-stant (s) for integral control. Setting this will override the integrator_ugf argument.Defaults to None.

• return_gain (boolean, optional) – Return the PID gain instead.

Returns

• regulator (TransferFunction, optional) – The regulator. Return only if return_gain isFalse.

• kp (float, optional) – Proportional gain. Return only if return_gain is ‘‘True”.

• ki (float, optional) – Integral gain. Return only if return_gain is True.

• kd (float, optional) – Derivative gain. Return only if return_gain is True.

142 Chapter 1. Features

Page 149: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.regulator.post_filter module

Functions for designing post-regulator filters

kontrol.regulator.post_filter.post_low_pass(plant, regulator, post_filter=None,ignore_ugf_above=None,decades_after_ugf=1, phase_margin=45,f_start=None, f_step=1.1, low_pass=None,mtol=1e-06, small_number=1e-06, oscilla-tory=True, **kwargs)

Add low-pass filter after regulator.

This function lowers/increase the the cutoff frequency of a low-pass filter until the phase margin at a dedicatedugf crosses the specified phase margin. Then, runs a bisection algorithm to poolish the cutoff frequency untilthe phase margin converges relative to the specified tolerance.

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator (TransferFunction) – The regulator.

• post_filter (TransferFunction, optional) – Any post filters that will be ap-plied on top of the regulator. Defaults None.

• ignore_ugf_above (float, optional) – Ignore unity gain frequencies higherthan ignore_ugf_above (Hz). If not specified, defaults to 1 decade higher than thelast UGF. This value can be overrided by the argument decades_after_ugf Note thatthere’s no guarantee that the UGF will be lower than this. The priority is to match the targetphase margin. Defaults to None.

• decades_after_ugf (float, optional) – Set ignore_ugf_above some decadeshigher than the UGF of the OLTF ignore_ugf_above is None. Defaults to 1.

• phase_margin (float, optional,) – The target phase margin (Degrees). Defaultsto 45.

• f_start (float, optional,) – The cutoff frequency to start iterating with. If notspecified, defaults to some decades higher than the highest UGF. “Some decade” is set bydecades_after_ugf. Defaults None.

• f_step (float, optional,) – The gain that is used to multiply (or divide) the cutofffrequency during a coarse search. Defaults 1.1

• low_pass (func(cutoff, order) -> TransferFunction, optional) –The low-pass filter. If not specified, kontrol.regulator.predefined.lowpass() with order 2 will be used. Defaults to None.

• mtol (float, optional) – Tolerance for convergence of phase margin. Defaults to1e-6.

• small_number (float, optional) – A small number as a delta f to detect whetherthe gain is a rising or lowering edge at the unity gain frequency. Defaults to 1e-6.

• oscillatory (boolean, optional) – Use the first mode of the oscillatory system toevaluate the phase margins to avoid having UGFs at steep phase slopes. The benefit of usingthis is to have a conservative phase margin estimate. The phase response of the first modeis the lower bound of the phase response. If False, use the plant itself to calculate phasemargins. If the plant does not contain any complex poles, this option will be overridden toFalse. Defaults True.

1.5. Kontrol API 143

Page 150: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• **kwargs – Keyword arguments passed to low_pass.

Returns The low-pass filter.

Return type TransferFunction

kontrol.regulator.post_filter.post_notch(plant, regulator=None, post_filter=None, tar-get_gain=None, notch_peaks_above=None,phase_margin=45, notch=None, **kwargs)

Returns a list of notch filters that suppress resonance peaks.

This functions finds the resonances peak of the plant/OLTF above certain frequencies and returns a list of notchfilters that suppress these peaks to the target gains.

Parameters

• plant (TransferFunction) – The transfer function of the system that needs to becontrolled.

• regulator (TransferFunction, optional) – The regulator. Defaults to None.

• post_filter (TransferFunction, optional) – Any post filters that will be ap-plied on top of the regulator. Defaults None.

• target_gain (float, optional) – The target open-loop gain for the suppressedpeak. To ensure a stable system, a value of less than 1 is recommended. If not specified, thenotch will fully suppress the peak. Default None.

• phase_margin (float, optional) – The target phase margin. Defaults to 45.

• notch_peaks_above (float, optional) – Notch modes that has freqeunciesabove notch_peaks_above. If not specified, defaults to the highest unity gain fre-quency that is above phase_margin Defaults to None.

• notch (func(frequency, q, depth) -> TransferFunction,optional) – The notch filter. If not specified, kontrol.Notch() will be used.Defaults to None.

• **kwargs – Keyword arguments passed to notch().

Returns A list of notch filters.

Return type list of TransferFunction

Notes

This operation does not guarantee stability. It only finds resonances peaking out of the unity gain above someunity gain frequency and make notch filters to suppress them to a target gain level lower than the unity gain. Thestability of the notched OLTF is not checked whatsoever.

1.5.1.6.2 Secondary modules

kontrol.regulator.predefined module

Predefined regulator library.

kontrol.regulator.predefined.low_pass(cutoff, order=1, **kwargs)Simple low-pass filter

Parameters

144 Chapter 1. Features

Page 151: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

• cutoff (float) – Cutoff frequency (Hz)

• order (int, optional) – The order of the filter. Defaults to be 1.

• **kwargs – Keyword arguments holder. Not passed to anywhere.

Returns The low-pass filter.

Return type TransferFunction

Notes

The low-pass filter is defined as

𝐿(𝑠) =

(2𝜋𝑓𝑐

𝑠+ 2𝜋𝑓𝑐

)𝑛

,

where 𝑓𝑐 is the cutoff frequency (Hz), 𝑛 is the order of the filter.

kontrol.regulator.predefined.notch(frequency, q, depth=None, depth_db=None, **kwargs)Notch filter defined in Foton.

Parameters

• frequency (float) – The notch frequency (Hz).

• q (float) – The quality factor.

• depth (float, optional) – The depth of the notch filter (magnitude). If not specified,depth_db will be used. Defaults None.

• depth_db (float, optional) – The depth of the notch filter (decibel). If not speci-fied, depth will be used instead. Defaults None.

Returns The notch filter

Return type TransferFunction

Notes

The notch filter is defined by Foton, as

𝑁(𝑠) =𝑠2 + (2𝜋𝑓𝑛)/(𝑑𝑄/2)𝑠+ (2𝜋𝑓𝑛)

2

𝑠2 + (2𝜋𝑓𝑛)/(𝑄/2)𝑠+ (2𝜋𝑓𝑛)2,

where 𝑓𝑛 is the notch frequency, 𝑞 is the quality factor , and 𝑑 is the depth.

kontrol.regulator.predefined.pid(kp=0, ki=0, kd=0)Alias of proportional_integral_derivative()

kontrol.regulator.predefined.proportional_integral_derivative(kp=0, ki=0,kd=0)

PID control build on proportional_derivative().

Parameters

• kp (float, optional) – The proportional control gain. Defaults to 0.

• ki (float, optional) – The integral control gain. Defaults to 0.

• kd (float, optional) – Defaults to 0.

Returns The PID controller.

Return type TransferFunction

1.5. Kontrol API 145

Page 152: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

The PID controller is defined as

𝐾PID(𝑠) = 𝐾𝑝 +𝐾𝑖/𝑠+𝐾𝑑𝑠 .

1.5.1.7 kontrol.transfer_function package

1.5.1.7.1 Primary modules

kontrol.transfer_function.transfer_function module

Transfer function class. Wrapper around control.TransferFunction to provide custom functionality related to KAGRA.

class kontrol.transfer_function.transfer_function.TransferFunction(*args)Bases: control.xferfcn.TransferFunction

Transfer function class

Parameters *args – Arguments passed to control.TransferFunction class.

foton(expression=’zpk’, root_location=’s’, significant_figures=6, itol=1e-25, epsilon=1e-25)Foton expression of this transfer function

Calls kontrol.core.foton.tf2foton and returns a foton expression of this transfer function

Parameters

• expression (str, optional) – Format of the foton expression. Choose from[“zpk”, “rpoly”]. Defaults to “zpk”.

• root_location (str, optional) – Root location of the zeros and poles for ex-pression==”zpk”. Choose from [“s”, “f”, “n”]. “s”: roots in s-plane, i.e. zpk([. . . ], [. . . ],. . . , “s”). “f”: roots in frequency plane, i.e. zpk([. . . ], [„,], . . . , “f”). “n”: roots in fre-quency plane but negated and gains are normalized, i.e. real parts are positive zpk([. . . ],[. . . ], . . . , “n”). Defaults to “s”.

• significant_figures (int, optional) – Number of significant figures to printout. Defaults to 6.

• itol (float, optional) – Treating complex roots as real roots if the ratio of theimaginary part and the real part is smaller than this tolerance Defaults to 1e-25.

• epsilon (float, optional) – Small number to add to denominator to prevent di-vision error. Defaults to 1e-25.

Returns foton_expression – The foton expression in selected format.

Return type str

lstrip(element, fc=None)Remove zero or pole from the left.

Parameters

• element (str) – Element to be removed. Choose from [“any”, “zero”, “pole”, “pair”,“all”].

• fc (float or None, optional) – Cutoff frequency. If element is in [“any”, “all”],remove any or all elements on the left of the cutoff. Defaults None.

146 Chapter 1. Features

Page 153: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

rstrip(element, fc=None)Remove zero or pole from the right.

Parameters

• element (str) – Element to be removed. Choose from [“any”, “zero”, “pole”, “pair”,“all”].

• fc (float or None, optional) – Cutoff frequency. If element is in [“any”, “all”],remove any or all elements on the left of the cutoff. Defaults None.

save(path, overwrite=True)Save the transfer function to a specified path.

This functions extracts the numerator and denominator coefficients and puts it into a pandas DataFramewith keys {“num” and “den”}. Then it outputs to the specified path with a pickle format.

Parameters

• path (str) – The string of the path.

• overwrite (boolean, optional) – Overwrite if the file exists.

stabilize()Convert unstable zeros and poles to stable ones.

kontrol.transfer_function.transfer_function.load_transfer_function(path)Load a kontrol TransferFunction object from path.

Parameters path (str) – The path of the saved transfer function

Returns The loaded TransferFunction object.

Return type TransferFunction

kontrol.transfer_function.notch module

Foton defined Notch filter.

class kontrol.transfer_function.notch.Notch(frequency, q, depth=None, depth_db=None)Bases: kontrol.transfer_function.transfer_function.TransferFunction

Notch Filter Object

Parameters

• frequency (float) – The notch frequency (Hz).

• q (float) – The quality factor.

• depth (float) – The depth of the notch filter (magnitude). If not specified, depth_dbwill be used. Defaults None.

• depth_db (float, optional) – The depth of the notch filter (decibel). If not speci-fied, depth will be used instead. Defaults None.

• Attritubes –

• ---------- –

• frequency – The notch frequency (Hz).

• q – The quality factor.

• depth – The depth of the notch filter (magnitude).

1.5. Kontrol API 147

Page 154: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Notes

The notch filter is defined by Foton, as

𝑁(𝑠) =𝑠2 + (2𝜋𝑓𝑛)/(𝑑𝑄/2)𝑠+ (2𝜋𝑓𝑛)

2

𝑠2 + (2𝜋𝑓𝑛)/(𝑄/2)𝑠+ (2𝜋𝑓𝑛)2,

where 𝑓𝑛 is the notch frequency, 𝑞 is the quality factor , and 𝑑 is the depth.

depthThe depth of the notch filter (magnitude).

foton()Foton expression of this notch filter.

frequencyThe notch frequency (Hz).

qThe quality factor

1.5.1.7.2 Secondary modules

1.5.2 Submodules

1.6 Contact

Author TSANG Terrence Tak Lun

E-mail addresses [email protected] (KAGRA contact)

[email protected]

[email protected]

1.7 For Developers

1.7.1 Standards and Tools

Please comply with the following standards/guides as much as possible.

1.7.1.1 Coding style

• PEP 8: https://www.python.org/dev/peps/pep-0008/

1.7.1.2 CHANGELOG

• Keep a Changelog: https://keepachangelog.com/en/1.0.0/

1.7.1.3 Versioning

• Semantic Versioning: https://semver.org/spec/v2.0.0.html

148 Chapter 1. Features

Page 155: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

1.7.1.4 Packaging

• PyPA: https://www.pypa.io

• python-packaging: https://python-packaging.readthedocs.io

1.7.1.5 Documentation

• NumPy docstrings: https://numpydoc.readthedocs.io/en/latest/format.html

• Sphinx: https://www.sphinx-doc.org/

• Read The Docs: https://readthedocs.org/

• Documenting Python Code: A Complete Guide: https://realpython.com/documenting-python-code/

1.7.2 How to Contribute

Just do it.

1.7.2.1 Pending

• Documentation.

• tests!

• Model reference sensor/actuator diagonalization

• Add support for reading Shoda-san’s SUMCON simulations.

• Controller optimization

• Optimal controller synthesis

• python-foton interface.

• Diaggui support.

• Issues: https://github.com/terrencetec/kontrol/issues

1.7.3 Cheat sheet

Auto generate source files (sphinx) in /kontrol, type

sphinx-apidoc -o docs/source kontrol

1.7. For Developers 149

Page 156: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

150 Chapter 1. Features

Page 157: Python Kontrol Library

CHAPTER 2

Indices and tables

• genindex

• modindex

• search

151

Page 158: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

152 Chapter 2. Indices and tables

Page 159: Python Kontrol Library

Python Module Index

kkontrol.complementary_filter.complementary_filter,

110kontrol.complementary_filter.predefined,

112kontrol.complementary_filter.synthesis,

113kontrol.core.controlutils, 102kontrol.core.foton, 85kontrol.core.math, 105kontrol.core.spectral, 83kontrol.curvefit.cost, 117kontrol.curvefit.curvefit, 114kontrol.curvefit.error_func, 117kontrol.curvefit.model.math_model, 122kontrol.curvefit.model.model, 118kontrol.curvefit.model.transfer_function_model,

119kontrol.curvefit.transfer_function_fit,

116kontrol.frequency_series.conversion, 127kontrol.frequency_series.costs, 128kontrol.frequency_series.frequency_series,

124kontrol.frequency_series.noise_models,

129kontrol.regulator.feedback, 95kontrol.regulator.oscillator, 98kontrol.regulator.post_filter, 98kontrol.regulator.predefined, 100kontrol.sensact.calibration, 81kontrol.sensact.matrix, 130kontrol.sensact.optical_lever, 131kontrol.transfer_function.notch, 147kontrol.transfer_function.transfer_function,

146

153

Page 160: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

154 Python Module Index

Page 161: Python Kontrol Library

Index

Aadd_integral_control() (in module kon-

trol.regulator.feedback), 95, 139add_proportional_control() (in module kon-

trol.regulator.feedback), 95, 139alpha_h (kontrol.OpticalLeverSensingMatrix at-

tribute), 78alpha_h (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 133alpha_v (kontrol.OpticalLeverSensingMatrix at-

tribute), 78alpha_v (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 133amplitude (kontrol.curvefit.model.Erf attribute), 94amplitude (kontrol.curvefit.model.math_model.Erf at-

tribute), 123args (kontrol.curvefit.model.ComplexZPK attribute), 93args (kontrol.curvefit.model.DampedOscillator at-

tribute), 91args (kontrol.curvefit.model.Model attribute), 90args (kontrol.curvefit.model.model.Model attribute),

119args (kontrol.curvefit.model.SimpleZPK attribute), 92args (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 119args (kontrol.curvefit.model.transfer_function_model.DampedOscillator

attribute), 120args (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121args2controltf() (in module kon-

trol.frequency_series.conversion), 127args2tf() (in module kon-

trol.frequency_series.conversion), 127args2zpk() (in module kon-

trol.frequency_series.conversion), 127args_empirical_model (kon-

trol.frequency_series.frequency_series.FrequencySeriesattribute), 124

args_empirical_model (kontrol.FrequencySeries

attribute), 73args_tf_model (kon-

trol.frequency_series.frequency_series.FrequencySeriesattribute), 125

args_tf_model (kontrol.FrequencySeries attribute),74

args_zpk_model (kon-trol.frequency_series.frequency_series.FrequencySeriesattribute), 124

args_zpk_model (kontrol.FrequencySeries attribute),74

asd2rms() (in module kontrol.core.spectral), 83, 105asd2ts() (in module kontrol.core.spectral), 83, 106

Cc_align() (in module kontrol.sensact.optical_lever),

135c_miscenter() (in module kon-

trol.sensact.optical_lever), 136c_rotation() (in module kon-

trol.sensact.optical_lever), 136calibrate() (in module kontrol.sensact.calibration),

81, 137calibrate_erf() (in module kon-

trol.sensact.calibration), 81, 137calibrate_linear() (in module kon-

trol.sensact.calibration), 82, 138check_tf_equal() (in module kon-

trol.core.controlutils), 102ComplementaryFilter (class in kontrol), 71ComplementaryFilter (class in kon-

trol.complementary_filter.complementary_filter),110

ComplexZPK (class in kontrol.curvefit.model), 92ComplexZPK (class in kon-

trol.curvefit.model.transfer_function_model),119

convert_unstable_tf() (in module kon-trol.core.controlutils), 102

Cost (class in kontrol.curvefit.cost), 117

155

Page 162: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

cost (kontrol.curvefit.CurveFit attribute), 88cost (kontrol.curvefit.curvefit.CurveFit attribute), 115cost_empirical_fit() (in module kon-

trol.frequency_series.costs), 128cost_tf_fit() (in module kon-

trol.frequency_series.costs), 128cost_zpk_fit() (in module kon-

trol.frequency_series.costs), 128CoupledOscillator (class in kon-

trol.curvefit.model.transfer_function_model),120

critical_damp_calculated() (in module kon-trol.regulator.feedback), 95, 140

critical_damp_optimize() (in module kon-trol.regulator.feedback), 96, 140

critical_damping() (in module kon-trol.regulator.feedback), 97, 141

CurveFit (class in kontrol.curvefit), 87CurveFit (class in kontrol.curvefit.curvefit), 114

Dd_h (kontrol.OpticalLeverSensingMatrix attribute), 78d_h (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 133d_v (kontrol.OpticalLeverSensingMatrix attribute), 78d_v (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 133damped_oscillator_args (kon-

trol.curvefit.model.DampedOscillator at-tribute), 91

damped_oscillator_args (kon-trol.curvefit.model.transfer_function_model.DampedOscillatorattribute), 120

DampedOscillator (class in kontrol.curvefit.model),90

DampedOscillator (class in kon-trol.curvefit.model.transfer_function_model),120

delta_d (kontrol.HorizontalOpticalLeverSensingMatrixattribute), 80

delta_d (kontrol.sensact.optical_lever.HorizontalOpticalLeverSensingMatrixattribute), 131

delta_d (kontrol.sensact.optical_lever.VerticalOpticalLeverSensingMatrixattribute), 135

delta_d (kontrol.VerticalOpticalLeverSensingMatrixattribute), 81

delta_x (kontrol.OpticalLeverSensingMatrix at-tribute), 78

delta_x (kontrol.sensact.optical_lever.OpticalLeverSensingMatrixattribute), 133

delta_y (kontrol.OpticalLeverSensingMatrix at-tribute), 78

delta_y (kontrol.sensact.optical_lever.OpticalLeverSensingMatrixattribute), 134

den (kontrol.curvefit.model.transfer_function_model.TransferFunctionModelattribute), 122

den (kontrol.curvefit.model.TransferFunctionModel at-tribute), 90

depth (kontrol.transfer_function.notch.Notch attribute),148

diagonalize() (kon-trol.sensact.matrix.SensingMatrix method),130

diagonalize() (kontrol.SensingMatrix method), 76

EErf (class in kontrol.curvefit.model), 94Erf (class in kontrol.curvefit.model.math_model), 122error_func (kontrol.curvefit.cost.Cost attribute), 117error_func_kwargs (kontrol.curvefit.cost.Cost at-

tribute), 117

Ff (kontrol.complementary_filter.complementary_filter.ComplementaryFilter

attribute), 111f (kontrol.ComplementaryFilter attribute), 72f (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124f (kontrol.FrequencySeries attribute), 73f (kontrol.HorizontalOpticalLeverSensingMatrix at-

tribute), 80f (kontrol.sensact.optical_lever.HorizontalOpticalLeverSensingMatrix

attribute), 131f (kontrol.sensact.optical_lever.VerticalOpticalLeverSensingMatrix

attribute), 135f (kontrol.VerticalOpticalLeverSensingMatrix attribute),

81f_h (kontrol.OpticalLeverSensingMatrix attribute), 78f_h (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134f_tf (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124f_tf (kontrol.FrequencySeries attribute), 74f_v (kontrol.OpticalLeverSensingMatrix attribute), 78f_v (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134f_zpk (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124f_zpk (kontrol.FrequencySeries attribute), 73filter1 (kontrol.complementary_filter.complementary_filter.ComplementaryFilter

attribute), 111filter1 (kontrol.ComplementaryFilter attribute), 72filter2 (kontrol.complementary_filter.complementary_filter.ComplementaryFilter

attribute), 111filter2 (kontrol.ComplementaryFilter attribute), 72fit() (kontrol.curvefit.CurveFit method), 88fit() (kontrol.curvefit.curvefit.CurveFit method), 115

156 Index

Page 163: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

fit_empirical() (kon-trol.frequency_series.frequency_series.FrequencySeriesmethod), 125

fit_empirical() (kontrol.FrequencySeries method),74

fit_tf() (kontrol.frequency_series.frequency_series.FrequencySeriesmethod), 125

fit_tf() (kontrol.FrequencySeries method), 75fit_zpk() (kontrol.frequency_series.frequency_series.FrequencySeries

method), 126fit_zpk() (kontrol.FrequencySeries method), 75fn (kontrol.curvefit.model.DampedOscillator attribute),

91fn (kontrol.curvefit.model.transfer_function_model.DampedOscillator

attribute), 120fn_pole (kontrol.curvefit.model.ComplexZPK at-

tribute), 92, 93fn_pole (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 119, 120fn_zero (kontrol.curvefit.model.ComplexZPK at-

tribute), 92, 93fn_zero (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 119, 120format (kontrol.OpticalLeverSensingMatrix attribute),

78format (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134foton() (kontrol.transfer_function.notch.Notch

method), 148foton() (kontrol.transfer_function.transfer_function.TransferFunction

method), 146frequency (kontrol.transfer_function.notch.Notch at-

tribute), 148FrequencySeries (class in kontrol), 73FrequencySeries (class in kon-

trol.frequency_series.frequency_series), 124

Ggain (kontrol.curvefit.model.ComplexZPK attribute), 93gain (kontrol.curvefit.model.DampedOscillator at-

tribute), 91gain (kontrol.curvefit.model.SimpleZPK attribute), 91,

92gain (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 120gain (kontrol.curvefit.model.transfer_function_model.DampedOscillator

attribute), 120gain (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121generalized_plant() (in module kon-

trol.complementary_filter.synthesis), 113generic_tf() (in module kontrol.core.controlutils),

102

geophone_noise() (in module kon-trol.frequency_series.noise_models), 129

Hh2complementary() (in module kon-

trol.complementary_filter.synthesis), 113h2synthesis() (kon-

trol.complementary_filter.complementary_filter.ComplementaryFiltermethod), 110, 111

h2synthesis() (kontrol.ComplementaryFiltermethod), 72

hinfcomplementary() (in module kon-trol.complementary_filter.synthesis), 114

hinfsynthesis() (kon-trol.complementary_filter.complementary_filter.ComplementaryFiltermethod), 110, 111

hinfsynthesis() (kontrol.ComplementaryFiltermethod), 72

HorizontalOpticalLeverSensingMatrix(class in kontrol), 79

HorizontalOpticalLeverSensingMatrix(class in kontrol.sensact.optical_lever), 131

Iintercept (kontrol.curvefit.model.math_model.StraightLine

attribute), 123intercept (kontrol.curvefit.model.StraightLine at-

tribute), 93, 94

Kkontrol.complementary_filter.complementary_filter

(module), 110kontrol.complementary_filter.predefined

(module), 112kontrol.complementary_filter.synthesis

(module), 113kontrol.core.controlutils (module), 102kontrol.core.foton (module), 85, 108kontrol.core.math (module), 105kontrol.core.spectral (module), 83, 105kontrol.curvefit.cost (module), 117kontrol.curvefit.curvefit (module), 114kontrol.curvefit.error_func (module), 117kontrol.curvefit.model.math_model (mod-

ule), 122kontrol.curvefit.model.model (module), 118kontrol.curvefit.model.transfer_function_model

(module), 119kontrol.curvefit.transfer_function_fit

(module), 116kontrol.frequency_series.conversion

(module), 127kontrol.frequency_series.costs (module),

128

Index 157

Page 164: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

kontrol.frequency_series.frequency_series(module), 124

kontrol.frequency_series.noise_models(module), 129

kontrol.regulator.feedback (module), 95, 139kontrol.regulator.oscillator (module), 98,

142kontrol.regulator.post_filter (module), 98,

143kontrol.regulator.predefined (module), 100,

144kontrol.sensact.calibration (module), 81,

137kontrol.sensact.matrix (module), 130kontrol.sensact.optical_lever (module),

131kontrol.transfer_function.notch (module),

147kontrol.transfer_function.transfer_function

(module), 146

Lload_transfer_function() (in module kon-

trol.transfer_function.transfer_function), 147log_mse() (in module kontrol.core.math), 105log_mse() (in module kontrol.curvefit.error_func), 117low_pass() (in module kontrol.regulator.predefined),

100, 144lstrip() (kontrol.transfer_function.transfer_function.TransferFunction

method), 146lucia() (in module kon-

trol.complementary_filter.predefined), 112lvdt_noise() (in module kon-

trol.frequency_series.noise_models), 129

MMatrix (class in kontrol.sensact.matrix), 130mode_composition() (in module kon-

trol.regulator.feedback), 97, 141mode_decomposition() (in module kon-

trol.regulator.feedback), 97, 141Model (class in kontrol.curvefit.model), 89Model (class in kontrol.curvefit.model.model), 118model (kontrol.curvefit.CurveFit attribute), 88model (kontrol.curvefit.curvefit.CurveFit attribute), 115model_empirical (kon-

trol.frequency_series.frequency_series.FrequencySeriesattribute), 124

model_empirical (kontrol.FrequencySeries at-tribute), 73

model_kwargs (kontrol.curvefit.CurveFit attribute),88

model_kwargs (kontrol.curvefit.curvefit.CurveFit at-tribute), 115

modified_sekiguchi() (in module kon-trol.complementary_filter.predefined), 112

mse() (in module kontrol.core.math), 105mse() (in module kontrol.curvefit.error_func), 118

Nnargs (kontrol.curvefit.model.Model attribute), 90nargs (kontrol.curvefit.model.model.Model attribute),

119noise1 (kontrol.complementary_filter.complementary_filter.ComplementaryFilter

attribute), 111noise1 (kontrol.ComplementaryFilter attribute), 72noise2 (kontrol.complementary_filter.complementary_filter.ComplementaryFilter

attribute), 111noise2 (kontrol.ComplementaryFilter attribute), 72noise_super() (kon-

trol.complementary_filter.complementary_filter.ComplementaryFiltermethod), 111

noise_super() (kontrol.ComplementaryFiltermethod), 73

Notch (class in kontrol.transfer_function.notch), 147notch() (in module kontrol.core.foton), 85, 108notch() (in module kontrol.regulator.predefined), 101,

145npole (kontrol.curvefit.model.SimpleZPK attribute), 92npole (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121npole (kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

attribute), 122npole (kontrol.curvefit.model.TransferFunctionModel

attribute), 90npole_pairs (kontrol.curvefit.model.ComplexZPK at-

tribute), 93npole_pairs (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 120num (kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

attribute), 122num (kontrol.curvefit.model.TransferFunctionModel at-

tribute), 90nzero (kontrol.curvefit.model.SimpleZPK attribute), 92nzero (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121nzero (kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

attribute), 122nzero (kontrol.curvefit.model.TransferFunctionModel

attribute), 90nzero_pairs (kontrol.curvefit.model.ComplexZPK at-

tribute), 93nzero_pairs (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 120

OOpticalLeverSensingMatrix (class in kontrol),

76

158 Index

Page 165: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

OpticalLeverSensingMatrix (class in kon-trol.sensact.optical_lever), 132

optimize_result (kontrol.curvefit.CurveFit at-tribute), 88

optimize_result (kontrol.curvefit.curvefit.CurveFitattribute), 115

optimized_args (kontrol.curvefit.CurveFit at-tribute), 88

optimized_args (kontrol.curvefit.curvefit.CurveFitattribute), 116

optimizer (kontrol.curvefit.CurveFit attribute), 88optimizer (kontrol.curvefit.curvefit.CurveFit at-

tribute), 116optimizer_kwargs (kontrol.curvefit.CurveFit

attribute), 88optimizer_kwargs (kon-

trol.curvefit.curvefit.CurveFit attribute),116

options (kontrol.curvefit.transfer_function_fit.TransferFunctionFitattribute), 117

options (kontrol.curvefit.TransferFunctionFit at-tribute), 89

outlier_exists() (in module kon-trol.core.controlutils), 103

outliers() (in module kontrol.core.controlutils), 103

Pphi_len (kontrol.OpticalLeverSensingMatrix at-

tribute), 79phi_len (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134phi_tilt (kontrol.OpticalLeverSensingMatrix at-

tribute), 79phi_tilt (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134pid() (in module kontrol.regulator.oscillator), 98, 142pid() (in module kontrol.regulator.predefined), 101,

145piecewise_noise() (in module kon-

trol.frequency_series.noise_models), 130pole (kontrol.curvefit.model.SimpleZPK attribute), 91,

92pole (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121, 122polyval() (in module kontrol.core.math), 105post_low_pass() (in module kon-

trol.regulator.post_filter), 98, 143post_notch() (in module kon-

trol.regulator.post_filter), 100, 144proportional_integral_derivative() (in

module kontrol.regulator.predefined), 101, 145

Qq (kontrol.curvefit.model.DampedOscillator attribute),

91q (kontrol.curvefit.model.transfer_function_model.DampedOscillator

attribute), 120q (kontrol.transfer_function.notch.Notch attribute), 148q_pole (kontrol.curvefit.model.ComplexZPK attribute),

93q_pole (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 119, 120q_zero (kontrol.curvefit.model.ComplexZPK attribute),

93q_zero (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 119, 120quad_sum() (in module kontrol.core.math), 105

Rr (kontrol.HorizontalOpticalLeverSensingMatrix at-

tribute), 80r (kontrol.sensact.optical_lever.HorizontalOpticalLeverSensingMatrix

attribute), 132r (kontrol.sensact.optical_lever.VerticalOpticalLeverSensingMatrix

attribute), 135r (kontrol.VerticalOpticalLeverSensingMatrix attribute),

81r_h (kontrol.OpticalLeverSensingMatrix attribute), 79r_h (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134r_lens (kontrol.HorizontalOpticalLeverSensingMatrix

attribute), 80r_lens (kontrol.sensact.optical_lever.HorizontalOpticalLeverSensingMatrix

attribute), 132r_lens (kontrol.sensact.optical_lever.VerticalOpticalLeverSensingMatrix

attribute), 135r_lens (kontrol.VerticalOpticalLeverSensingMatrix at-

tribute), 81r_lens_h (kontrol.OpticalLeverSensingMatrix at-

tribute), 79r_lens_h (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134r_lens_v (kontrol.OpticalLeverSensingMatrix at-

tribute), 79r_lens_v (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134r_v (kontrol.OpticalLeverSensingMatrix attribute), 79r_v (kontrol.sensact.optical_lever.OpticalLeverSensingMatrix

attribute), 134rstrip() (kontrol.transfer_function.transfer_function.TransferFunction

method), 146

Ssave() (kontrol.transfer_function.transfer_function.TransferFunction

method), 147sekiguchi() (in module kon-

trol.complementary_filter.predefined), 112SensingMatrix (class in kontrol), 76

Index 159

Page 166: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

SensingMatrix (class in kontrol.sensact.matrix), 130SimpleZPK (class in kontrol.curvefit.model), 91SimpleZPK (class in kon-

trol.curvefit.model.transfer_function_model),121

slope (kontrol.curvefit.model.Erf attribute), 94slope (kontrol.curvefit.model.math_model.Erf at-

tribute), 123slope (kontrol.curvefit.model.math_model.StraightLine

attribute), 123slope (kontrol.curvefit.model.StraightLine attribute),

93, 94sos() (in module kontrol.core.controlutils), 103stabilize() (kontrol.transfer_function.transfer_function.TransferFunction

method), 147StraightLine (class in kontrol.curvefit.model), 93StraightLine (class in kon-

trol.curvefit.model.math_model), 123

Ttf (kontrol.curvefit.model.ComplexZPK attribute), 93tf (kontrol.curvefit.model.SimpleZPK attribute), 91, 92tf (kontrol.curvefit.model.transfer_function_model.ComplexZPK

attribute), 120tf (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121, 122tf (kontrol.curvefit.model.transfer_function_model.TransferFunctionModel

attribute), 122tf (kontrol.curvefit.model.TransferFunctionModel at-

tribute), 90tf (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 125tf (kontrol.FrequencySeries attribute), 74tf2foton() (in module kontrol.core.foton), 86, 108tf2rpoly() (in module kontrol.core.foton), 86, 109tf2tf_args() (in module kon-

trol.frequency_series.conversion), 127tf2zpk() (in module kontrol.core.foton), 86, 109tf_args2tf() (in module kon-

trol.frequency_series.conversion), 127tf_error() (in module kontrol.curvefit.error_func),

118tf_order_split() (in module kon-

trol.core.controlutils), 104tf_zpk (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124tf_zpk (kontrol.FrequencySeries attribute), 74tfmatrix2tf() (in module kontrol.core.controlutils),

104three_channel_correlation() (in module kon-

trol.core.spectral), 84, 106TransferFunction (class in kon-

trol.transfer_function.transfer_function),146

TransferFunctionFit (class in kontrol.curvefit), 88TransferFunctionFit (class in kon-

trol.curvefit.transfer_function_fit), 116TransferFunctionModel (class in kon-

trol.curvefit.model), 90TransferFunctionModel (class in kon-

trol.curvefit.model.transfer_function_model),122

two_channel_correlation() (in module kon-trol.core.spectral), 85, 108

Uupdate_matrices_decorator() (kon-

trol.OpticalLeverSensingMatrix method),79

update_matrices_decorator() (kon-trol.sensact.optical_lever.OpticalLeverSensingMatrixmethod), 134

VVerticalOpticalLeverSensingMatrix (class

in kontrol), 80VerticalOpticalLeverSensingMatrix (class

in kontrol.sensact.optical_lever), 134

Wweight (kontrol.curvefit.transfer_function_fit.TransferFunctionFit

attribute), 117weight (kontrol.curvefit.TransferFunctionFit attribute),

89

Xx (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124x (kontrol.FrequencySeries attribute), 73x0 (kontrol.curvefit.transfer_function_fit.TransferFunctionFit

attribute), 117x0 (kontrol.curvefit.TransferFunctionFit attribute), 89x_empirical (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124x_empirical (kontrol.FrequencySeries attribute), 73x_offset (kontrol.curvefit.model.Erf attribute), 94x_offset (kontrol.curvefit.model.math_model.Erf at-

tribute), 123x_tf (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124x_tf (kontrol.FrequencySeries attribute), 74x_zpk (kontrol.frequency_series.frequency_series.FrequencySeries

attribute), 124x_zpk (kontrol.FrequencySeries attribute), 73xdata (kontrol.curvefit.CurveFit attribute), 88xdata (kontrol.curvefit.curvefit.CurveFit attribute), 116

160 Index

Page 167: Python Kontrol Library

Python Kontrol Library, Release 1.1.0

Yy_offset (kontrol.curvefit.model.Erf attribute), 94y_offset (kontrol.curvefit.model.math_model.Erf at-

tribute), 123ydata (kontrol.curvefit.CurveFit attribute), 88ydata (kontrol.curvefit.curvefit.CurveFit attribute), 116yfit (kontrol.curvefit.CurveFit attribute), 88yfit (kontrol.curvefit.curvefit.CurveFit attribute), 116

Zzero (kontrol.curvefit.model.SimpleZPK attribute), 91,

92zero (kontrol.curvefit.model.transfer_function_model.SimpleZPK

attribute), 121, 122zpk() (in module kontrol.core.controlutils), 104

Index 161