Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions src/LCRmeter-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self) -> None:
self.frequency = 1000
self.bias_mode = "Voltage bias"
self.bias = 1
self.average = 1
self.use_list_sweep = False
self.list_sweep_values: np.array = np.empty((0,))

Expand All @@ -61,17 +62,19 @@ def __init__(self) -> None:
self.capacitance = 4e-9 # F
self.resistance = 50 # Ohm
self.noise_level = 0.05 # percent
self.bias_coefficient = 0.05

def set_GUIparameter(self) -> dict: # noqa: N802
"""Return a dictionary with GUI parameters."""
return {
"SweepMode": ["Frequency in Hz"],
"SweepValue": ["List"],
"SweepMode": ["Frequency in Hz", "Voltage bias in V"],
# "SweepValue": ["List"],
"StepMode": ["None"],
"Frequency": 1000,
"OperatingMode": ["R-X"],
"ValueTypeBias": ["Voltage bias", "Current bias"],
"ValueBias": 1,
"Average": "1",

# List Sweep Parameters
"ListSweepCheck": True,
Expand All @@ -92,9 +95,10 @@ def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802

self.bias_mode = parameter["ValueTypeBias"]
self.bias = float(parameter["ValueBias"])
self.average = int(float(parameter["Average"]))

# List Mode
self.use_list_sweep = parameter["SweepValue"] == "List sweep"
self.use_list_sweep = parameter.get("SweepValue", "None") == "List sweep"
if self.use_list_sweep:
self.use_list_sweep = True
list_sweep_values = np.arange(
Expand Down Expand Up @@ -128,30 +132,41 @@ def handle_set_value(self, mode: str, value: float) -> None:

def call(self) -> list:
"""Simulate data and return as a list."""
measured_frequency = self.list_sweep_values if self.use_list_sweep else self.frequency
measured_resistance = self.simulate_resistance(measured_frequency)
measured_reactance = self.simulate_reactance(measured_frequency)
if self.use_list_sweep:
measured_frequency = self.list_sweep_values
measured_resistance = self.simulate_resistance(measured_frequency)
measured_reactance = self.simulate_reactance(measured_frequency)

else:
measured_frequency = self.frequency
measured_resistance = np.mean([self.simulate_resistance(measured_frequency) for _ in range(self.average)])
measured_reactance = np.mean([self.simulate_reactance(measured_frequency) for _ in range(self.average)])

return [measured_resistance, measured_reactance, measured_frequency, self.bias]

"""Simulated LCRmeter"""

def simulate_resistance(self, frequency: float | np.array) -> float:
"""Simulate resistance with weak frequency dependency of the device under test."""
resistance = self.resistance + frequency / 1000
return self.simulate_noise(resistance)
# Calculate the base resistance with a weak frequency dependency
base_resistance = self.resistance + frequency / 1000.0
# Adjust resistance based on the bias (using an arbitrary coefficient of 0.01)
adjusted_resistance = base_resistance * (1 + self.bias_coefficient * self.bias)
return self.simulate_noise(adjusted_resistance)

def simulate_reactance(self, frequency: float | np.array) -> float:
"""Simulate reactance of the device under test."""
angular_frequency = 2 * np.pi * frequency

# inductive reactance (Inductance)
# Calculate inductive reactance (Inductance) and capacitive reactance
x_l = angular_frequency * self.inductance

# capacitive reactance
x_c = -1 / (angular_frequency * self.capacitance)
base_reactance = x_l + x_c

# Adjust reactance based on the bias (using an arbitrary coefficient of 0.01)
adjusted_reactance = base_reactance * (1 + self.bias_coefficient * self.bias)

return self.simulate_noise(x_l + x_c)
return self.simulate_noise(adjusted_reactance)

def simulate_noise(self, value: float | np.array) -> float:
"""Simulate noise."""
Expand Down
4 changes: 4 additions & 0 deletions src/Logger-Simulation_MultiMeter/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def set_GUIparameter(self) -> dict[str, str]: # noqa: N802

def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802
"""Receive the values of the GUI parameters that were set by the user in the SweepMe! GUI."""
self.variables = []
self.units = []
self.voltage_channels = list(map(int, parameter["Voltage Channels"].split(",")))
for channel in self.voltage_channels:
self.variables.append(f"Voltage CH{channel}")
Expand All @@ -80,6 +82,8 @@ def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802
self.units.append("A")

# Update plottype and savetype
self.plottype = []
self.savetype = []
for _ in self.variables:
self.plottype.append(True)
self.savetype.append(True)
Expand Down
5 changes: 3 additions & 2 deletions src/SMU-Simulation_Diode/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802

def initialize(self) -> None:
"""Initialize the device. This function is called only once at the start of the measurement."""
if float(self.protection) > 1.0:
self.stop_Measurement("Aborted because %s" % self.protection)
max_compliance = 1.0
if float(self.protection) > max_compliance:
self.stop_Measurement(f"Compliance {self.protection} is higher than the maximum compliance of {max_compliance}.")

def call(self) -> list[float]:
"""Return the measurement results. Must return as many values as defined in self.variables."""
Expand Down
13 changes: 11 additions & 2 deletions src/Scope-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def __init__(self) -> None:
self.channel2_range: float = 5.0
self.channel2_offset: float = 0.0

self.use_simulated_signal: bool = False
"""If True, the driver uses the simulated signal from a Signal-Simulation driver in the same branch."""

@staticmethod
def find_ports() -> list[str]:
"""Return dummy port."""
Expand All @@ -75,9 +78,9 @@ def set_GUIparameter() -> dict: # noqa: N802
return {
# Timing:
"TimeRange": ["Time range in s"],
"TimeRangeValue": 1e-3,
"TimeRangeValue": ["1e-3"],
"SamplingRateType": ["Sampling rate in Hz"],
"SamplingRate": "1e3",
"SamplingRate": ["1e6"],
# Channels:
"Channel1": True,
"Channel2": False,
Expand All @@ -97,6 +100,12 @@ def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802
self.sampling_rate_type = parameter["SamplingRate"]
self.sampling_rate = float(parameter["SamplingRate"])

# Return Variables
self.variables = ["Time"]
self.units = ["s"]
self.plottype = [True] # True to plot data
self.savetype = [True] # True to save data

self.channel1 = parameter["Channel1"]
if self.channel1:
self.channel1_range = float(parameter["Channel1_Range"])
Expand Down
8 changes: 4 additions & 4 deletions src/Signal-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ def set_GUIparameter(self) -> dict: # noqa: N802
],
"Waveform": ["Sine", "Square", "Triangle", "Sawtooth"],
"PeriodFrequency": ["Frequency in Hz", "Period in s"],
"PeriodFrequencyValue": 2e-6,
"PeriodFrequencyValue": "2e3",
"AmplitudeHiLevel": ["Amplitude in V"],
"AmplitudeHiLevelValue": 1.0,
"AmplitudeHiLevelValue": "1.0",
"OffsetLoLevel": ["Offset in V"],
"OffsetLoLevelValue": 0.0,
"OffsetLoLevelValue": "0.0",
"DelayPhase": ["Delay in s", "Phase in deg"],
"DelayPhaseValue": 0.0,
"DelayPhaseValue": "0.0",
# "Impedance": ["50"],
}

Expand Down
36 changes: 28 additions & 8 deletions src/Spectrometer-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self) -> None:
self.shortname = "Simulation" # short name will be shown in the sequencer

