discretetf.py
- Use Python's syntactic sugar to 'pythonize' the Simulink Model. This class wraps the Simulink Model with some Python concepts. In addition to wrapping all of the functions with Python, it builds in a data-logger and plotting and pandas datadframes.
This has been tested on both Windows and Linux. All of the python logic stays the same, the only difference is how the shared library is loaded.
import sys
from discretetf import DiscreteTF
mdl = DiscreteTF()
mdl.initialize()
mdl
discrete_tf<0.0, 0.0, 0.0>
mdl.step()
mdl
discrete_tf<0.0, 0.0, 0.0>
mdl.step()
mdl
discrete_tf<0.001, 0.0, 0.0>
mdl.num
[0.0, 0.0014996250624921886]
mdl.den
[1.0, -0.9995001249791693]
control
toolbox.¶import control
import numpy as np
# How fast the simulink model is running.
Ts = 1e-3
# Static Gain
K = 3
# Time Constant.
tau = 2
sys = control.TransferFunction([K], [tau, 1])
sysd = control.c2d(sys, Ts)
sysd
sysd.num
[[array([0.00149963])]]
mdl.num
[0.0, 0.0014996250624921886]
np.isclose(sysd.num, mdl.num)
array([[[False, True]]])
sysd.den
[[array([ 1. , -0.99950012])]]
mdl.den
[1.0, -0.9995001249791693]
np.isclose(sysd.den, mdl.den)
array([[[ True, True]]])
mdl.initialize()
for step in range(int(15 / Ts) + 1):
mdl.step()
mdl.time
15.0
data = dict()
mdl.init_log()
for step in range(int(15 / Ts) + 1):
mdl.step_log()
mdl.plot()
Ts = 0.001
K = 1
dataframes = list()
for tau in [1, 2, 3]:
sys = control.TransferFunction([K], [tau, 1])
sysd = control.c2d(sys, Ts)
mdl.num = sysd.num[0][0]
mdl.den = sysd.den[0][0]
mdl.init_log()
for step in range(int(15 / Ts)):
mdl.input_signal = 3 if mdl.time >= 2 else 0
mdl.step_log()
mdl.plot()
dataframes.append(mdl.dataframe)
dataframes[0].rename(columns={"output": "output_tau1"}, inplace=True)
dataframes[1].rename(columns={"output": "output_tau2"}, inplace=True)
dataframes[2].rename(columns={"output": "output_tau3"}, inplace=True)
df = dataframes[0].append(dataframes[1]).append(dataframes[2])
df.plot(x="time", y=["output_tau1", "output_tau2", "output_tau3"]);
control.bode
function.import random
# Generate an unknown first order transfer function.
K = random.randint(1, 10)
tau = random.randint(1, 10)
sys = control.TransferFunction([K], [tau, 1])
sysd = control.c2d(sys, Ts)
mdl.num = sysd.num[0][0]
mdl.den = sysd.den[0][0]
def input_fcn(step, freq):
"""Function to simplify calculate sine response given step and frequency (hz)."""
return np.sin(2 * np.pi * freq * step / 1000)
mdl.init_log()
for step in range(int(2 / Ts)):
mdl.input_signal = input_fcn(step, 1)
mdl.step_log()
mdl.plot()
dataframes.append(mdl.dataframe)
f = 0.1
cycles = 10
mdl.init_log()
for step in range(int((cycles * 1 / f) / Ts)):
mdl.input_signal = input_fcn(step, f)
mdl.step_log()
mdl.plot()
Select the last half of the data after it has 'setttled'.
df = mdl.dataframe[int(len(mdl.dataframe) / 2) :]
df.plot(x="time", y=["input", "output"])
<AxesSubplot:xlabel='time'>
df.input.max()
1.0
df.output.max()
2.3077727333628815
from scipy.optimize import minimize
from sklearn.metrics import mean_squared_error
def minfunc(*args):
"""Function to calculate error between estimate function and output."""
# Amplitude
A = args[0][0]
# Phase in Radians/s
phi = args[0][1]
# DC Offset
dc = args[0][2]
output_p = A * np.sin(2 * np.pi * f * df.time + phi) + dc
# Return the mean squared between estimate function and actual output.
return mean_squared_error(output_p, df.output)
# Initial conditions
x0 = [df.output.max(), 0, 0]
results = minimize(minfunc, x0)
results
fun: 1.2575742218969943e-08 hess_inv: array([[ 1.1534359 , -0.00529581, 0.16281604], [-0.00529581, 0.16905652, -0.0042234 ], [ 0.16281604, -0.0042234 , 0.67266656]]) jac: array([-2.07334194e-06, -4.32183696e-07, -4.37765321e-06]) message: 'Optimization terminated successfully.' nfev: 40 nit: 8 njev: 10 status: 0 success: True x: array([ 2.30752107e+00, -1.31180928e+00, 6.21330875e-05])
Calculate the bode function at a given frequency f and return the magnitude and phase.
mag, phase, omega = control.bode(sysd, omega=[2 * np.pi * f], plot=False)
print(f"mag={mag}@{omega/(2*np.pi)}Hz")
print(f"phase={phase}@{omega/(2*np.pi)}Hz")
mag=[2.30752317]@[0.1]Hz phase=[-1.31182349]@[0.1]Hz
The magnitude and phase of the output signal match the bode analysis results.
Magnitude difference between control.bode
function results and estimated function.
mag - results.x[0]
array([2.10128312e-06])
Phase difference between control.bode
function results and estimated function.
phase - results.x[1]
array([-1.42067947e-05])