nutshell/plots/01_tx_lines.py

192 lines
6.1 KiB
Python
Raw Normal View History

2023-04-09 00:04:20 -06:00
import logging
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Tuple, Union
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.axes import Axes
from matplotlib.figure import Figure
dir_ = Path(__file__).parent.resolve()
dir_root = dir_ / ".."
dir_assets = dir_root / "assets"
log = logging.Logger(__name__)
log.setLevel(logging.INFO)
class AnimatedPlot(ABC):
fig: Figure
ax: Union[Axes, Tuple[Axes]]
def __init__(self, frames: int = 100):
self.frames = int(frames)
self.fig, self.ax = plt.subplots(1, 1)
def save(self, filename: Union[Path, str], framerate: int = 30):
log.info(f"Generating animation: {self.__class__}...")
an = FuncAnimation(
self.fig,
self.update,
frames=np.linspace(0, 1, self.frames, endpoint=False),
init_func=self.init,
blit=False,
)
an.save(str(filename), writer="pillow", fps=framerate)
log.info(f"Generating animation: {self.__class__}...Done")
def init(self) -> None:
pass
@abstractmethod
def update(self, t: float) -> None:
pass
class TxLinePlot(AnimatedPlot):
x = np.linspace(-2 * np.pi, 2 * np.pi, 500)
def update(self, t: float):
v = 2 * np.pi
self.ax.clear()
self.ax.plot(self.x, np.real(np.exp(1j * (t * v - self.x))), label="$V$")
self.ax.autoscale(enable=True, axis="x", tight=True)
self.ax.set_ylim(-1.1, 1.1)
self.ax.set_xlabel("z")
self.ax.set_ylabel("V")
self.ax.set_title(f"$t={t:0.2f}$")
def format_func(value, tick_number):
# find number of multiples of pi/2
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif np.abs(N) == 1:
return r"${0}\pi/2$".format("-" if N < 0 else "")
elif np.abs(N) == 2:
return r"${0}\pi$".format("-" if N < 0 else "")
elif N % 2 > 0:
return r"${0}\pi/2$".format(N)
else:
return r"${0}\pi$".format(N // 2)
self.ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
self.ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
self.ax.grid(True)
self.fig.tight_layout()
class SuperpositionPlot(AnimatedPlot):
x = np.linspace(-2 * np.pi, 2 * np.pi, 500)
def update(self, t: float):
v = 2 * np.pi
self.ax.clear()
vf = np.real(np.exp(1j * (t * v - self.x)))
vr = np.real(np.exp(1j * (t * v + self.x)))
self.ax.plot(self.x, vf, label="$V_f$")
self.ax.plot(self.x, vr, label="$V_r$")
self.ax.plot(self.x, vf + vr, color="black", linestyle="dotted", label="$V$")
self.ax.autoscale(enable=True, axis="x", tight=True)
self.ax.set_ylim(-2.2, 2.2)
self.ax.set_xlabel("z")
self.ax.set_ylabel("V")
self.ax.set_title(f"$t={t:0.2f}$")
def pi_ticks(value, tick_number):
# find number of multiples of pi/2
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif np.abs(N) == 2:
# +/- 1 * pi
return "$" + ("-" if N < 0 else "") + r"\pi$"
elif N % 2 == 0:
return "$" + f"{N // 2}" + r"\pi$"
else:
return "$" + ("-" if N < 0 else "") + r"\frac{" + f"{np.abs(N)}" + r"}{2}\pi$"
self.ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
self.ax.xaxis.set_major_formatter(plt.FuncFormatter(pi_ticks))
self.ax.grid(True)
self.ax.legend(loc="lower right")
self.fig.tight_layout()
class ReflectionPlot(AnimatedPlot):
x = np.linspace(-4 * np.pi, 0, 500)
def __init__(self, zl: complex, **kwargs):
super().__init__(**kwargs)
self.zl = zl
self.z0 = 1
def update(self, t: float):
v = 2 * np.pi
self.ax.clear()
gamma = 1 if self.zl == np.inf else (self.zl - self.z0) / (self.zl + self.z0)
vf = np.exp(1j * (t * v - self.x))
vr = gamma * np.exp(1j * (t * v + self.x))
self.ax.plot(self.x, np.real(vf), label="$V_f$")
self.ax.plot(self.x, np.real(vr), label="$V_r$")
self.ax.plot(self.x, np.real(vf + vr), color="black", linestyle="dotted", label="$V$")
envelope = np.abs(vf + vr)
self.ax.fill_between(self.x, envelope, -envelope, color="black", alpha=0.1)
self.ax.autoscale(enable=True, axis="x", tight=True)
self.ax.set_ylim(-2.2, 2.2)
self.ax.set_xlabel("z")
self.ax.set_ylabel("V")
self.ax.set_title(f"$Z_L/Z_0={self.zl}$, $t={t:0.2f}$")
def pi_ticks(value, tick_number):
# find number of multiples of pi/2
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif np.abs(N) == 2:
# +/- 1 * pi
return "$" + ("-" if N < 0 else "") + r"\pi$"
elif N % 2 == 0:
return "$" + f"{N // 2}" + r"\pi$"
else:
return "$" + ("-" if N < 0 else "") + r"\frac{" + f"{np.abs(N)}" + r"}{2}\pi$"
self.ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
self.ax.xaxis.set_major_formatter(plt.FuncFormatter(pi_ticks))
self.ax.grid(True)
self.ax.legend(loc="lower left")
self.fig.tight_layout()
# class SmithPlot:
# def __init__(self, )
def generate():
TxLinePlot().save(dir_assets / "tx_lines" / "tx_line.gif")
SuperpositionPlot().save(dir_assets / "tx_lines" / "superposition.gif")
ReflectionPlot(zl=1).save(dir_assets / "tx_lines" / "reflection_matched.gif")
ReflectionPlot(zl=np.inf).save(dir_assets / "tx_lines" / "reflection_open.gif")
ReflectionPlot(zl=0).save(dir_assets / "tx_lines" / "reflection_short.gif")
ReflectionPlot(zl=0.5).save(dir_assets / "tx_lines" / "reflection_real.gif")
ReflectionPlot(zl=1 + 1j).save(dir_assets / "tx_lines" / "reflection_complex.gif")
if __name__ == "__main__":
generate()