from finesse.components.node import NodeType, NodeDirection, Node
from finesse.components.general import Connector, ModelElement, DOFDefinition
from finesse.components.workspace import ConnectorWorkspace, Connections, ConnectionSetting
import numpy as np
from finesse.parameter import float_parameter
[docs]class DOFWorkspace(ConnectorWorkspace):
def __init__(self, owner, sim, refill):
super().__init__(owner, sim, refill, refill, Connections(), Connections())
self.drives = None
self.amplitudes = None
[docs]@float_parameter("DC", "DC state of degree of freedom")
class DegreeOfFreedom(Connector):
def __init__(self, name, node, amplitude=1, *node_amp_pairs, DC=0):
Connector.__init__(self, name)
self._add_to_model_namespace = True
self.__nodes = tuple((node, *node_amp_pairs[::2]))
self.__amplitudes = tuple((amplitude, *node_amp_pairs[1::2]))
self.DC = DC
if len(self.drives) != len(self.amplitudes):
raise Exception(
f"Nodes and amplitudes were not the same length, {len(self.drives)} vs {len(self.amplitudes)}"
)
AC_type = None
for node in self.drives:
if not isinstance(node, DOFDefinition):
raise Exception(
f"Degree of freedom ({name}) input `{node}` should be a {DOFDefinition}"
)
if not (node.AC.type == NodeType.ELECTRICAL or node.AC.type == NodeType.MECHANICAL):
raise Exception(
f"Degree of freedom ({name}) input `{node}` should be an electrical or mechanical node"
)
if AC_type and AC_type != node.AC.type:
raise Exception(f"Degree of freedom ({name}) input `{node}` should be the same type as other nodes, {AC_type}")
else:
AC_type = node.AC.type
for amp in self.amplitudes:
if not (np.isscalar(amp) and np.real(amp)):
raise Exception(
f"Degree of freedom ({name}) amplitude `{amp}` is not a real number"
)
if AC_type:
# Only add an AC port if there are some AC drives
self._add_port("AC", NodeType.ELECTRICAL)
self.AC._add_node("i", NodeDirection.INPUT)
self._add_port("out", AC_type)
for i, node in enumerate(self.drives):
self.out._add_node(f"o{i}", None, node=self.drives[i].AC)
self._register_node_coupling(f"AC_out{i}", self.AC.i, self.drives[i].AC)
def _on_add(self, model):
# Setup this DOf to set itself as an external
# setter for the DC parameters it injects into
model._on_pre_build.append(self._pre_build)
model._on_unbuild.append(self._on_unbuild)
def _pre_build(self):
# Set up the DC parameters to be controlled externally, by this DOF element
for node, amp in zip(self.drives, self.amplitudes):
dc_param = node.DC
if dc_param is not None:
# Here we set the DC parameter associated with a node to track the
# value of the DC parameter of this DOF.
# mark that this element will be controlling the value of this parameter
node.DC.set_external_setter(self, amp * self.DC.ref)
def _on_unbuild(self):
# need to remove our
for node, amp in zip(self.drives, self.amplitudes):
dc_param = node.DC
if dc_param is not None:
node.DC.remove_external_setter(self, amp * self.DC.ref)
@property
def node(self):
":getter: Returns the node to be used for driving."
return self.p1.i
@property
def drives(self):
":getter: Returns The nodes this degree of freedom drives."
return tuple(self.__nodes)
@property
def amplitudes(self):
":getter: Returns the node amplitudes which a node is driven."
return tuple(self.__amplitudes)
@property
def dc_enabled(self):
""":getter: Returns True if all driving nodes have an associated DC parameter that can be varied."""
return all((_.dc_parameter is not None for _ in self.drives))
def _get_workspace(self, sim):
if sim.signal:
ws = DOFWorkspace(self, sim, False)
ws.signal.add_fill_function(self.__fill, False)
ws.drives = self.drives
ws.amplitudes = np.array(self.amplitudes)
for i, drive in enumerate(self.drives):
if drive.AC.type == NodeType.MECHANICAL:
ws.amplitudes[i] /= sim.model_data.x_scale
return ws
else:
return None
def __fill(self, ws):
for idx in range(len(ws.drives)):
# Need to loop and determine if our connections have
# been allocated or not
mat_views = getattr(ws.signal.connections, "AC_out"+str(idx))
if mat_views:
# All connections are just their amplitude value
# assumes no HOM couplings or anything between elec
# and mechanical nodes
mat_views[0][:] = ws.amplitudes[idx]