pathsim 0.1.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.
- pathsim-0.1.0/LICENSE.txt +21 -0
- pathsim-0.1.0/PKG-INFO +141 -0
- pathsim-0.1.0/README.md +124 -0
- pathsim-0.1.0/pathsim/__init__.py +3 -0
- pathsim-0.1.0/pathsim/blocks/__init__.py +14 -0
- pathsim-0.1.0/pathsim/blocks/_block.py +217 -0
- pathsim-0.1.0/pathsim/blocks/adder.py +33 -0
- pathsim-0.1.0/pathsim/blocks/amplifier.py +34 -0
- pathsim-0.1.0/pathsim/blocks/delay.py +70 -0
- pathsim-0.1.0/pathsim/blocks/differentiator.py +65 -0
- pathsim-0.1.0/pathsim/blocks/function.py +83 -0
- pathsim-0.1.0/pathsim/blocks/integrator.py +65 -0
- pathsim-0.1.0/pathsim/blocks/lti.py +149 -0
- pathsim-0.1.0/pathsim/blocks/multiplier.py +34 -0
- pathsim-0.1.0/pathsim/blocks/ode.py +83 -0
- pathsim-0.1.0/pathsim/blocks/rf/__init__.py +4 -0
- pathsim-0.1.0/pathsim/blocks/rf/filters.py +169 -0
- pathsim-0.1.0/pathsim/blocks/rf/noise.py +192 -0
- pathsim-0.1.0/pathsim/blocks/rf/sources.py +130 -0
- pathsim-0.1.0/pathsim/blocks/rf/wienerhammerstein.py +317 -0
- pathsim-0.1.0/pathsim/blocks/rng.py +57 -0
- pathsim-0.1.0/pathsim/blocks/scope.py +224 -0
- pathsim-0.1.0/pathsim/blocks/sources.py +72 -0
- pathsim-0.1.0/pathsim/blocks/spectrum.py +305 -0
- pathsim-0.1.0/pathsim/connection.py +109 -0
- pathsim-0.1.0/pathsim/simulation.py +617 -0
- pathsim-0.1.0/pathsim/solvers/__init__.py +25 -0
- pathsim-0.1.0/pathsim/solvers/_solver.py +387 -0
- pathsim-0.1.0/pathsim/solvers/bdf.py +240 -0
- pathsim-0.1.0/pathsim/solvers/dirk2.py +102 -0
- pathsim-0.1.0/pathsim/solvers/dirk3.py +86 -0
- pathsim-0.1.0/pathsim/solvers/esdirk32.py +131 -0
- pathsim-0.1.0/pathsim/solvers/esdirk4.py +99 -0
- pathsim-0.1.0/pathsim/solvers/esdirk43.py +139 -0
- pathsim-0.1.0/pathsim/solvers/esdirk54.py +141 -0
- pathsim-0.1.0/pathsim/solvers/esdirk85.py +200 -0
- pathsim-0.1.0/pathsim/solvers/euler.py +81 -0
- pathsim-0.1.0/pathsim/solvers/rk4.py +61 -0
- pathsim-0.1.0/pathsim/solvers/rkbs32.py +101 -0
- pathsim-0.1.0/pathsim/solvers/rkck54.py +108 -0
- pathsim-0.1.0/pathsim/solvers/rkdp54.py +111 -0
- pathsim-0.1.0/pathsim/solvers/rkdp87.py +116 -0
- pathsim-0.1.0/pathsim/solvers/rkf45.py +102 -0
- pathsim-0.1.0/pathsim/solvers/rkf78.py +111 -0
- pathsim-0.1.0/pathsim/solvers/rkv65.py +103 -0
- pathsim-0.1.0/pathsim/solvers/ssprk22.py +62 -0
- pathsim-0.1.0/pathsim/solvers/ssprk33.py +65 -0
- pathsim-0.1.0/pathsim/solvers/ssprk34.py +74 -0
- pathsim-0.1.0/pathsim/subsystem.py +262 -0
- pathsim-0.1.0/pathsim/utils/__init__.py +0 -0
- pathsim-0.1.0/pathsim/utils/adaptivebuffer.py +83 -0
- pathsim-0.1.0/pathsim/utils/anderson.py +173 -0
- pathsim-0.1.0/pathsim/utils/funcs.py +173 -0
- pathsim-0.1.0/pathsim/utils/progresstracker.py +87 -0
- pathsim-0.1.0/pathsim/utils/realtimeplotter.py +219 -0
- pathsim-0.1.0/pathsim/utils/statespacerealizations.py +142 -0
- pathsim-0.1.0/pathsim/utils/waveforms.py +36 -0
- pathsim-0.1.0/pathsim.egg-info/PKG-INFO +141 -0
- pathsim-0.1.0/pathsim.egg-info/SOURCES.txt +62 -0
- pathsim-0.1.0/pathsim.egg-info/dependency_links.txt +1 -0
- pathsim-0.1.0/pathsim.egg-info/requires.txt +3 -0
- pathsim-0.1.0/pathsim.egg-info/top_level.txt +1 -0
- pathsim-0.1.0/setup.cfg +4 -0
- pathsim-0.1.0/setup.py +27 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Milan Rother
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pathsim-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pathsim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A block based time domain system simulation framework.
|
|
5
|
+
Home-page: https://github.com/milanofthe/PathSim
|
|
6
|
+
Author: Milan Rother
|
|
7
|
+
Author-email: milan.rother@gmx.de
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.txt
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: matplotlib
|
|
16
|
+
Requires-Dist: scipy
|
|
17
|
+
|
|
18
|
+
# PathSim: A Time-Domain System Simulation Framework
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
PathSim is a minimalistic and flexible block-based time-domain system simulation framework in Python. It provides a modular and intuitive approach to modeling and simulating complex dynamical systems using a directed computational graph. It is similar to Matlab Simulink in spirit but works very differently under the hood.
|
|
24
|
+
|
|
25
|
+
Key features of PathSim include:
|
|
26
|
+
|
|
27
|
+
- Decentralized architecture where each dynamical block has their own numerical integration engine.
|
|
28
|
+
- The system is solved directly on the computational graph instead of compiling a unified differetial algebraic system.
|
|
29
|
+
- This has some advantages such as hot-swappable blocks during simulation and reading simulation results directly from the scopes.
|
|
30
|
+
- The block execution is decoupled from the data transfer, which enables parallelization (future) and linear computational complexity scaling for sparsely connected systems.
|
|
31
|
+
- Support for MIMO (Multiple Input, Multiple Output) blocks, enabling the creation of complex interconnected system topologies.
|
|
32
|
+
- Fixed-point iteration approach with path length estimation to efficiently resolve algebraic loops.
|
|
33
|
+
- Wide range of numerical solvers, including implicit and explicit multi-stage, and adaptive Runge-Kutta methods such as `RKDP54` or `ESDIRK54`.
|
|
34
|
+
- Modular and hierarchical modeling with (nested) subsystems.
|
|
35
|
+
- Library of pre-defined blocks, including mathematical operations, integrators, delays, transfer functions, and more.
|
|
36
|
+
- Easy extensibility, allowing users to define custom blocks by subclassing the base `Block` class and implementing just a handfull of methods.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
There are many example simulations in the `examples` directory that cover most of the functionalities of the pathsim package.
|
|
40
|
+
|
|
41
|
+
## Getting Started
|
|
42
|
+
|
|
43
|
+
To get started with PathSim, you need to import the necessary modules and classes. The main components of the package are:
|
|
44
|
+
|
|
45
|
+
- `Simulation`: The main class that handles the blocks, connections, and the simulation loop.
|
|
46
|
+
- `Connection`: The class that defines the connections between blocks.
|
|
47
|
+
- Various block classes from the `blocks` module, such as `Integrator`, `Amplifier`, `Adder`, `Scope`, etc.
|
|
48
|
+
|
|
49
|
+
Here's an example that demonstrates how to create a basic simulation.
|
|
50
|
+
In this example, we create a simulation of the harmonic oscillator (a spring mass damper 2nd order system) initial value problem. The ODE that defines it is give by
|
|
51
|
+
|
|
52
|
+
$$
|
|
53
|
+
\ddot{x} + \frac{c}{m} \dot{x} + \frac{k}{m} x = 0
|
|
54
|
+
$$
|
|
55
|
+
|
|
56
|
+
where $c$ is the damping, $k$ the spring constant and $m$ the mass.
|
|
57
|
+
|
|
58
|
+
It can be translated to a block diagram using integrators, amplifiers and adders in the following way:
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
The topology of the block diagram above can be directly defined as blocks and connections in the pathsim framework. First we initialize the blocks needed to represent the dynamical systems with their respective arguments such as initial conditions and gain values, then the blocks are connected using `Connection` objects, forming two feedback loops. The `Simulation` instance manages the blocks and connections and advances the system in time with the timestep (`dt`). The `log` flag for logging the simulation progress is also set. Finally, we run the simulation for some number of seconds and plot the results using the `plot()` method of the scope block.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from pathsim import Simulation
|
|
68
|
+
from pathsim import Connection
|
|
69
|
+
from pathsim.blocks import Integrator, Amplifier, Adder, Scope
|
|
70
|
+
from pathsim.solvers import SSPRK22 # 2nd order fixed timestep, this is also the default
|
|
71
|
+
|
|
72
|
+
#initial position and velocity
|
|
73
|
+
x0, v0 = 2, 5
|
|
74
|
+
|
|
75
|
+
#parameters (mass, damping, spring constant)
|
|
76
|
+
m, c, k = 0.8, 0.2, 1.5
|
|
77
|
+
|
|
78
|
+
# Create blocks
|
|
79
|
+
I1 = Integrator(v0) # integrator for velocity
|
|
80
|
+
I2 = Integrator(x0) # integrator for position
|
|
81
|
+
A1 = Amplifier(c)
|
|
82
|
+
A2 = Amplifier(k)
|
|
83
|
+
A3 = Amplifier(-1/m)
|
|
84
|
+
P1 = Adder()
|
|
85
|
+
Sc = Scope(labels=["velocity", "position"])
|
|
86
|
+
|
|
87
|
+
blocks = [I1, I2, A1, A2, A3, P1, Sc]
|
|
88
|
+
|
|
89
|
+
# Create connections
|
|
90
|
+
connections = [
|
|
91
|
+
Connection(I1, I2, A1, Sc), # one to many connection
|
|
92
|
+
Connection(I2, A2, Sc[1]),
|
|
93
|
+
Connection(A1, P1), # default connection to port 0
|
|
94
|
+
Connection(A2, P1[1]), # specific connection to port 1
|
|
95
|
+
Connection(P1, A3),
|
|
96
|
+
Connection(A3, I1)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
# Create a simulation instance from the blocks and connections
|
|
100
|
+
Sim = Simulation(blocks, connections, dt=0.05, log=True, Solver=SSPRK22)
|
|
101
|
+
|
|
102
|
+
# Run the simulation for 50 seconds
|
|
103
|
+
Sim.run(duration=50.0)
|
|
104
|
+
|
|
105
|
+
# Plot the results directly from the scope
|
|
106
|
+
Sc.plot()
|
|
107
|
+
|
|
108
|
+
# Read the results from the scope for further processing
|
|
109
|
+
time, data = Sc.read()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
2024-08-11 21:08:32,152 - INFO - LOGGING enabled
|
|
113
|
+
2024-08-11 21:08:32,152 - INFO - SOLVER SSPRK22 adaptive=False implicit=False
|
|
114
|
+
2024-08-11 21:08:32,153 - INFO - PATH LENGTH ESTIMATE 3, 'iterations_min' set to 3
|
|
115
|
+
2024-08-11 21:08:32,154 - INFO - RESET
|
|
116
|
+
2024-08-11 21:08:32,154 - INFO - RUN duration=50.0
|
|
117
|
+
2024-08-11 21:08:32,155 - INFO - STARTING progress tracker
|
|
118
|
+
2024-08-11 21:08:32,155 - INFO - progress=0%
|
|
119
|
+
2024-08-11 21:08:32,179 - INFO - progress=10%
|
|
120
|
+
2024-08-11 21:08:32,202 - INFO - progress=20%
|
|
121
|
+
2024-08-11 21:08:32,226 - INFO - progress=30%
|
|
122
|
+
2024-08-11 21:08:32,251 - INFO - progress=40%
|
|
123
|
+
2024-08-11 21:08:32,277 - INFO - progress=50%
|
|
124
|
+
2024-08-11 21:08:32,302 - INFO - progress=60%
|
|
125
|
+
2024-08-11 21:08:32,325 - INFO - progress=70%
|
|
126
|
+
2024-08-11 21:08:32,347 - INFO - progress=80%
|
|
127
|
+
2024-08-11 21:08:32,371 - INFO - progress=90%
|
|
128
|
+
2024-08-11 21:08:32,393 - INFO - progress=100%
|
|
129
|
+
2024-08-11 21:08:32,393 - INFO - FINISHED steps(total)=1001(1001) runtime=238.25ms
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+

|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## Examples
|
|
139
|
+
There are many examples of dynamical system simulations in the `examples` directory. They cover almost all the blocks currently available in PathSim as well as different solvers.
|
|
140
|
+
|
|
141
|
+
|
pathsim-0.1.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# PathSim: A Time-Domain System Simulation Framework
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Overview
|
|
5
|
+
|
|
6
|
+
PathSim is a minimalistic and flexible block-based time-domain system simulation framework in Python. It provides a modular and intuitive approach to modeling and simulating complex dynamical systems using a directed computational graph. It is similar to Matlab Simulink in spirit but works very differently under the hood.
|
|
7
|
+
|
|
8
|
+
Key features of PathSim include:
|
|
9
|
+
|
|
10
|
+
- Decentralized architecture where each dynamical block has their own numerical integration engine.
|
|
11
|
+
- The system is solved directly on the computational graph instead of compiling a unified differetial algebraic system.
|
|
12
|
+
- This has some advantages such as hot-swappable blocks during simulation and reading simulation results directly from the scopes.
|
|
13
|
+
- The block execution is decoupled from the data transfer, which enables parallelization (future) and linear computational complexity scaling for sparsely connected systems.
|
|
14
|
+
- Support for MIMO (Multiple Input, Multiple Output) blocks, enabling the creation of complex interconnected system topologies.
|
|
15
|
+
- Fixed-point iteration approach with path length estimation to efficiently resolve algebraic loops.
|
|
16
|
+
- Wide range of numerical solvers, including implicit and explicit multi-stage, and adaptive Runge-Kutta methods such as `RKDP54` or `ESDIRK54`.
|
|
17
|
+
- Modular and hierarchical modeling with (nested) subsystems.
|
|
18
|
+
- Library of pre-defined blocks, including mathematical operations, integrators, delays, transfer functions, and more.
|
|
19
|
+
- Easy extensibility, allowing users to define custom blocks by subclassing the base `Block` class and implementing just a handfull of methods.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
There are many example simulations in the `examples` directory that cover most of the functionalities of the pathsim package.
|
|
23
|
+
|
|
24
|
+
## Getting Started
|
|
25
|
+
|
|
26
|
+
To get started with PathSim, you need to import the necessary modules and classes. The main components of the package are:
|
|
27
|
+
|
|
28
|
+
- `Simulation`: The main class that handles the blocks, connections, and the simulation loop.
|
|
29
|
+
- `Connection`: The class that defines the connections between blocks.
|
|
30
|
+
- Various block classes from the `blocks` module, such as `Integrator`, `Amplifier`, `Adder`, `Scope`, etc.
|
|
31
|
+
|
|
32
|
+
Here's an example that demonstrates how to create a basic simulation.
|
|
33
|
+
In this example, we create a simulation of the harmonic oscillator (a spring mass damper 2nd order system) initial value problem. The ODE that defines it is give by
|
|
34
|
+
|
|
35
|
+
$$
|
|
36
|
+
\ddot{x} + \frac{c}{m} \dot{x} + \frac{k}{m} x = 0
|
|
37
|
+
$$
|
|
38
|
+
|
|
39
|
+
where $c$ is the damping, $k$ the spring constant and $m$ the mass.
|
|
40
|
+
|
|
41
|
+
It can be translated to a block diagram using integrators, amplifiers and adders in the following way:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
The topology of the block diagram above can be directly defined as blocks and connections in the pathsim framework. First we initialize the blocks needed to represent the dynamical systems with their respective arguments such as initial conditions and gain values, then the blocks are connected using `Connection` objects, forming two feedback loops. The `Simulation` instance manages the blocks and connections and advances the system in time with the timestep (`dt`). The `log` flag for logging the simulation progress is also set. Finally, we run the simulation for some number of seconds and plot the results using the `plot()` method of the scope block.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from pathsim import Simulation
|
|
51
|
+
from pathsim import Connection
|
|
52
|
+
from pathsim.blocks import Integrator, Amplifier, Adder, Scope
|
|
53
|
+
from pathsim.solvers import SSPRK22 # 2nd order fixed timestep, this is also the default
|
|
54
|
+
|
|
55
|
+
#initial position and velocity
|
|
56
|
+
x0, v0 = 2, 5
|
|
57
|
+
|
|
58
|
+
#parameters (mass, damping, spring constant)
|
|
59
|
+
m, c, k = 0.8, 0.2, 1.5
|
|
60
|
+
|
|
61
|
+
# Create blocks
|
|
62
|
+
I1 = Integrator(v0) # integrator for velocity
|
|
63
|
+
I2 = Integrator(x0) # integrator for position
|
|
64
|
+
A1 = Amplifier(c)
|
|
65
|
+
A2 = Amplifier(k)
|
|
66
|
+
A3 = Amplifier(-1/m)
|
|
67
|
+
P1 = Adder()
|
|
68
|
+
Sc = Scope(labels=["velocity", "position"])
|
|
69
|
+
|
|
70
|
+
blocks = [I1, I2, A1, A2, A3, P1, Sc]
|
|
71
|
+
|
|
72
|
+
# Create connections
|
|
73
|
+
connections = [
|
|
74
|
+
Connection(I1, I2, A1, Sc), # one to many connection
|
|
75
|
+
Connection(I2, A2, Sc[1]),
|
|
76
|
+
Connection(A1, P1), # default connection to port 0
|
|
77
|
+
Connection(A2, P1[1]), # specific connection to port 1
|
|
78
|
+
Connection(P1, A3),
|
|
79
|
+
Connection(A3, I1)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# Create a simulation instance from the blocks and connections
|
|
83
|
+
Sim = Simulation(blocks, connections, dt=0.05, log=True, Solver=SSPRK22)
|
|
84
|
+
|
|
85
|
+
# Run the simulation for 50 seconds
|
|
86
|
+
Sim.run(duration=50.0)
|
|
87
|
+
|
|
88
|
+
# Plot the results directly from the scope
|
|
89
|
+
Sc.plot()
|
|
90
|
+
|
|
91
|
+
# Read the results from the scope for further processing
|
|
92
|
+
time, data = Sc.read()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
2024-08-11 21:08:32,152 - INFO - LOGGING enabled
|
|
96
|
+
2024-08-11 21:08:32,152 - INFO - SOLVER SSPRK22 adaptive=False implicit=False
|
|
97
|
+
2024-08-11 21:08:32,153 - INFO - PATH LENGTH ESTIMATE 3, 'iterations_min' set to 3
|
|
98
|
+
2024-08-11 21:08:32,154 - INFO - RESET
|
|
99
|
+
2024-08-11 21:08:32,154 - INFO - RUN duration=50.0
|
|
100
|
+
2024-08-11 21:08:32,155 - INFO - STARTING progress tracker
|
|
101
|
+
2024-08-11 21:08:32,155 - INFO - progress=0%
|
|
102
|
+
2024-08-11 21:08:32,179 - INFO - progress=10%
|
|
103
|
+
2024-08-11 21:08:32,202 - INFO - progress=20%
|
|
104
|
+
2024-08-11 21:08:32,226 - INFO - progress=30%
|
|
105
|
+
2024-08-11 21:08:32,251 - INFO - progress=40%
|
|
106
|
+
2024-08-11 21:08:32,277 - INFO - progress=50%
|
|
107
|
+
2024-08-11 21:08:32,302 - INFO - progress=60%
|
|
108
|
+
2024-08-11 21:08:32,325 - INFO - progress=70%
|
|
109
|
+
2024-08-11 21:08:32,347 - INFO - progress=80%
|
|
110
|
+
2024-08-11 21:08:32,371 - INFO - progress=90%
|
|
111
|
+
2024-08-11 21:08:32,393 - INFO - progress=100%
|
|
112
|
+
2024-08-11 21:08:32,393 - INFO - FINISHED steps(total)=1001(1001) runtime=238.25ms
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Examples
|
|
122
|
+
There are many examples of dynamical system simulations in the `examples` directory. They cover almost all the blocks currently available in PathSim as well as different solvers.
|
|
123
|
+
|
|
124
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .differentiator import *
|
|
2
|
+
from .integrator import *
|
|
3
|
+
from .amplifier import *
|
|
4
|
+
from .function import *
|
|
5
|
+
from .spectrum import *
|
|
6
|
+
from .sources import *
|
|
7
|
+
from .multiplier import *
|
|
8
|
+
from .adder import*
|
|
9
|
+
from .scope import *
|
|
10
|
+
from .delay import *
|
|
11
|
+
from .lti import *
|
|
12
|
+
from .ode import *
|
|
13
|
+
from .rng import *
|
|
14
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## BASE BLOCK
|
|
4
|
+
## (blocks/_block.py)
|
|
5
|
+
##
|
|
6
|
+
## This module defines the base 'Block' class that is the parent
|
|
7
|
+
## to all other blocks and can serve as a base for new or custom blocks
|
|
8
|
+
##
|
|
9
|
+
##
|
|
10
|
+
## Milan Rother 2024
|
|
11
|
+
##
|
|
12
|
+
#########################################################################################
|
|
13
|
+
|
|
14
|
+
# IMPORTS ===============================================================================
|
|
15
|
+
|
|
16
|
+
#for unique identifiers of blocks
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# BASE BLOCK CLASS ======================================================================
|
|
21
|
+
|
|
22
|
+
class Block:
|
|
23
|
+
"""
|
|
24
|
+
Base 'Block' object that defines the inputs, outputs and the connect method.
|
|
25
|
+
|
|
26
|
+
Block interconnections are handeled via the io interface of the blocks.
|
|
27
|
+
It is realized by dicts for the 'inputs' and for the 'outputs', where
|
|
28
|
+
the key of the dict is the input/output channel and the corresponding
|
|
29
|
+
value is the input/output value.
|
|
30
|
+
|
|
31
|
+
NOTE :
|
|
32
|
+
This block is not intended to be used directly and serves as a base
|
|
33
|
+
class definition for other blocks to be inherited.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
|
|
38
|
+
#dicts to hold input and output values
|
|
39
|
+
self.inputs = {0:0.0}
|
|
40
|
+
self.outputs = {0:0.0}
|
|
41
|
+
|
|
42
|
+
#initialiye integration engine as 'None' by default
|
|
43
|
+
self.engine = None
|
|
44
|
+
|
|
45
|
+
#unique block identifier
|
|
46
|
+
self.id = uuid.uuid4()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __str__(self):
|
|
50
|
+
return self.__class__.__name__
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __len__(self):
|
|
54
|
+
"""
|
|
55
|
+
The '__len__' method of the block is used to compute the length of the
|
|
56
|
+
pass-throuh path of the block. For instant time blocks or blocks with
|
|
57
|
+
pass-through components (adders, amplifiers, etc.) it returns 1,
|
|
58
|
+
otherwise (integrator, buffer, etc.) it returns 0.
|
|
59
|
+
"""
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __getitem__(self, key):
|
|
64
|
+
"""
|
|
65
|
+
This method is intended to make connection creation more convenient
|
|
66
|
+
and therefore just returns the block itself and the key directly after
|
|
67
|
+
doing some basic checks.
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(key, int):
|
|
70
|
+
raise ValueError(f"Port has to be of type 'int' but is '{type(key)}'!")
|
|
71
|
+
return (self, key)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def reset(self):
|
|
75
|
+
"""
|
|
76
|
+
Reset the blocks inputs and outputs and also its internal solver, if the
|
|
77
|
+
block has a solver instance.
|
|
78
|
+
"""
|
|
79
|
+
#reset inputs and outputs while maintaining ports
|
|
80
|
+
self.inputs = {k:0.0 for k in sorted(self.inputs.keys())}
|
|
81
|
+
self.outputs = {k:0.0 for k in sorted(self.outputs.keys())}
|
|
82
|
+
|
|
83
|
+
#reset engine if block has solver
|
|
84
|
+
if self.engine is not None:
|
|
85
|
+
self.engine.reset()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# methods for blocks with integration engines ---------------------------------------
|
|
89
|
+
|
|
90
|
+
def initialize_solver(self, Solver, tolerance_lte=1e-6):
|
|
91
|
+
"""
|
|
92
|
+
Initialize the numerical integration engine with local truncation error
|
|
93
|
+
tolerance if required.
|
|
94
|
+
If the block doesnt require an integration engine, this method just passes.
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def change_solver(self, Solver):
|
|
100
|
+
"""
|
|
101
|
+
Changes the solver type of the integration engine of the block, if the
|
|
102
|
+
block has a solver instance.
|
|
103
|
+
"""
|
|
104
|
+
if self.engine is not None:
|
|
105
|
+
self.engine = self.engine.change(Solver)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def revert(self):
|
|
109
|
+
"""
|
|
110
|
+
Revert the block to the state of the previous timestep, if the
|
|
111
|
+
block has a solver instance indicated by the 'has_engine' flag.
|
|
112
|
+
This is required for adaptive solvers to revert the state to the
|
|
113
|
+
previous timestep.
|
|
114
|
+
"""
|
|
115
|
+
if self.engine is not None:
|
|
116
|
+
self.engine.revert()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def buffer(self):
|
|
120
|
+
"""
|
|
121
|
+
Buffer current internal state of the block, if the block has
|
|
122
|
+
a solver instance (is stateful).
|
|
123
|
+
This is required for multistage and implicit solvers.
|
|
124
|
+
"""
|
|
125
|
+
if self.engine is not None:
|
|
126
|
+
self.engine.buffer()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# methods for sampling data ---------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def sample(self, t):
|
|
132
|
+
"""
|
|
133
|
+
Samples the data of the blocks inputs or internal state when called.
|
|
134
|
+
This can record block parameters after a succesful timestep such as
|
|
135
|
+
for the 'Scope' and 'Delay' blocks but also for sampling from a random
|
|
136
|
+
distribution in the 'RNG' and the noise blocks.
|
|
137
|
+
"""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# methods for inter-block data transfer ---------------------------------------------
|
|
142
|
+
|
|
143
|
+
def set(self, port, value):
|
|
144
|
+
"""
|
|
145
|
+
Set the value of an input port of the block.
|
|
146
|
+
"""
|
|
147
|
+
self.inputs[port] = value
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get(self, port):
|
|
151
|
+
"""
|
|
152
|
+
Get the value of an output port of the block.
|
|
153
|
+
Uses the 'get' method of 'outputs' dict with default value '0.0'.
|
|
154
|
+
"""
|
|
155
|
+
return self.outputs.get(port, 0.0)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# methods for block output and state updates ----------------------------------------
|
|
159
|
+
|
|
160
|
+
def update(self, t):
|
|
161
|
+
"""
|
|
162
|
+
The 'update' method is called iteratively for all blocks BEFORE the timestep
|
|
163
|
+
to resolve algebraic loops (fixed-point iteraion).
|
|
164
|
+
|
|
165
|
+
It is meant for instant time blocks (blocks that dont have a delay due to the
|
|
166
|
+
timestep, such as Amplifier, etc.) and updates the 'outputs' of the block
|
|
167
|
+
directly based on the 'inputs' and possibly internal states.
|
|
168
|
+
|
|
169
|
+
It computes and returns the relative difference between the new output and
|
|
170
|
+
the previous output (before the step) to track convergence of the fixed-point
|
|
171
|
+
iteration.
|
|
172
|
+
|
|
173
|
+
RETURNS :
|
|
174
|
+
error : (float) relative error to previous iteration for convergence control
|
|
175
|
+
"""
|
|
176
|
+
return 0.0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def solve(self, t, dt):
|
|
180
|
+
"""
|
|
181
|
+
The 'solve' method performes one iterative solution step that is required
|
|
182
|
+
to solve the implicit update equation of the solver if an implicit solver
|
|
183
|
+
(numerical integrator) is used.
|
|
184
|
+
|
|
185
|
+
It returns the relative difference between the new updated solution
|
|
186
|
+
and the previous iteration of the solution to track convergence within
|
|
187
|
+
an outer loop.
|
|
188
|
+
|
|
189
|
+
This only has to be implemented by blocks that have an internal
|
|
190
|
+
integration engine with an implicit solver.
|
|
191
|
+
|
|
192
|
+
RETURNS :
|
|
193
|
+
error : (float) solver residual norm
|
|
194
|
+
"""
|
|
195
|
+
return 0.0
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def step(self, t, dt):
|
|
199
|
+
"""
|
|
200
|
+
The 'step' method is used in transient simulations and performs an action
|
|
201
|
+
(numeric integration timestep, recording data, etc.) based on the current
|
|
202
|
+
inputs and the current internal state.
|
|
203
|
+
|
|
204
|
+
It performes one timestep for the internal states. For instant time blocks,
|
|
205
|
+
the 'step' method does not has to be implemented specifically.
|
|
206
|
+
|
|
207
|
+
The method handles timestepping for dynamic blocks with internal states
|
|
208
|
+
such as 'Integrator', 'StateSpace', etc.
|
|
209
|
+
|
|
210
|
+
RETURNS :
|
|
211
|
+
success : (bool) step was successful
|
|
212
|
+
error : (float) local truncation error from adaptive integrators
|
|
213
|
+
scale : (float) timestep rescale from adaptive integrators
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
#by default no error estimate
|
|
217
|
+
return True, 0.0, 1.0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## REDUCTION BLOCKS (blocks/adder.py)
|
|
4
|
+
##
|
|
5
|
+
## This module defines static 'Adder' block
|
|
6
|
+
##
|
|
7
|
+
## Milan Rother 2024
|
|
8
|
+
##
|
|
9
|
+
#########################################################################################
|
|
10
|
+
|
|
11
|
+
# IMPORTS ===============================================================================
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from ._block import Block
|
|
16
|
+
|
|
17
|
+
from ..utils.funcs import (
|
|
18
|
+
rel_error,
|
|
19
|
+
dict_to_array
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# MISO BLOCKS ===========================================================================
|
|
24
|
+
|
|
25
|
+
class Adder(Block):
|
|
26
|
+
"""
|
|
27
|
+
summs / adds all input signals (MISO)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def update(self, t):
|
|
31
|
+
prev_output = self.outputs[0]
|
|
32
|
+
self.outputs[0] = np.sum(dict_to_array(self.inputs), axis=0)
|
|
33
|
+
return rel_error(prev_output, self.outputs[0])
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## IDEAL AMPLIFIER BLOCK
|
|
4
|
+
## (blocks/amplifier.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
#########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ===============================================================================
|
|
11
|
+
|
|
12
|
+
from ._block import Block
|
|
13
|
+
|
|
14
|
+
from ..utils.funcs import rel_error
|
|
15
|
+
|
|
16
|
+
# SISO BLOCKS ===========================================================================
|
|
17
|
+
|
|
18
|
+
class Amplifier(Block):
|
|
19
|
+
"""
|
|
20
|
+
amplifies the input signal by
|
|
21
|
+
multiplication with a constant gain term
|
|
22
|
+
|
|
23
|
+
INPUTS :
|
|
24
|
+
gain : (float) amplifier gain
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, gain=1.0):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.gain = gain
|
|
30
|
+
|
|
31
|
+
def update(self, t):
|
|
32
|
+
prev_output = self.outputs[0]
|
|
33
|
+
self.outputs[0] = self.gain * self.inputs[0]
|
|
34
|
+
return rel_error(prev_output, self.outputs[0])
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## TIME DOMAIN DELAY BLOCK (blocks/delay.py)
|
|
4
|
+
##
|
|
5
|
+
## Milan Rother 2024
|
|
6
|
+
##
|
|
7
|
+
#########################################################################################
|
|
8
|
+
|
|
9
|
+
# IMPORTS ===============================================================================
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from ._block import Block
|
|
14
|
+
from ..utils.adaptivebuffer import AdaptiveBuffer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# BLOCKS ================================================================================
|
|
18
|
+
|
|
19
|
+
class Delay(Block):
|
|
20
|
+
"""
|
|
21
|
+
delays the input signal by a time constant 'tau' in seconds
|
|
22
|
+
using an adaptive rolling buffer
|
|
23
|
+
|
|
24
|
+
INPUTS :
|
|
25
|
+
tau : (float) delay time constant for
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, tau=1e-3):
|
|
29
|
+
super().__init__()
|
|
30
|
+
|
|
31
|
+
#time delay in seconds
|
|
32
|
+
self.tau = tau
|
|
33
|
+
|
|
34
|
+
#create adaptive buffer
|
|
35
|
+
self._buffer = AdaptiveBuffer(self.tau)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __len__(self):
|
|
39
|
+
#no passthrough by definition
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def reset(self):
|
|
44
|
+
#reset inputs and outputs
|
|
45
|
+
self.inputs = {0:0.0}
|
|
46
|
+
self.outputs = {0:0.0}
|
|
47
|
+
|
|
48
|
+
#clear the buffer
|
|
49
|
+
self._buffer.clear()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def update(self, t):
|
|
53
|
+
"""
|
|
54
|
+
Evaluation of the buffer at different times.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
#retrieve value from buffer
|
|
58
|
+
self.outputs[0] = self._buffer.get(t)
|
|
59
|
+
|
|
60
|
+
return 0.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def sample(self, t):
|
|
64
|
+
"""
|
|
65
|
+
Sample input values and time of sampling
|
|
66
|
+
and add them to the buffer.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
#add new value to buffer
|
|
70
|
+
self._buffer.add(t, self.inputs[0])
|