Source code for ossdbs.model_geometry.contacts

# Copyright 2023, 2024 Johannes Reding, Julius Zimmermann
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
from dataclasses import dataclass

import numpy as np

_logger = logging.getLogger(__name__)


[docs]@dataclass class Contact: """Electrode contact settings. Notes ----- This class stores the main parameters of the electrode contacts. General property: * `name`: Will be set during the geometry creation process. * `area`: Will be set during the geometry creation process. Mesh related properties: * `max_h`: Maximum element size on surface * `edge_max_h`: Maximum element size on contact edges Volume conductor model related properties: * `active`: Whether it needs a Dirichlet BC. * `floating`: Whether the voltage shall be fixed but unknown. * `current`: Assigned or computed current value. * `voltage`: Assigned or computed voltage value. * `surface_impedance_model`: Assigned surface impedance model. * `surface_impedance_parameters`: Parameters for surface impedance model. """ name: str area: float | None = None max_h: float = 1e10 # Netgen default edge_max_h: float = 1e10 active: bool = False floating: bool = False current: float = 0.0 voltage: float = 0.0 surface_impedance_model: str | None = None surface_impedance_parameters: dict | None = None def __str__(self): """Write properties to string.""" contact_str = self.name contact_str += f"\nMax h: {self.max_h}" contact_str += f"\nEdge max h: {self.edge_max_h}" contact_str += f"\nActive: {self.active}" contact_str += f"\nFloating: {self.floating}" contact_str += f"\nCurrent: {self.current}" contact_str += f"\nVoltage: {self.voltage}" if self.surface_impedance_model is not None: contact_str += ( f"\nSurface impedance model: {self.surface_impedance_model}\n" ) contact_str += ( f"\nSurface impedance parameters: {self.surface_impedance_parameters}\n" ) return contact_str
[docs] def get_surface_impedance( self, frequency: float, is_complex: bool ) -> float | complex: """Return surface impedance at fixed frequency.""" try: import impedancefitter as ifit except ImportError as err: raise ImportError( "Please install impedancefitter to compute the surface impedance." "Ensure that impedancefitter and its dependencies were correctly " "installed." ) from err # TODO cache this variable ecm = ifit.get_equivalent_circuit_model(self.surface_impedance_model) # get impedance and turn it into surface impedance Z = ecm.eval(omega=2.0 * np.pi * frequency, **self.surface_impedance_parameters) surface_Z = complex(Z) * self.area if np.isclose(surface_Z, 0.0, atol=1e-6): raise ValueError(f"Surface impedance on contact {self.name} almost zero.") if not is_complex: return np.abs(surface_Z) return surface_Z
[docs]def check_contact(contact: Contact): """Check if contact has a clear role.""" if contact.active and contact.floating: raise ValueError( f"""The contact {contact.name} has multiple roles. Please make sure that contacts are either active, floating or none of the two.""" ) if contact.surface_impedance_model is not None: if contact.surface_impedance_parameters is None: raise ValueError( "Surface impedance model was provided without parameter dictionary." ) else: if contact.surface_impedance_parameters is not None: _logger.warning( "Surface impedance parameter dictionary was provided without model. " "It will not be taken into account." )
[docs]class Contacts: """Wrapper class to classify contacts. Notes ----- This class is intended to take the list of contacts of the model geometry and detect active, floating and unused contacts. """ def __init__(self, contacts: list[Contact]) -> None: for contact in contacts: check_contact(contact) self._all_contacts = contacts self._update_contacts() def _update_contacts(self) -> None: """Update contacts after change.""" # Dirichlet boundary conditions self._active = [contact for contact in self._all_contacts if contact.active] # Floating boundary conditions self._floating = [contact for contact in self._all_contacts if contact.floating]
[docs] def append(self, contact: Contact) -> None: """Add another contact.""" self._all_contacts.append(contact) if contact.active: self._active.append(contact) elif contact.floating: self._floating.append(contact)
@property def active(self) -> list[Contact]: """List of all active contacts. Returns ------- list of Contacts """ return self._active @property def floating(self) -> list[Contact]: """List of all floating contacts. Returns ------- list of Contacts """ return self._floating @property def currents(self) -> dict: """Returns the current values of each contact. Returns ------- dict """ return {contact.name: contact.current for contact in self._all_contacts} @currents.setter def currents(self, current_values: dict) -> None: """Set current values of contacts. Parameters ---------- current_values : dict Current values. Not all contacts have to be present in the dictionary. """ _logger.debug(f"Setting contacts with new current_values: {current_values}") for contact in self._all_contacts: if contact.name in current_values: contact.current = current_values[contact.name] @property def voltages(self) -> dict: """Returns the voltage values of each contact. Returns ------- dict """ return {contact.name: contact.voltage for contact in self._all_contacts} @voltages.setter def voltages(self, voltage_values: dict) -> None: """Set voltage value contacts. Parameters ---------- voltage_values : dict Voltage values. Not all contacts have to be present in the dictionary. """ _logger.debug(f"Setting contacts with new voltage_values: {voltage_values}") for contact in self._all_contacts: if contact.name in voltage_values: contact.voltage = voltage_values[contact.name]
[docs] def get_surface_impedances(self, frequency: float, is_complex: bool) -> dict: """Returns the floating impedance values of each contact at a fixed frequency. Returns ------- dict """ return { contact.name: contact.get_surface_impedance(frequency, is_complex) if contact.surface_impedance_model is not None else None for contact in self._all_contacts }
[docs] def update_contact(self, name, floating=None, active=None): """Change type of contact.""" contact = self.__getitem__(name) if floating is not None: contact.floating = floating if active is not None: contact.active = active self._update_contacts()
def __getitem__(self, name): """Get contact by name.""" for contact in self._all_contacts: if name == contact.name: return contact def __iter__(self): """Iterate over contacts.""" return iter(self._all_contacts) def __str__(self): """Write info of all contacts to string.""" contacts_str = "" for contact in self._all_contacts: contacts_str += str(contact) return contacts_str