self.variables = ["Wavelength", "Intensity", "Integration time"]
self.units = ["nm", "µJ", "s"]
self.units = ["nm", "counts/s", "s"]
self.plottype = [True, True, True]
self.savetype = [True, True, True]

Expand Down Expand Up @@ -94,6 +94,11 @@ def measure(self) -> None:
for _ in range(self.average):
time.sleep(self.integration_time)

def apply(self) -> None:
"""Apply the sweep values."""
if self.sweep_mode == "Integration time in s":
self.integration_time = self.value

def call(self) -> list:
"""Return the measurement results. Must return as many values as defined in self.variables."""
wavelengths = self.get_wavelengths()
Expand All @@ -102,8 +107,9 @@ def call(self) -> list:
intensities += self.get_intensities()

intensities = intensities / self.average
intensities_per_second = intensities / self.integration_time

return [wavelengths, intensities, self.integration_time]
return [wavelengths, intensities_per_second, self.integration_time]

def get_wavelengths(self) -> np.array:
"""Return a list of all wavelengths at which the spectrum is measured."""
Expand All @@ -124,10 +130,10 @@ def get_intensities(self) -> np.array:
peak_amplitude = 1000

intensities = peak_amplitude * self.calculate_gaussian_peak(peak_position, peak_width)
intensities += self.calculate_background_spectrum()
elif self.port_string == "Spectrometer3":
# Simulate background spectrum
intensities = 150 * self.calculate_gaussian_peak(750, 250000)
intensities += 350 * self.calculate_gaussian_peak(450, 50000)
intensities = self.calculate_background_spectrum()
else:
# load spectral data from test_spectrum.txt
spectrum_file = os.path.dirname(os.path.abspath(__file__)) + os.sep + "test_spectrum.txt"
Expand All @@ -136,13 +142,27 @@ def get_intensities(self) -> np.array:

