Source code for finesse.components.electronics

import numpy as np

from finesse.components.general import Connector
from finesse.components.workspace import ConnectorWorkspace
from finesse.components.node import NodeDirection, NodeType
from finesse.parameter import float_parameter


[docs]class FilterWorkspace(ConnectorWorkspace): pass
[docs]@float_parameter("gain", "Gain") class ZPKNodeActuator(Connector):
[docs] def __init__(self, name, mechanical_node, gain=1, z=[], p=[], k=1): super().__init__(name) if mechanical_node.type is not NodeType.MECHANICAL: raise Exception("Actuation must be on a mechanical node.") self.gain = gain self.z = z self.p = p self.k = k self.__node = mechanical_node self._add_port("p1", NodeType.ELECTRICAL) self.p1._add_node("i", NodeDirection.INPUT) self._add_port("mech", NodeType.MECHANICAL) self.mech._add_node("actuation", None, mechanical_node) self._register_node_coupling("P1_ACT", self.p1.i, mechanical_node)
def _get_workspace(self, sim): if sim.signal: refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) ws = FilterWorkspace(self, sim, refill, refill) ws.signal.add_fill_function(self.fill, refill) ws.frequencies = sim.electrical_frequencies[self.p1.i].frequencies return ws else: return None
[docs] def fill(self, ws): if ws.signal.connections.P1_ACT_idx > -1: for _ in ws.frequencies: # Assumes only couples to first mech frequency with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.signal.connections.P1_ACT_idx, _.index, 0, ) as mat: mat[:] = ws.values.gain
[docs]@float_parameter("gain", "Gain") class Amplifier(Connector):
[docs] def __init__(self, name, gain=1): super().__init__(name) self.gain = gain self._add_port("p1", NodeType.ELECTRICAL) self.p1._add_node("i", NodeDirection.INPUT) self._add_port("p2", NodeType.ELECTRICAL) self.p2._add_node("o", NodeDirection.OUTPUT) self._register_node_coupling("P1_P2", self.p1.i, self.p2.o)
def _get_workspace(self, sim): if sim.signal: if self.p1.i.full_name not in sim.signal.nodes: return refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) ws = FilterWorkspace(self, sim, refill, refill) ws.signal.add_fill_function(self.fill, refill) ws.frequencies = sim.signal.electrical_frequencies[self.p1.i].frequencies return ws else: return None
[docs] def fill(self, ws): if ws.signal.connections.P1_P2_idx > -1: for _ in ws.frequencies: with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.signal.connections.P1_P2_idx, 0, 0, ) as mat: mat[:] = ws.values.gain
[docs] def eval(self, f): return float(self.gain)
[docs]@float_parameter("gain", "Gain") class Filter(Connector):
[docs] def __init__(self, name, gain=1): super().__init__(name) self.gain = gain self._add_port("p1", NodeType.ELECTRICAL) self.p1._add_node("i", NodeDirection.INPUT) self._add_port("p2", NodeType.ELECTRICAL) self.p2._add_node("o", NodeDirection.OUTPUT) self._register_node_coupling("P1_P2", self.p1.i, self.p2.o)
def _get_workspace(self, sim): if sim.signal: refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) ws = FilterWorkspace(self, sim, refill, refill) ws.signal.add_fill_function(self.fill, refill) ws.frequencies = sim.signal.electrical_frequencies[self.p1.i].frequencies return ws else: return None
[docs] def fill(self, ws): Hz = self.eval(ws.sim.model_data.fsig) if ws.signal.connections.P1_P2_idx > -1: for _ in ws.frequencies: with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.signal.connections.P1_P2_idx, 0, 0, ) as mat: mat[:] = Hz
[docs] def bode_plot(self, f=None, n=None, return_axes=False): """ Plots Bode for this filter. Parameters ---------- f : optional Frequencies to plot for in Hz (Not radians) n : int, optional number of points to plot Returns ------- axis : Matplotlib axis for plot if return_axes=True """ import matplotlib.pyplot as plt import scipy import scipy.signal if f is not None: w = 2 * np.pi * f else: w = None w, mag, phase = scipy.signal.bode(self.sys, n=n) fig, axs = plt.subplots(2, 1, sharex=True) axs[0].semilogx(w / 2 / np.pi, mag) axs[0].set_ylabel("Amplitude [dB]") axs[1].semilogx(w / 2 / np.pi, phase) axs[1].set_xlabel("Frequency [Hz]") axs[1].set_ylabel("Phase [Deg]") fig.suptitle(f"Bode plot for {self.name}") if return_axes: return axs
[docs]@float_parameter("gain", "Gain") class ZPKFilter(Filter):
[docs] def __init__(self, name, z, p, k, *, fQ=False, gain=1): super().__init__(name, gain) import cmath root = lambda f, Q: -2*np.pi*f/(2*Q) + cmath.sqrt((2*np.pi*f/(2*Q))**2 - (2*np.pi*f)**2) if fQ: self.z = [] for f,Q in z: r = root(f,Q) self.z.append(r) self.z.append(r.conjugate()) self.p = [] for f,Q in p: r = root(f,Q) self.p.append(r) self.p.append(r.conjugate()) else: self.z = z self.p = p self.k = k
@property def sys(self): return (self.z, self.p, self.k)
[docs] def eval(self, f): import scipy.signal as signal return ( float(self.gain) * signal.freqs_zpk(self.z, self.p, float(self.k), 2 * np.pi * f)[1] )
[docs]@float_parameter("gain", "Gain") class ButterFilter(ZPKFilter):
[docs] def __init__(self, name, order, btype, frequency, *, gain=1, analog=True): super().__init__(name, [],[],[], gain=gain) self.__order = order self.__btype = btype self.__analog = analog self.__frequency = frequency self.set_zpk()
[docs] def set_zpk(self): import scipy.signal as signal z,p,k = signal.butter( self.order, 2 * np.pi * np.array(self.frequency), btype=self.btype, analog=self.analog, output="zpk", ) self.z = z self.p = p self.k = k
@property def frequency(self): return self.__frequency @frequency.setter def frequency(self, value): self.__frequency = value self.set_zpk() @property def order(self): return self.__order @order.setter def order(self, value): self.__order = value self.set_zpk() @property def btype(self): return self.__btype @btype.setter def btype(self, value): self.__btype = value self.set_zpk() @property def analog(self): return self.__analog @analog.setter def analog(self, value): self.__analog = value self.set_zpk()
[docs]@float_parameter("gain", "Gain") class Cheby1Filter(ZPKFilter): def __init__(self, name, order, rp, btype, frequency, *, gain=1, analog=True): import scipy.signal as signal zpk = signal.cheby1( order, rp, 2 * np.pi * np.array(frequency), btype=btype, analog=analog, output="zpk", ) super().__init__(name, *zpk, gain=gain)