Skip to content

Commit af2e0d3

Browse files
committed
adding example plot
linting examples, making ruff happy
1 parent b59abe7 commit af2e0d3

File tree

9 files changed

+597
-322
lines changed

9 files changed

+597
-322
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# pulse_transitions
22
This library implements a python version of the [Pulse and Transition Metrics function category](https://www.mathworks.com/help/signal/pulse-and-transition-metrics.html?s_tid=CRUX_lftnav) in Matlab.
33

4+
![Second Order System](second-order.png)
45

56
| Function | Description | | Implemented?
67
| --------------------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------- |---------

examples/__init__.py

Whitespace-only changes.

examples/second_order_system.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
from scipy.signal import lsim
4+
from scipy.signal import lti
5+
6+
from pulse_transitions import detect_edges
7+
from pulse_transitions import detect_signal_levels
8+
from pulse_transitions import get_edge_metrics
9+
10+
11+
# Example: synthetic underdamped step response
12+
def generate_step_response(t, damping=0.2, freq=10):
13+
# Second-order system parameters
14+
wn = freq # Natural frequency (rad/s)
15+
zeta = damping # Damping ratio
16+
17+
# Transfer function: H(s) = wn^2 / (s^2 + 2*zeta*wn*s + wn^2)
18+
num = [wn**2]
19+
den = [1, 2*zeta*wn, wn**2]
20+
21+
# Create the system
22+
system = lti(num, den)
23+
24+
u = [0]*len(t[t<0]) + [1]*len(t[t>=0])
25+
t_shift = t-min(t)
26+
t_out, y_out, _ = lsim(system, U=u, T=t_shift)
27+
28+
return t, y_out
29+
30+
31+
def make_annotations_plot(t, y, edge, metrics):
32+
t_low, t_high = edge.start, edge.end
33+
trise = t_high - t_low
34+
mid_level = np.mean(metrics["levels"])
35+
36+
fractional_thresholds = metrics["fractional_thresholds"]
37+
absolute_thresholds = metrics["absolute_thresholds"]
38+
threshold_colors = ["C1", "C2"]
39+
edge_color = "black"
40+
signal_color = "C0"
41+
42+
fig, ax = plt.subplots(figsize=(8, 4))
43+
ax.plot(t, y, label="Signal", color=signal_color)
44+
45+
# Draw absolute thresholds and level lines
46+
for frac, abs_val, color in zip(fractional_thresholds, absolute_thresholds, threshold_colors, strict=False):
47+
ax.axhline(abs_val, linestyle="--", color=color, label=f"{int(frac * 100)}% Threshold")
48+
ax.annotate(f"{int(frac * 100)}%",
49+
xy=(t[0], abs_val),
50+
xytext=(20, 2.5), textcoords="offset points",
51+
color=color)
52+
53+
# Edge start/end lines
54+
edge_label = metrics.get("edge_label", "Edge")
55+
ax.axvline(t_low, linestyle=":", color=edge_color, label=f"{edge_label} start")
56+
ax.axvline(t_high, linestyle=":", color=edge_color, label=f"{edge_label} end")
57+
ax.plot([metrics["midcross"]], [mid_level], "kx", label="Mid Cross")
58+
59+
# Annotate rise time & slew rate
60+
slewrate = metrics.get("slewrate", np.nan)
61+
ax.annotate(f"Rise: {trise * 1e3:.0f} ps\nSlew: {slewrate:.1f} V/ns",
62+
xy=(t_high, mid_level),
63+
xytext=(20, 0), textcoords="offset points",
64+
ha="left", va="center")
65+
66+
idx, overshoot = metrics["overshoot"]
67+
ax.plot(t[idx], y[idx], "ro", label="Overshoot")
68+
ax.annotate(f"Overshoot: {overshoot * 100:.0f}%",
69+
xy=(t[idx], y[idx]),
70+
xytext=(20, 0), textcoords="offset points",
71+
ha="left", va="center")
72+
73+
idx, undershoot = metrics["undershoot"]
74+
ax.plot(t[idx], y[idx], "go", label="Undershoot")
75+
ax.annotate(f"Undershoot: {undershoot * 100:.0f}%",
76+
xy=(t[idx], y[idx]),
77+
xytext=(20, 0), textcoords="offset points",
78+
ha="left", va="center")
79+
80+
ax.set_xlabel("Time (ns)")
81+
ax.set_ylabel("Amplitude (V)")
82+
ax.set_title("Step Response with Edge Detection")
83+
ax.legend()
84+
ax.grid(True, linestyle="--", linewidth=0.5, color="lightgray") # noqa: FBT003
85+
plt.tight_layout()
86+
return fig, ax
87+
88+
89+
90+
# Generate data
91+
t = np.linspace(-0.25, 1, 1000)
92+
t_out, y = generate_step_response(t, freq=25/(max(t)))
93+
94+
95+
#plt.plot(t, y)
96+
#plt.show()
97+
98+
threshold_fractions=(0.1, 0.9)
99+
100+
# Estimate levels (e.g. using histogram or endpoints)
101+
levels = detect_signal_levels(x=t, y=y, method="histogram")
102+
low, high = levels
103+
104+
# Detect edges
105+
edge = detect_edges(t, y, levels=levels, thresholds=threshold_fractions)[0]
106+
107+
# Compute overshoot / undershoot
108+
metrics = get_edge_metrics(x=t, y=y, thresholds=threshold_fractions, levels=levels)
109+
110+
fig, ax = make_annotations_plot(t, y, edge, metrics)
111+
fig.savefig("second-order-annotations.png", dpi=300)
112+
plt.show()

second-order.png

215 KB
Loading

src/pulse_transitions/__init__.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1+
from .transient_response import calculate_falltime
2+
from .transient_response import calculate_midcross
3+
from .transient_response import calculate_overshoot
4+
from .transient_response import calculate_risetime
5+
from .transient_response import calculate_thresholds
6+
from .transient_response import calculate_undershoot
17
from .transient_response import detect_edges
28
from .transient_response import detect_first_edge
39
from .transient_response import detect_signal_levels
410
from .transient_response import detect_thresholds
5-
from .transient_response import falltime
6-
from .transient_response import midcross
7-
from .transient_response import overshoot
8-
from .transient_response import risetime
9-
from .transient_response import statelevels
10-
from .transient_response import undershoot
11+
from .transient_response import get_edge_metrics
1112

1213
__all__ = (
13-
"falltime",
14-
"midcross",
15-
"overshoot",
16-
"risetime",
17-
"statelevels",
18-
"undershoot",
14+
#"matpulse", # Matlab like interface
15+
"get_edge_metrics",
16+
"calculate_falltime",
17+
"calculate_midcross",
18+
"calculate_overshoot",
19+
"calculate_risetime",
20+
"calculate_undershoot",
21+
"calculate_thresholds",
1922
"detect_thresholds",
2023
"detect_signal_levels",
2124
"detect_edges",

0 commit comments

Comments
 (0)