intensities = intensities * self.integration_time

# Scale intensity with integration time
intensities = intensities * self.integration_time

# Add noise
intensities_new = []
for intensity in intensities:
intensities_new.append(intensity + 50 * random.random() - 25)
intensities += self.calculate_noise()
Comment on lines +145 to +149

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Remove duplicate scaling

Suggested change
# Scale intensity with integration time
intensities = intensities * self.integration_time
# Add noise
intensities_new = []
for intensity in intensities:
intensities_new.append(intensity + 50 * random.random() - 25)
intensities += self.calculate_noise()
# Add noise
intensities += self.calculate_noise()
# No need to scale here as it's already scaled in get_intensities()


return np.array(intensities_new)
return np.array(intensities)

def calculate_background_spectrum(self) -> np.array:
"""Calculate a background spectrum."""
intensities = 150 * self.calculate_gaussian_peak(750, 250000)
intensities += 350 * self.calculate_gaussian_peak(450, 50000)
return intensities

def calculate_gaussian_peak(self, peak_position: float, peak_width: float) -> np.array:
"""Calculate a gaussian peak with the given parameters."""
return np.exp(-(self.get_wavelengths() - peak_position) ** 2 / peak_width)

def calculate_noise(self) -> np.array:
"""Calculate a noise spectrum."""
noise = []
for _ in self.get_wavelengths():
noise.append(50 * random.random() - 25)
return noise
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@

# SweepMe! driver
# * Module: Switch
# * Instrument: Simulation Power Supply
# * Instrument: Simulation Driver

from pysweepme.EmptyDeviceClass import EmptyDevice


class Device(EmptyDevice):
"""Device class to simulate a power supply."""
"""Device class to simulate a voltage switch."""
description = """
<h3>Simulation Power Supply</h3>
<p>This device class simulates a power supply. The same voltage that is set is also returned.</p>
<h3>Simulation Switch</h3>
<p>This device class simulates a Voltage switch. The same voltage that is set is also returned.</p>
"""

def __init__(self) -> None:
Expand Down
4 changes: 3 additions & 1 deletion src/Temperature-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import math
import time
from random import random
from typing import Any

from pysweepme.EmptyDeviceClass import EmptyDevice
Expand Down Expand Up @@ -128,7 +129,8 @@ def measure(self) -> None:

def call(self) -> [float, float]:
"""Return the measurement results. Must return as many values as defined in self.variables."""
return [self.temperature]
# add some noise to the temperature
return [self.temperature + 0.005 * (2 * random() - 1)]

def measure_temperature(self) -> float:
"""Returns the calculated temperature."""
Expand Down
11 changes: 6 additions & 5 deletions src/WaferProber-Simulation_Driver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,15 @@ def get_GUIparameter(self, parameter: dict) -> None: # noqa: N802

# here functions start that only exists for the WaferProber module and are called by this module

def get_probeplan(self, probeplanpath: str = "") -> tuple[list[str], list[str], list[str]]:
def get_probeplan(self) -> tuple[list[str], list[str], list[str]]:
"""This function is called when the 'Update' button is pressed in the GUI.

It reads the probeplan from a given file and returns the lists of wafers, dies, and subsites.
If this function is implemented with a 'probeplan: str = ""' parameter, it will open a file dialog to select a
probeplan file. It reads the probeplan from a given file and returns the lists of wafers, dies, and subsites.
"""
wafers = [f"C{i}W{j}" for i in range(1, 3) for j in range(1, 7)]
dies = ["%i,%i" % (i, i + 1) for i in np.arange(5) + 1]
subsites = [f"Pos {i * 100},{j * 50}" for i in range(1, 3) for j in range(4, 6)]
wafers = ["C1W1", "C1W2", "C2W1"]
dies = ["1,1", "1, 13", "7,5", "12,8"]
subsites = ["Pos 100, 50", "Pos 75, 150"]

return wafers, dies, subsites

Expand Down