Bode-Plotter / app.py
KYLiN724's picture
init commit
334ba8c
import gradio as gr
import numpy as np
from control.matlab import tf, pole, zero, frequency_response, bode
from sympy.polys.polytools import poly_from_expr
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def poly_handler(poly_str: str) -> list[float]:
try:
if poly_str.isdigit():
poly_coffs = [poly_str]
else:
poly = poly_from_expr(poly_str)[0]
poly_coffs = poly.all_coeffs()
return list(map(float, poly_coffs))
except Exception as e:
raise ValueError(f"Error parsing polynomial: {e}")
def bode_information(poly_upper: str, poly_lower: str) -> tuple:
upper_poly_coffs = poly_handler(poly_upper)
lower_poly_coffs = poly_handler(poly_lower)
num, den = np.array(upper_poly_coffs), np.array(lower_poly_coffs)
try:
trans_func_g = tf(num, den)
except Exception as e:
raise ValueError(f"Error creating transfer function: {e}")
pole_result = pole(trans_func_g)
zero_result = zero(trans_func_g)
mag, phase, omega = frequency_response(trans_func_g)
return mag, phase, omega
def to_latex(poly_upper: str, poly_lower: str) -> str:
upper_poly_coffs = poly_handler(poly_upper)
lower_poly_coffs = poly_handler(poly_lower)
def process_item(i: float, c: float):
c = f"+ {c}" if c >= 0 else f"- {-c}"
if i == 0:
return f"{c}"
if i == 1:
return f"{c} s"
return f"{c} s^{i}"
upper_str = "".join(
[process_item(i, c) for i, c in enumerate(upper_poly_coffs[::-1]) if c != 0]
)
lower_str = "".join(
[process_item(i, c) for i, c in enumerate(lower_poly_coffs[::-1]) if c != 0]
)
if upper_str.startswith("+ "):
upper_str = upper_str[2:]
if lower_str.startswith("+ "):
lower_str = lower_str[2:]
latex_str = r"\frac{" + upper_str + r"}{" + lower_str + r"}"
return latex_str
def bode_graph(mag: np.ndarray, phase: np.ndarray, omega: np.ndarray):
mag_db = 20 * np.log10(mag)
fig = make_subplots(
rows=1,
cols=2,
subplot_titles=(
"Bode Plot - Magnitude",
"Bode Plot - Phase",
),
)
fig.add_trace(
go.Scatter(x=omega, y=mag_db, mode="lines", name="Magnitude"), row=1, col=1
)
fig.add_trace(
go.Scatter(x=omega, y=phase, mode="lines", name="Phase"), row=1, col=2
)
fig.update_yaxes(title_text="Magnitude (dB)", row=1, col=1)
fig.update_yaxes(title_text="Phase (degrees)", row=1, col=2)
fig.update_xaxes(type="log", title_text="Frequency (rad/s)", row=1, col=1)
fig.update_xaxes(type="log", title_text="Frequency (rad/s)", row=1, col=2)
fig.update_layout(title_text="Bode Plot")
return fig
def run_it(upper_poly: str, lower_poly: str):
mag, phase, omega = bode_information(upper_poly, lower_poly)
latex_str = to_latex(upper_poly, lower_poly)
fig = bode_graph(mag, phase, omega)
return fig, f"$$H(s)={latex_str}$$"
def main():
with gr.Blocks() as demo:
gr.Markdown("# Bode Plotter")
gr.Markdown("This app allows you to plot Bode plots for transfer functions.")
gr.Markdown(
"Enter the transfer function in the form of a string, e.g., '1/(s^2 + 2*s + 1)'."
)
upper_input = gr.Textbox(
label="Numerator Polynomial",
placeholder="Enter the numerator polynomial (e.g., '1 - 2*s + 3*s^2')",
lines=2,
)
lower_input = gr.Textbox(
label="Denominator Polynomial",
placeholder="Enter the denominator polynomial (e.g., '1 + 4*s + 5*s^2')",
lines=2,
)
run_btn = gr.Button("Plot Bode", variant="primary")
gr.Markdown("### Bode Plot")
with gr.Column():
latex_output = gr.Markdown(label="Transfer Function LaTeX")
bode_plot = gr.Plot(label="Bode Plot")
run_btn.click(
run_it,
inputs=[
upper_input,
lower_input,
],
outputs=[bode_plot, latex_output],
)
demo.launch()
if __name__ == "__main__":
main()