loopkit 0.0.1a1__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loopkit-1.0.0/AUTHORS +1 -0
- loopkit-1.0.0/LICENSE +34 -0
- loopkit-1.0.0/MANIFEST.in +20 -0
- loopkit-1.0.0/PKG-INFO +228 -0
- loopkit-1.0.0/README.md +159 -0
- loopkit-1.0.0/loopkit/__init__.py +12 -0
- loopkit-1.0.0/loopkit/component.py +611 -0
- loopkit-1.0.0/loopkit/components/__init__.py +66 -0
- loopkit-1.0.0/loopkit/components/_opamp.py +135 -0
- loopkit-1.0.0/loopkit/components/_validation.py +115 -0
- loopkit-1.0.0/loopkit/components/actuators.py +301 -0
- loopkit-1.0.0/loopkit/components/controllers.py +958 -0
- loopkit-1.0.0/loopkit/components/delay.py +111 -0
- loopkit-1.0.0/loopkit/components/filters.py +301 -0
- loopkit-1.0.0/loopkit/components/gains.py +181 -0
- loopkit-1.0.0/loopkit/components/pll_components.py +198 -0
- loopkit-1.0.0/loopkit/dimension.py +216 -0
- loopkit-1.0.0/loopkit/dsp.py +252 -0
- loopkit-1.0.0/loopkit/loop.py +1255 -0
- loopkit-1.0.0/loopkit/loopmath.py +917 -0
- loopkit-1.0.0/loopkit/loops/__init__.py +44 -0
- loopkit-1.0.0/loopkit/loops/laserlock.py +221 -0
- loopkit-1.0.0/loopkit/loops/mokulaserlock.py +108 -0
- loopkit-1.0.0/loopkit/loops/nprolaserlock.py +676 -0
- loopkit-1.0.0/loopkit/loops/pll.py +342 -0
- loopkit-1.0.0/loopkit/plots.py +318 -0
- loopkit-1.0.0/loopkit/simulation.py +569 -0
- loopkit-1.0.0/loopkit/utils.py +144 -0
- loopkit-1.0.0/loopkit/vectfit3.py +897 -0
- loopkit-1.0.0/loopkit.egg-info/PKG-INFO +228 -0
- loopkit-1.0.0/loopkit.egg-info/SOURCES.txt +46 -0
- loopkit-1.0.0/loopkit.egg-info/requires.txt +17 -0
- loopkit-1.0.0/pyproject.toml +49 -0
- loopkit-1.0.0/tests/__init__.py +1 -0
- loopkit-1.0.0/tests/conftest.py +6 -0
- loopkit-1.0.0/tests/generate_pll_golden.py +90 -0
- loopkit-1.0.0/tests/golden/README.md +18 -0
- loopkit-1.0.0/tests/golden/pll_golden.npz +0 -0
- loopkit-1.0.0/tests/golden/pll_golden_params.json +10 -0
- loopkit-1.0.0/tests/test_component.py +270 -0
- loopkit-1.0.0/tests/test_dimension.py +289 -0
- loopkit-1.0.0/tests/test_dsp.py +283 -0
- loopkit-1.0.0/tests/test_loop.py +498 -0
- loopkit-1.0.0/tests/test_loopmath.py +499 -0
- loopkit-1.0.0/tests/test_pll.py +416 -0
- loopkit-0.0.1a1/LICENSE +0 -7
- loopkit-0.0.1a1/PKG-INFO +0 -44
- loopkit-0.0.1a1/README.md +0 -1
- loopkit-0.0.1a1/pyproject.toml +0 -68
- loopkit-0.0.1a1/src/loopkit/__init__.py +0 -85
- loopkit-0.0.1a1/src/loopkit/cli/__init__.py +0 -3
- loopkit-0.0.1a1/src/loopkit/cli/compare.py +0 -274
- loopkit-0.0.1a1/src/loopkit/cli/main.py +0 -62
- loopkit-0.0.1a1/src/loopkit/cli/sweep.py +0 -313
- loopkit-0.0.1a1/src/loopkit/cli/visualize.py +0 -285
- loopkit-0.0.1a1/src/loopkit/config.py +0 -1236
- loopkit-0.0.1a1/src/loopkit/git.py +0 -154
- loopkit-0.0.1a1/src/loopkit/logger.py +0 -729
- loopkit-0.0.1a1/src/loopkit/monitor.py +0 -259
- loopkit-0.0.1a1/src/loopkit/torch/__init__.py +0 -43
- loopkit-0.0.1a1/src/loopkit/torch/checkpoint.py +0 -339
- loopkit-0.0.1a1/src/loopkit/torch/mp.py +0 -346
- loopkit-0.0.1a1/src/loopkit/tracking.py +0 -203
- loopkit-0.0.1a1/src/loopkit/utils.py +0 -75
- loopkit-0.0.1a1/src/loopkit.egg-info/PKG-INFO +0 -44
- loopkit-0.0.1a1/src/loopkit.egg-info/SOURCES.txt +0 -24
- loopkit-0.0.1a1/src/loopkit.egg-info/entry_points.txt +0 -2
- loopkit-0.0.1a1/src/loopkit.egg-info/requires.txt +0 -23
- {loopkit-0.0.1a1/src → loopkit-1.0.0}/loopkit.egg-info/dependency_links.txt +0 -0
- {loopkit-0.0.1a1/src → loopkit-1.0.0}/loopkit.egg-info/top_level.txt +0 -0
- {loopkit-0.0.1a1 → loopkit-1.0.0}/setup.cfg +0 -0
loopkit-1.0.0/AUTHORS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Miguel Dovale, University of Arizona
|
loopkit-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Miguel Dovale
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
This software may be subject to U.S. export control laws. By accepting this
|
|
31
|
+
software, the user agrees to comply with all applicable U.S. export laws and
|
|
32
|
+
regulations. User has the responsibility to obtain export licenses, or other
|
|
33
|
+
export authority as may be required before exporting such information to
|
|
34
|
+
foreign countries or providing access to foreign persons.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Include top-level metadata
|
|
2
|
+
include LICENSE
|
|
3
|
+
include README.md
|
|
4
|
+
include AUTHORS
|
|
5
|
+
include pyproject.toml
|
|
6
|
+
|
|
7
|
+
# Ship library source
|
|
8
|
+
graft loopkit
|
|
9
|
+
|
|
10
|
+
# Ship tests and examples in sdist
|
|
11
|
+
graft examples
|
|
12
|
+
graft tests
|
|
13
|
+
include tests/*.gz
|
|
14
|
+
|
|
15
|
+
# Exclusions
|
|
16
|
+
prune **/__pycache__
|
|
17
|
+
prune tests/__pycache__
|
|
18
|
+
|
|
19
|
+
# Global ignores
|
|
20
|
+
global-exclude *.py[cod] *.so *.dylib *.dll *.DS_Store
|
loopkit-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loopkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Python toolkit for Deep Frequency Modulation Interferometry.
|
|
5
|
+
Author-email: Miguel Dovale <mikedovale@pm.me>
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025, Miguel Dovale
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
34
|
+
|
|
35
|
+
This software may be subject to U.S. export control laws. By accepting this
|
|
36
|
+
software, the user agrees to comply with all applicable U.S. export laws and
|
|
37
|
+
regulations. User has the responsibility to obtain export licenses, or other
|
|
38
|
+
export authority as may be required before exporting such information to
|
|
39
|
+
foreign countries or providing access to foreign persons.
|
|
40
|
+
|
|
41
|
+
Project-URL: Homepage, https://github.com/mdovale/LoopKit
|
|
42
|
+
Project-URL: Repository, https://github.com/mdovale/LoopKit
|
|
43
|
+
Project-URL: Issues, https://github.com/mdovale/LoopKit/issues
|
|
44
|
+
Classifier: Programming Language :: Python :: 3
|
|
45
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
46
|
+
Classifier: Operating System :: OS Independent
|
|
47
|
+
Classifier: Intended Audience :: Science/Research
|
|
48
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
49
|
+
Requires-Python: >=3.9
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
License-File: AUTHORS
|
|
53
|
+
Requires-Dist: numpy
|
|
54
|
+
Requires-Dist: scipy
|
|
55
|
+
Requires-Dist: matplotlib
|
|
56
|
+
Requires-Dist: tqdm
|
|
57
|
+
Requires-Dist: control
|
|
58
|
+
Requires-Dist: sympy
|
|
59
|
+
Requires-Dist: joblib
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Requires-Dist: pytest; extra == "dev"
|
|
62
|
+
Requires-Dist: build; extra == "dev"
|
|
63
|
+
Requires-Dist: twine; extra == "dev"
|
|
64
|
+
Requires-Dist: ruff; extra == "dev"
|
|
65
|
+
Provides-Extra: diagram
|
|
66
|
+
Requires-Dist: PyMuPDF; extra == "diagram"
|
|
67
|
+
Requires-Dist: IPython; extra == "diagram"
|
|
68
|
+
Dynamic: license-file
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# loopkit
|
|
72
|
+
|
|
73
|
+
A Python library for modeling, analyzing, and simulating feedback control loops, with a focus on scientific and engineering applications such as laser locking, electronics, and precision instrumentation.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🔧 What is `loopkit`?
|
|
78
|
+
|
|
79
|
+
`loopkit` provides an object-oriented framework for building and analyzing control loops as directed graphs of signal-processing components. It supports:
|
|
80
|
+
|
|
81
|
+
- Modular design of control systems using Python classes and NumPy/Scipy tools
|
|
82
|
+
- Construction of linear time-invariant (LTI) models via symbolic transfer functions or tabulated data
|
|
83
|
+
- Frequency-domain and time-domain simulation of system behavior
|
|
84
|
+
- Visualization and analysis of loop stability, gain, noise propagation, and performance
|
|
85
|
+
- Extensibility through subclassing and dynamic graph generation
|
|
86
|
+
|
|
87
|
+
The library is designed with scientific control systems in mind—especially applications in optics, interferometry, and laser metrology.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🧩 Key Features
|
|
92
|
+
|
|
93
|
+
- **Component-based modeling**: Each system block is a `Component` with defined transfer functions, inputs, and outputs.
|
|
94
|
+
- **Flexible interconnection**: Loops are defined by connecting components into directed graphs, not just serial chains.
|
|
95
|
+
- **Simulation support**: Built-in methods to simulate step responses, noise propagation, and open/closed-loop behavior.
|
|
96
|
+
- **Plotting utilities**: Visualize Bode plots, noise budgets, signal flow graphs, and more.
|
|
97
|
+
- **Extensible**: Easily define custom blocks, auto-generate loop structures, and integrate with hardware configurations.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 📦 Installation
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install loopkit
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For block diagram generation (`loop.block_diagram()`), install the optional dependencies:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install loopkit[diagram]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Note:** `pytikz` is required for block diagrams but is not on PyPI. Install it from the allefeld fork:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install git+https://github.com/allefeld/pytikz.git
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 🚀 Quick Start
|
|
122
|
+
|
|
123
|
+
### Example
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import numpy as np
|
|
127
|
+
import matplotlib.pyplot as plt
|
|
128
|
+
from loopkit.component import Component
|
|
129
|
+
from loopkit.components import PIControllerComponent
|
|
130
|
+
from loopkit.loop import LOOP
|
|
131
|
+
import loopkit.loopmath as lm
|
|
132
|
+
|
|
133
|
+
# Define loop parameters
|
|
134
|
+
sps = 80e6 # Loop update frequency in Hz
|
|
135
|
+
frfr = np.logspace(np.log10(1e0), np.log10(40e6), int(1e5))[:-1] # Frequency array (Hz)
|
|
136
|
+
|
|
137
|
+
# Define Plant using Laplace-domain string (auto-discretized)
|
|
138
|
+
w_n = 2 * np.pi * 1e6 # 10 kHz resonance
|
|
139
|
+
zeta = 2.0 # damping ratio
|
|
140
|
+
plant = Component("Plant", sps=sps, tf=f"{w_n**2} / (s**2 + {2*zeta*w_n}*s + {w_n**2})", domain='s')
|
|
141
|
+
|
|
142
|
+
# Define Sensor using z-domain string (explicit difference equation)
|
|
143
|
+
sensor = Component("Sensor", sps=sps, tf="(0.391 + 0.391*z**-1) / (1 - 0.218*z**-1)", domain='z')
|
|
144
|
+
|
|
145
|
+
# Compute the P-servo log2 gain from a dB value
|
|
146
|
+
p_log2_gain = lm.db_to_log2_gain(15)
|
|
147
|
+
|
|
148
|
+
# Compute the integrator log2 gains for a certain cross-over frequency with the P-servo
|
|
149
|
+
i_log2_gain = lm.gain_for_crossover_frequency(p_log2_gain, sps, 1e5, kind='I')
|
|
150
|
+
|
|
151
|
+
# Define PI controller component with those gains
|
|
152
|
+
controller = PIControllerComponent("Controller", sps=sps, Kp=p_log2_gain, Ki=i_log2_gain)
|
|
153
|
+
|
|
154
|
+
# Build the loop
|
|
155
|
+
loop = LOOP(sps, [plant, sensor, controller], name="My Loop")
|
|
156
|
+
|
|
157
|
+
ugf, margin = lm.get_margin(loop.Gf(f=frfr), frfr, deg=True, unwrap_phase=True, interpolate=True) # compute UGF and phase margin
|
|
158
|
+
|
|
159
|
+
print(f"Unity gain frequency = {ugf:.4e} Hz; Phase margin = {margin:.4f} degrees")
|
|
160
|
+
|
|
161
|
+
# Visualize block diagram
|
|
162
|
+
loop.block_diagram(dpi=150)
|
|
163
|
+
|
|
164
|
+
# Bode plot of open-loop gain
|
|
165
|
+
ax = loop.bode_plot(frfr)
|
|
166
|
+
ax[0].axvline(x=ugf, ls='--', c='gray')
|
|
167
|
+
ax[1].axvline(x=ugf, ls='--', c='gray')
|
|
168
|
+
plt.show()
|
|
169
|
+
|
|
170
|
+
# Nyquist plot of open-loop gain
|
|
171
|
+
ax = loop.nyquist_plot(frfr, which='G', logy=True, logx=True, critical_point=True)
|
|
172
|
+
plt.show()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🧪 Specialized Loop Implementations
|
|
178
|
+
|
|
179
|
+
The `loopkit.loops` subpackage provides pre-built loop models:
|
|
180
|
+
|
|
181
|
+
- **`PLL`**: Digital phase-locked loop model.
|
|
182
|
+
- **`MokuLaserLock`**: Laser frequency lock model for Moku hardware (heterodyne phase-locking).
|
|
183
|
+
- **`NPROLaserLock`**: Composite model for NPRO laser frequency stabilization using PZT and temperature control loops with digital PLL (phasemeter) readout.
|
|
184
|
+
- **`LaserLockPZT`**, **`LaserLockTemp`**: Building blocks for laser lock subsystems.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from loopkit.loops import PLL, MokuLaserLock, NPROLaserLock
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 📚 Documentation
|
|
193
|
+
|
|
194
|
+
In-depth API documentation, tutorials, and example notebooks coming soon.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 💡 Design Philosophy
|
|
199
|
+
|
|
200
|
+
- Favor explicit, graph-based design over implicit signal paths
|
|
201
|
+
- Maintain clarity between loop structure and numerical simulation
|
|
202
|
+
- Support rapid prototyping of complex control systems
|
|
203
|
+
- Blend symbolic (transfer function) and numeric (data-driven) components
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 👥 Contributing
|
|
208
|
+
|
|
209
|
+
Contributions are welcome! We are especially interested in:
|
|
210
|
+
|
|
211
|
+
- New component definitions (hardware models, filters, sensors)
|
|
212
|
+
- Visualization tools for loop inspection
|
|
213
|
+
- Test coverage and validation scripts
|
|
214
|
+
- Real-world loop configurations from physics labs
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 📜 License
|
|
219
|
+
|
|
220
|
+
BSD 3-Clause License.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🛰 Authors & Acknowledgments
|
|
225
|
+
|
|
226
|
+
Developed by Miguel Dovale, based on an initial implementation by Kohei Yamamoto.
|
|
227
|
+
|
|
228
|
+
---
|
loopkit-1.0.0/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
# loopkit
|
|
3
|
+
|
|
4
|
+
A Python library for modeling, analyzing, and simulating feedback control loops, with a focus on scientific and engineering applications such as laser locking, electronics, and precision instrumentation.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🔧 What is `loopkit`?
|
|
9
|
+
|
|
10
|
+
`loopkit` provides an object-oriented framework for building and analyzing control loops as directed graphs of signal-processing components. It supports:
|
|
11
|
+
|
|
12
|
+
- Modular design of control systems using Python classes and NumPy/Scipy tools
|
|
13
|
+
- Construction of linear time-invariant (LTI) models via symbolic transfer functions or tabulated data
|
|
14
|
+
- Frequency-domain and time-domain simulation of system behavior
|
|
15
|
+
- Visualization and analysis of loop stability, gain, noise propagation, and performance
|
|
16
|
+
- Extensibility through subclassing and dynamic graph generation
|
|
17
|
+
|
|
18
|
+
The library is designed with scientific control systems in mind—especially applications in optics, interferometry, and laser metrology.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🧩 Key Features
|
|
23
|
+
|
|
24
|
+
- **Component-based modeling**: Each system block is a `Component` with defined transfer functions, inputs, and outputs.
|
|
25
|
+
- **Flexible interconnection**: Loops are defined by connecting components into directed graphs, not just serial chains.
|
|
26
|
+
- **Simulation support**: Built-in methods to simulate step responses, noise propagation, and open/closed-loop behavior.
|
|
27
|
+
- **Plotting utilities**: Visualize Bode plots, noise budgets, signal flow graphs, and more.
|
|
28
|
+
- **Extensible**: Easily define custom blocks, auto-generate loop structures, and integrate with hardware configurations.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install loopkit
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For block diagram generation (`loop.block_diagram()`), install the optional dependencies:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install loopkit[diagram]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Note:** `pytikz` is required for block diagrams but is not on PyPI. Install it from the allefeld fork:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install git+https://github.com/allefeld/pytikz.git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🚀 Quick Start
|
|
53
|
+
|
|
54
|
+
### Example
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import numpy as np
|
|
58
|
+
import matplotlib.pyplot as plt
|
|
59
|
+
from loopkit.component import Component
|
|
60
|
+
from loopkit.components import PIControllerComponent
|
|
61
|
+
from loopkit.loop import LOOP
|
|
62
|
+
import loopkit.loopmath as lm
|
|
63
|
+
|
|
64
|
+
# Define loop parameters
|
|
65
|
+
sps = 80e6 # Loop update frequency in Hz
|
|
66
|
+
frfr = np.logspace(np.log10(1e0), np.log10(40e6), int(1e5))[:-1] # Frequency array (Hz)
|
|
67
|
+
|
|
68
|
+
# Define Plant using Laplace-domain string (auto-discretized)
|
|
69
|
+
w_n = 2 * np.pi * 1e6 # 10 kHz resonance
|
|
70
|
+
zeta = 2.0 # damping ratio
|
|
71
|
+
plant = Component("Plant", sps=sps, tf=f"{w_n**2} / (s**2 + {2*zeta*w_n}*s + {w_n**2})", domain='s')
|
|
72
|
+
|
|
73
|
+
# Define Sensor using z-domain string (explicit difference equation)
|
|
74
|
+
sensor = Component("Sensor", sps=sps, tf="(0.391 + 0.391*z**-1) / (1 - 0.218*z**-1)", domain='z')
|
|
75
|
+
|
|
76
|
+
# Compute the P-servo log2 gain from a dB value
|
|
77
|
+
p_log2_gain = lm.db_to_log2_gain(15)
|
|
78
|
+
|
|
79
|
+
# Compute the integrator log2 gains for a certain cross-over frequency with the P-servo
|
|
80
|
+
i_log2_gain = lm.gain_for_crossover_frequency(p_log2_gain, sps, 1e5, kind='I')
|
|
81
|
+
|
|
82
|
+
# Define PI controller component with those gains
|
|
83
|
+
controller = PIControllerComponent("Controller", sps=sps, Kp=p_log2_gain, Ki=i_log2_gain)
|
|
84
|
+
|
|
85
|
+
# Build the loop
|
|
86
|
+
loop = LOOP(sps, [plant, sensor, controller], name="My Loop")
|
|
87
|
+
|
|
88
|
+
ugf, margin = lm.get_margin(loop.Gf(f=frfr), frfr, deg=True, unwrap_phase=True, interpolate=True) # compute UGF and phase margin
|
|
89
|
+
|
|
90
|
+
print(f"Unity gain frequency = {ugf:.4e} Hz; Phase margin = {margin:.4f} degrees")
|
|
91
|
+
|
|
92
|
+
# Visualize block diagram
|
|
93
|
+
loop.block_diagram(dpi=150)
|
|
94
|
+
|
|
95
|
+
# Bode plot of open-loop gain
|
|
96
|
+
ax = loop.bode_plot(frfr)
|
|
97
|
+
ax[0].axvline(x=ugf, ls='--', c='gray')
|
|
98
|
+
ax[1].axvline(x=ugf, ls='--', c='gray')
|
|
99
|
+
plt.show()
|
|
100
|
+
|
|
101
|
+
# Nyquist plot of open-loop gain
|
|
102
|
+
ax = loop.nyquist_plot(frfr, which='G', logy=True, logx=True, critical_point=True)
|
|
103
|
+
plt.show()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 🧪 Specialized Loop Implementations
|
|
109
|
+
|
|
110
|
+
The `loopkit.loops` subpackage provides pre-built loop models:
|
|
111
|
+
|
|
112
|
+
- **`PLL`**: Digital phase-locked loop model.
|
|
113
|
+
- **`MokuLaserLock`**: Laser frequency lock model for Moku hardware (heterodyne phase-locking).
|
|
114
|
+
- **`NPROLaserLock`**: Composite model for NPRO laser frequency stabilization using PZT and temperature control loops with digital PLL (phasemeter) readout.
|
|
115
|
+
- **`LaserLockPZT`**, **`LaserLockTemp`**: Building blocks for laser lock subsystems.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from loopkit.loops import PLL, MokuLaserLock, NPROLaserLock
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 📚 Documentation
|
|
124
|
+
|
|
125
|
+
In-depth API documentation, tutorials, and example notebooks coming soon.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 💡 Design Philosophy
|
|
130
|
+
|
|
131
|
+
- Favor explicit, graph-based design over implicit signal paths
|
|
132
|
+
- Maintain clarity between loop structure and numerical simulation
|
|
133
|
+
- Support rapid prototyping of complex control systems
|
|
134
|
+
- Blend symbolic (transfer function) and numeric (data-driven) components
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 👥 Contributing
|
|
139
|
+
|
|
140
|
+
Contributions are welcome! We are especially interested in:
|
|
141
|
+
|
|
142
|
+
- New component definitions (hardware models, filters, sensors)
|
|
143
|
+
- Visualization tools for loop inspection
|
|
144
|
+
- Test coverage and validation scripts
|
|
145
|
+
- Real-world loop configurations from physics labs
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 📜 License
|
|
150
|
+
|
|
151
|
+
BSD 3-Clause License.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🛰 Authors & Acknowledgments
|
|
156
|
+
|
|
157
|
+
Developed by Miguel Dovale, based on an initial implementation by Kohei Yamamoto.
|
|
158
|
+
|
|
159
|
+
---
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""loopkit: control loop modeling and analysis."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Backward compatibility: expose loops submodules at old paths
|
|
6
|
+
import loopkit.loops.pll
|
|
7
|
+
import loopkit.loops.mokulaserlock
|
|
8
|
+
import loopkit.loops.nprolaserlock
|
|
9
|
+
|
|
10
|
+
sys.modules["loopkit.pll"] = loopkit.loops.pll
|
|
11
|
+
sys.modules["loopkit.mokulaserlock"] = loopkit.loops.mokulaserlock
|
|
12
|
+
sys.modules["loopkit.nprolaserlock"] = loopkit.loops.nprolaserlock
|