"""Electronic noise sources"""
import abc
import numpy as np
from scipy.constants import Boltzmann
from .format import Quantity
from .components import BaseElement
from .config import ZeroConfig
CONF = ZeroConfig()
[docs]class NoiseNotFoundError(ValueError):
def __init__(self, noise_description, *args, **kwargs):
message = f"{noise_description} not found"
super().__init__(message, *args, **kwargs)
[docs]class Noise(BaseElement, metaclass=abc.ABCMeta):
"""Noise source.
Parameters
----------
function : callable
Callable that returns the noise associated with a specified frequency vector.
component : :class:`Component`, optional
Component associated with the noise. While optional, this must be set before the noise can
be used in a calculation.
"""
# Noise type, e.g. Johnson noise.
NOISE_TYPE = None
def __init__(self, function=None, component=None):
super().__init__()
self.function = function
self.component = component
[docs] def spectral_density(self, frequencies):
return self.function(frequencies=frequencies)
@property
@abc.abstractmethod
def label(self):
return NotImplemented
def _meta_data(self):
"""Meta data used to provide hash."""
return tuple(self.label)
@property
def noise_type(self):
return self.NOISE_TYPE
def __str__(self):
return self.label
def __repr__(self):
return str(self)
def __eq__(self, other):
return hash(self) == hash(other)
def __hash__(self):
return hash(self._meta_data())
[docs]class ComponentNoise(Noise, metaclass=abc.ABCMeta):
"""Component noise source."""
ELEMENT_TYPE = "component"
@property
def component_type(self):
return self.component.element_type
[docs]class NodeNoise(Noise, metaclass=abc.ABCMeta):
"""Node noise source.
Parameters
----------
node : :class:`Node`
Node associated with the noise.
"""
ELEMENT_TYPE = "node"
def __init__(self, node=None, **kwargs):
super().__init__(**kwargs)
self.node = node
[docs]class VoltageNoise(ComponentNoise, metaclass=abc.ABCMeta):
"""Component voltage noise source."""
NOISE_TYPE = "voltage"
def __init__(self, **kwargs):
super().__init__(function=self.noise_voltage, **kwargs)
[docs] @abc.abstractmethod
def noise_voltage(self, frequencies, **kwargs):
raise NotImplementedError
@property
def label(self):
return f"V({self.component.name})"
[docs]class OpAmpVoltageNoise(VoltageNoise):
[docs] def noise_voltage(self, frequencies):
return self.flat_noise * np.sqrt(1 + self.corner_frequency / frequencies)
@property
def flat_noise(self):
return self.component.params["vnoise"]
@property
def corner_frequency(self):
return self.component.params["vcorner"]
[docs]class ResistorJohnsonNoise(VoltageNoise):
"""Resistor Johnson-Nyquist noise source."""
NOISE_TYPE = "johnson"
[docs] def noise_voltage(self, frequencies):
white_noise = np.sqrt(4 * Boltzmann * float(CONF["constants"]["T"]) * self.resistance)
return np.ones_like(frequencies) * white_noise
@property
def resistance(self):
return self.component.resistance
@property
def label(self):
return f"R({self.component.name})"
[docs]class CurrentNoise(NodeNoise, metaclass=abc.ABCMeta):
"""Node current noise source."""
NOISE_TYPE = "current"
def __init__(self, **kwargs):
super().__init__(function=self.noise_current, **kwargs)
[docs] @abc.abstractmethod
def noise_current(self, frequencies, **kwargs):
raise NotImplementedError
@property
def label(self):
return f"I({self.component.name}, {self.node.name})"
[docs]class OpAmpCurrentNoise(CurrentNoise):
[docs] def noise_current(self, frequencies):
# Ignore node; noise is same at both inputs.
return self.flat_noise * np.sqrt(1 + self.corner_frequency / frequencies)
@property
def flat_noise(self):
return self.component.params["inoise"]
@property
def corner_frequency(self):
return self.component.params["icorner"]