Dataset Generation: random_variations_v2 Dataset

Imports

from copy import deepcopy
import numpy as np
from matplotlib import pyplot as plt
from tqdm import tqdm

from simcats import Simulation, default_configs
from simcats.distortions import (
    OccupationDotJumps,
    OccupationTransitionBlurringFermiDirac,
    SensorPotentialPinkNoise,
    SensorPotentialRTN,
    SensorResponseRTN,
    SensorResponseWhiteNoise,
)
from simcats.sensor import SensorGeneric, SensorInterface, SensorPeakLorentzian
from simcats.support_functions import (
    NormalSamplingRange,
    ExponentialSamplingRange,
    plot_csd,
    rotate_points,
)

from simcats_datasets.generation import create_simulated_dataset
from simcats_datasets.loading import load_dataset
DIPlib -- a quantitative image analysis library
Version 3.4.1 (Oct 13 2023)
For more information see https://diplib.org

Dataset Config

Configurations for the generation of the dataset.

num_configs = 1000  # number of different configs to be sampled
num_samples_per_config = 100  #  number of CSDs to be generated per config
resolution = np.array([96, 96])  # resolution (in pixels) of the generated CSDs given as [res_g1, re_g2] ([res_x, res_y])
volt_range = np.array([0.03 * 0.96, 0.03 * 0.96])  # volt_range of the generated CSDs
lut_entries = 1000  # lookup table entries to use for the calculation of the tct_bezier

num_workers = 10  # number of workers for parallel generation of CSDs

ds_path = r".\simulated_data_GaAs_v1_random_variations_v2_96x96.h5"

# enable / disable the debug plotting / visualization. If debug plotting is enabled, no dataset will be generated and saved. Instead, the images and distributions are plotted.
only_debug_plots = False
rng = np.random.default_rng()

Create Samplers for Different Parts of the Simulation

Parameters are extracted from data of the GaAs sample mentioned in Hader et al. “Simulation of Charge Stability Diagrams for Automated Tuning Solutions (SimCATS)”, DOI: 10.1109/TQE.2024.3445967 (https://ieeexplore.ieee.org/document/10640289). Most parameters are taken from Fleitmann “Characterization of Distortions in Charge Stability Diagrams and their Simulation in Modeled Data”, DOI: 10.34734/FZJ-2023-05704 (https://juser.fz-juelich.de/record/1019873).

Total Charge Transition (TCT) Parameter Sampler

# slopes of the lead transitions in 45°-rotated space
slope1_min = 0.21429 
slope1_max = 0.54688
slope2_min = -0.44
slope2_max = -0.07692
# angle between lead transitions
angle_lead_min = 0.43760867228
angle_lead_max = 1.6789624091
# lengths of the lead transitions (between minima and maxima of a TCT)
length_min = 0.01
length_max = 0.025
# angle between interdot and first lead transition (slope1 is rotated counter-clockwise by this angle to get the direction of the interdot vector)
angle_interdot_min = 0.58153916891
angle_interdot_max = 1.396938844
# length of the interdot transition
interdot_length_min = 0.00261
interdot_length_max = 0.00987
interdot_length_mean = 0.004 # Currently not used, as we sample interdot length with a uniform distribution. Kept for the sake of completeness.
interdot_length_std = 0.0015 # Currently not used, as we sample interdot length with a uniform distribution. Kept for the sake of completeness.
# width of the interdot transition, larger values lead to more curved TCTs
interdot_width_min = 0.00043
interdot_width_max = 0.00814
# relation between width and length of the interdot transition
rel_interdot_length_width_min = 0.8692269873603532
rel_interdot_length_width_max = 9.055385138137407
# limits for the plunger gate voltages used for the simulation 
volt_limits_g1 = np.array([-0.2, -0.06])
volt_limits_g2 = np.array([-0.2, -0.06])
# place for the first bezier anchor, which determines were the first interdot transition is located
first_bezier_point_min, first_bezier_point_max = -0.14, -0.14
def add_one_tct(tct_params: list[np.ndarray], shift_vec: np.ndarray) -> list[np.ndarray]:
    """Adds one TCT to the list of TCTs, by shifting the last TCT by the shift vector.
    
    Args:
        tct_params: List of TCTs (or more precisely TCT parameters).
        shift_vec: The vector that describes the shift between two adjacent TCTs.

    Returns:
        The list of TCTs including one additional TCT.
    """
    temp_params = tct_params[-1].copy()
    temp_params[4] += shift_vec[0]
    temp_params[5] += shift_vec[1]
    temp_params[6] += shift_vec[0]
    temp_params[7] += shift_vec[1]
    tct_params.append(temp_params)
    return tct_params

def sample_tct_params(num_tct=15) -> tuple[list[np.ndarray], np.ndarray]:
    """Samples TCT parameters from the ranges listed in the cell above. 
    
    Args:
        num_tct: The number of TCTs to sample.

    Returns:
        Tuple consisting of the sampled TCTs and the corresponding shift vector.
    """
    # sample slope
    slope1 = np.array([1, rng.uniform(slope1_min, slope1_max)])
    slope2 = np.array([1, rng.uniform(slope2_min, slope2_max)])

    angle_lead = np.arccos(slope1.dot(slope2) / (np.linalg.norm(slope1) * np.linalg.norm(slope2)))
    # resample slope if angle_lead is not between angle_lead_min and angle_lead_max
    while not (angle_lead_min <= angle_lead <= angle_lead_max):
        slope1 = np.array([1, rng.uniform(slope1_min, slope1_max)])
        slope2 = np.array([1, rng.uniform(slope2_min, slope2_max)])
        angle_lead = np.arccos(slope1.dot(slope2) / (np.linalg.norm(slope1) * np.linalg.norm(slope2)))

    # sample lengths
    lengths = [
        rng.uniform(length_min, length_max),
        rng.uniform(length_min, length_max),
    ]

    # sample interdot_vec
    interdot_vec = rotate_points(slope1, rng.uniform(angle_interdot_min, angle_interdot_max))
    # rotate interdot_vec like it would be in the CSD space
    interdot_vec_rot = rotate_points(interdot_vec, -np.pi / 4)
    while interdot_vec_rot[0] < 0 or interdot_vec_rot[1] < 0:
        # interdot vector should not go backwards in the image space, so resample if the rotated vector has negative entries
        interdot_vec = rotate_points(slope1, rng.uniform(angle_interdot_min, angle_interdot_max))
        # rotate interdot_vec
        interdot_vec_rot = rotate_points(interdot_vec, -np.pi / 4)
    interdot_vec = interdot_vec / np.linalg.norm(interdot_vec)

    # sample interdot_length
    # usage of uniform distribution to get more diverse data
    interdot_length = rng.uniform(interdot_length_min, interdot_length_max)
    while interdot_length < interdot_length_min or interdot_length > interdot_length_max:
        interdot_length = rng.uniform(interdot_length_min, interdot_length_max)

    # sample bezier_width
    bezier_width = rng.uniform(interdot_width_min, interdot_width_max)
    # resample interdot_length and bezier_width until interdot_length / bezier_width is between rel_interdot_length_width_min and rel_interdot_length_width_max
    while not (
        rel_interdot_length_width_min <= interdot_length / bezier_width
        and interdot_length / bezier_width <= rel_interdot_length_width_max
    ):
        interdot_length = rng.uniform(interdot_length_min, interdot_length_max)
        bezier_width = rng.uniform(interdot_width_min, interdot_width_max)
    interdot_vec = interdot_vec * interdot_length

    # place first bezier_point in lower left corner
    bezier_point = rng.uniform(first_bezier_point_min, first_bezier_point_max, size=2)
    bezier_point = rotate_points(bezier_point)

    # calculate the second bezier point, so that it is in the correct direction
    # direction for bezier_point is the mean of both slopes
    direction = np.array([1, (slope1[1] + slope2[1]) / 2])
    direction = direction / np.linalg.norm(direction)
    bezier_point2 = bezier_point + bezier_width * direction

    # set up shift vector to calculate next tct params
    shift_vec = np.array([-lengths[1] + interdot_vec[0], interdot_vec[1] - lengths[1] * slope2[1]])

    # set up first tct params
    temp_params = np.array(
        [
            lengths[0],
            lengths[1],
            slope1[1],
            slope2[1],
            bezier_point[0],
            bezier_point[1],
            bezier_point2[0],
            bezier_point2[1],
        ]
    )

    # calculate next tct params
    tct_params = list()
    for i in range(num_tct):
        if i == 0:
            tct_params.append(temp_params.copy())
        else:
            tct_params = add_one_tct(tct_params=tct_params, shift_vec=shift_vec)

    return tct_params, shift_vec

Noise Samplers

Occupation Distortions

OccupationDotJumps

We do not sample this, because the DotJump implementation gets an expectation for the strength and length of the jumps and already varies around it. Therefore we use the original settings from default_configs["GaAs_v1"] for the OccupationDotJumps.

OccupationTransitionBlurringFermiDirac

This blurs the TCTs

occ_trans_blur_fermi_dirac_sigma_min = (
    0.25 * 0.03 / 100
) 
occ_trans_blur_fermi_dirac_sigma_max = 2 * 0.03 / 100 # changed min and max for random_variations_v2
# use 3*sigma distance from mean to borders
std_transition_blurring = (occ_trans_blur_fermi_dirac_sigma_max - occ_trans_blur_fermi_dirac_sigma_min) / 6
if only_debug_plots:
    sampler = NormalSamplingRange(
        total_range=(occ_trans_blur_fermi_dirac_sigma_min, occ_trans_blur_fermi_dirac_sigma_max),
        std=std_transition_blurring,
    ) # using other sampler for random_variations_v2
    samples = [sampler.sample_parameter() for i in range(10000)]
    print(f"Percentage of samples in the lowest 25% of the value range: {(np.count_nonzero(np.array(samples)<(occ_trans_blur_fermi_dirac_sigma_min + (0.25 * (occ_trans_blur_fermi_dirac_sigma_max - occ_trans_blur_fermi_dirac_sigma_min)))))/len(samples)*100}%")
    print(f"Percentage of samples in the highest 25% of the value range: {(np.count_nonzero(np.array(samples)>(occ_trans_blur_fermi_dirac_sigma_min + (0.75 * (occ_trans_blur_fermi_dirac_sigma_max - occ_trans_blur_fermi_dirac_sigma_min)))))/len(samples)*100}%")
    plt.hist(samples, bins=100, log=False)
    plt.xlabel("Sampled $\sigma{}$")
    plt.ylabel("Number of occurences")
    plt.show()
def sample_occupation_transition_blurring_fermi_dirac() -> OccupationTransitionBlurringFermiDirac:
    """Sample parameters and initialize an OccupationTransitionBlurringFermiDirac object. 
    
    Returns:
        Fully initialized OccupationTransitionBlurringFermiDirac object.
    """
    return OccupationTransitionBlurringFermiDirac(
        sigma=NormalSamplingRange(
            total_range=(occ_trans_blur_fermi_dirac_sigma_min, occ_trans_blur_fermi_dirac_sigma_max),
            std=std_transition_blurring,
        ) # using other sampler for random_variations_v2
    )

Sensor Potential Distortions

SensorPotentialPinkNoise
sensor_pot_pink_sigma_min, sensor_pot_pink_sigma_max = 1e-10, 0.0005 # reduced max for random_variations_v2
quantile_relative_pos = 0.6
quantile_percentage = 0.99
exp_scale_sensor_dot_pink = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
if only_debug_plots:
    sampler = ExponentialSamplingRange(
        total_range=(sensor_pot_pink_sigma_min, sensor_pot_pink_sigma_max),
        scale=exp_scale_sensor_dot_pink
    ) # using other sampler for random_variations_v2
    samples = [sampler.sample_parameter() for i in range(10000)]
    print(f"Percentage of samples in the lowest 25% of the value range: {(np.count_nonzero(np.array(samples)<(sensor_pot_pink_sigma_min + (0.25 * (sensor_pot_pink_sigma_max - sensor_pot_pink_sigma_min)))))/len(samples)*100}%")
    print(f"Percentage of samples in the highest 25% of the value range: {(np.count_nonzero(np.array(samples)>(sensor_pot_pink_sigma_min + (0.75 * (sensor_pot_pink_sigma_max - sensor_pot_pink_sigma_min)))))/len(samples)*100}%")
    plt.hist(samples, bins=100, log=False)
    plt.xlabel("Sampled $\sigma{}$")
    plt.ylabel("Number of occurences")
    plt.show()
def sample_sensor_pot_pink_noise() -> SensorPotentialPinkNoise:
    """Sample parameters and initialize an SensorPotentialPinkNoise object. 
    
    Returns:
        Fully initialized SensorPotentialPinkNoise object.
    """
    return SensorPotentialPinkNoise(
        sigma=ExponentialSamplingRange(
            total_range=(sensor_pot_pink_sigma_min, sensor_pot_pink_sigma_max),
            scale=exp_scale_sensor_dot_pink
            ) # using other sampler for random_variations_v2
    )
SensorPotentialRTN

We do not sample this, because the RTN implementation gets an expectation for the length and strength of the jumps and already varies around it. Therefore we use the original settings from default_configs["GaAs_v1"] for the SensorPotentialRTN.

Sensor Response Distortions

SensorResponseRTN

We do not sample this, because the RTN implementation gets an expectation and for the length and strength of the jumps and already varies around it. Therefore we use the original settings from default_configs["GaAs_v1"] for the SensorResponseRTN. We only reduced the ratio from 0.3 to 0.1.

SensorResponseWhiteNoise
sensor_res_white_sigma_min, sensor_res_white_sigma_max = 1e-10, 0.0005 # reduced max for random_variations_v2
quantile_relative_pos = 0.6
quantile_percentage = 0.99
exp_scale_res_white = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
if only_debug_plots:
    sampler = ExponentialSamplingRange(
        total_range=(sensor_res_white_sigma_min, sensor_res_white_sigma_max),
        scale=exp_scale_res_white
    ) # using other sampler for random_variations_v2
    samples = [sampler.sample_parameter() for i in range(10000)]
    print(f"Percentage of samples in the lowest 25% of the value range: {(np.count_nonzero(np.array(samples)<(sensor_res_white_sigma_min + (0.25 * (sensor_res_white_sigma_max - sensor_res_white_sigma_min)))))/len(samples)*100}%")
    print(f"Percentage of samples in the highest 25% of the value range: {(np.count_nonzero(np.array(samples)>(sensor_res_white_sigma_min + (0.75 * (sensor_res_white_sigma_max - sensor_res_white_sigma_min)))))/len(samples)*100}%")
    plt.hist(samples, bins=100, log=False)
    plt.xlabel("Sampled $\sigma{}$")
    plt.ylabel("Number of occurences")
    plt.show()
def sample_sensor_res_white_noise() -> SensorResponseWhiteNoise:
    """Sample parameters and initialize an SensorResponseWhiteNoise object. 
    
    Returns:
        Fully initialized SensorResponseWhiteNoise object.
    """
    return SensorResponseWhiteNoise(
        sigma=ExponentialSamplingRange(
            total_range=(sensor_res_white_sigma_min, sensor_res_white_sigma_max),
            scale=exp_scale_res_white
        ) # using other sampler for random_variations_v2
    )

Sensor Sampler

SensorGeneric with SensorPeakLorentzian

num_sensor_peak_min, num_sensor_peak_max = 3, 6
# s_off is the offset of the peaks (y)
sensor_offset_min, sensor_offset_max = -0.42275, -0.0838
# mu_0 (offset_mu_sens) from sensor (x)
sensor_mu_0_min, sensor_mu_0_max = -0.12168, -0.03824
sensor_height_min, sensor_height_max = 0.02245, 0.19204
sensor_gamma_min, sensor_gamma_max = 0.0009636, 0.0029509
sensor_alpha_gate_1_min, sensor_alpha_gate_1_max = 0.02805, 0.15093
sensor_alpha_gate_2_min, sensor_alpha_gate_2_max = 0.03788, 0.19491 # Currently not used, as we sample alpha gate 2 in dependence on alpha gate 1. Kept for the sake of completeness.
sensor_alpha_dot_1_min, sensor_alpha_dot_1_max = -0.0007994, -0.0000961
sensor_alpha_dot_2_min, sensor_alpha_dot_2_max = -0.0005214, -0.0000630

quantile_relative_pos = 0.6
quantile_percentage = 0.99
exp_scale_sensor_height = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
if only_debug_plots:
    sampler = ExponentialSamplingRange(
        total_range=(sensor_height_min, sensor_height_max),
        scale=exp_scale_sensor_height
    ) # using other sampler for random_variations_v2
    samples = [sampler.sample_parameter() for i in range(10000)]
    print(f"Percentage of samples in the lowest 25% of the value range: {(np.count_nonzero(np.array(samples)<(sensor_height_min + (0.25 * (sensor_height_max - sensor_height_min)))))/len(samples)*100}%")
    print(f"Percentage of samples in the highest 25% of the value range: {(np.count_nonzero(np.array(samples)>(sensor_height_min + (0.75 * (sensor_height_max - sensor_height_min)))))/len(samples)*100}%")
    plt.hist(samples, bins=100, log=False)
    plt.xlabel("Sampled sensor height")
    plt.ylabel("Number of occurences")
    plt.show()
def sample_sensor(config) -> SensorInterface:
    """Sample parameters and initialize a Sensor object. 
    
    Returns:
        Fully initialized Sensor object.
    """
    config = deepcopy(config)
    # sample alpha gates and alpha dots
    sensor_alpha_gate_1 = rng.uniform(low=sensor_alpha_gate_1_min, high=sensor_alpha_gate_1_max)
    sensor_alpha_gate_2 = rng.uniform(low=0.5 * sensor_alpha_gate_1, high=2 * sensor_alpha_gate_1)

    # sample alpha dot 1 depending on alpha gate 1
    # -0.03 / 100 = at least as much change for occ change (alpha dot) as change for sweeping one pixel (alpha gate)
    # keep in mind negative means, max is actually min (think of absolute values)
    sensor_alpha_dot_1_max = 2 * sensor_alpha_gate_1 * -0.03 / 100 # set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least 1* pixel step (for random_variations_v2)
    sensor_alpha_dot_1 = rng.uniform(low=sensor_alpha_dot_1_min, high=sensor_alpha_dot_1_max)

    # sample alpha dot 2 depending on alpha gate 2 and alpha dot 1
    sensor_alpha_dot_2_max = np.min([0.5 * sensor_alpha_dot_1, 2 * sensor_alpha_gate_2 * -0.03 / 100]) # set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least 1* pixel step (for random_variations_v2) 
    sensor_alpha_dot_2_min = 2 * sensor_alpha_dot_1
    sensor_alpha_dot_2 = rng.uniform(low=sensor_alpha_dot_2_min, high=sensor_alpha_dot_2_max)

    sensor = SensorGeneric(
        sensor_peak_function=None,
        alpha_dot=np.array([sensor_alpha_dot_1, sensor_alpha_dot_2]),
        alpha_gate=np.array([sensor_alpha_gate_1, sensor_alpha_gate_2]),
    )

    # calculate potential to set mu_sens
    config["sensor"] = sensor
    # set up simulation
    sim = Simulation(**config)
    # measure
    _, occupations, _, _ = sim.measure(volt_limits_g1, volt_limits_g2, resolution=np.array([10, 10]))
    # calculate potentials
    potentials = sensor.sensor_potential(occupations, volt_limits_g1, volt_limits_g2)
    sensor_mu_0_min, sensor_mu_0_max = np.min(potentials), np.max(potentials)

    # sample number of peaks
    num_sensor_peak = rng.choice(np.arange(num_sensor_peak_min, num_sensor_peak_max + 1))
    
    sensor_peak_functions = list()

    sensor_height_sampler = ExponentialSamplingRange(
        total_range=(sensor_height_min, sensor_height_max),
        scale=exp_scale_sensor_height
    ) # using other sampler for random_variations_v2
    # sample individual peaks
    for i in range(num_sensor_peak):
        sensor_gamma = rng.uniform(low=sensor_gamma_min, high=sensor_gamma_max)
        sensor_mu_0 = rng.uniform(low=sensor_mu_0_min, high=sensor_mu_0_max)
        if i > 0: # added a rule for the variation of mu0 between peaks of the same config (for random_variations_v2)
            # make sure that sensors have at least (0.5 * potential_range) / number_of_peaks distance (for mu0)
            counter = 0
            # introduced this counter, because we could end up in a case where it is not possible to place the next peak,
            # because two peaks can be slightly below twice min required distance apart from each other and therefore block 2x
            # and not just 1.5x (sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak). Try 1000 times and else allow 2x.
            while (np.min([np.abs(sensor_mu_0 - sampled_peak.mu0) for sampled_peak in sensor_peak_functions]) <= (sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak * 1.5) and counter < 1000) or np.min([np.abs(sensor_mu_0 - sampled_peak.mu0) for sampled_peak in sensor_peak_functions]) <= (sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak * 2):
                sensor_mu_0 = rng.uniform(low=sensor_mu_0_min, high=sensor_mu_0_max)
                counter +=1
        if i == 0: # added a rule for the variation of peak_offset between peaks of the same config (for random_variations_v2)
            sensor_peak_offset = rng.uniform(low=sensor_offset_min, high=sensor_offset_max)
        else:
            # make sure to have at most 20% difference in peak_offset to first peak_offset
            sensor_peak_offset = rng.uniform(low=sensor_peak_functions[0].offset*1.2, high=sensor_peak_functions[0].offset*0.8)
        if i == 0: # added a rule for the variation of height between peaks of the same config (for random_variations_v2)
            sensor_height = sensor_height_sampler.sample_parameter()
        else:
            # make sure to have at most 20% difference in height to first height
            sensor_height = rng.uniform(low=sensor_peak_functions[0].height*0.8, high=sensor_peak_functions[0].height*1.2)
        sensor_peak_functions.append(
            SensorPeakLorentzian(
                gamma=sensor_gamma,
                mu0=sensor_mu_0,
                height=sensor_height,
                offset=sensor_peak_offset,
            )
        )

    sensor.sensor_peak_function = sensor_peak_functions

    return sensor

Sample SimCATS Configs (Modify Default Config)

configs = list()

for i in tqdm(range(num_configs)):
    # fresh default config
    config = deepcopy(default_configs["GaAs_v1"])

    # basic settings regarding volt_limits and lookup table entries
    config["ideal_csd_config"].lut_entries = lut_entries
    config["volt_limits_g1"] = volt_limits_g1
    config["volt_limits_g2"] = volt_limits_g2

    # sample random tct params
    config["ideal_csd_config"].tct_params, shift_vec = sample_tct_params()
    # sample random occupation_distortions
    config["occupation_distortions"] = [
        sample_occupation_transition_blurring_fermi_dirac(),
        # in g2 (y in 2D) direction
        OccupationDotJumps(ratio=0.01, scale=100 * 0.03 / 100, lam=6 * 0.03 / 100, axis=0),
        # in g1 (x in 2D) direction
        OccupationDotJumps(ratio=0.01 / 6, scale=100 * 0.03 / 100, lam=6 * 0.03 / 100, axis=1),
    ]
    config["sensor_potential_distortions"] = [
        sample_sensor_pot_pink_noise(),
        SensorPotentialRTN(
            scale=74.56704 * 0.03 / 100,
            std=3.491734e-05,
            height=2.53855325e-05,
            ratio=1 / 6,
        ),
    ]
    config["sensor_response_distortions"] = [
        SensorResponseRTN(
            scale=10000 * 0.03 / 100,
            std=0.047453767599999995,
            height=0.0152373696,
            ratio=0.01,  # reduced to 0.01 from 0.03 (not required as often with lots of images)
        ),
        sample_sensor_res_white_noise(),
    ]

    # sample sensor, when config not possible: add new tct until possible
    running = True
    while running:
        try:
            # sample sensors, crashes if TCTs are missing
            config["sensor"] = sample_sensor(config)
        except IndexError as e:
            config["ideal_csd_config"].tct_params = add_one_tct(config["ideal_csd_config"].tct_params, shift_vec)
        else:
            running = False

    configs.append(config)

    if only_debug_plots:
        sim = Simulation(**config)
        for i in range(num_samples_per_config):
            v_1 = np.array([np.mean(volt_limits_g1), np.mean(volt_limits_g1) + volt_range[0]])
            v_2 = np.array([np.mean(volt_limits_g2), np.mean(volt_limits_g2) + volt_range[1]])
            csd_small, _, tct_small, metadata_small = sim.measure(v_1, v_2, resolution=resolution)
            csd, _, tct, metadata = sim.measure(volt_limits_g1, volt_limits_g2, resolution=np.array([300, 300]))
            if i == 0:
                plot_csd(tct_small, v_1, v_2, title=f"Config {len(configs)} TCT (small)")
                plot_csd(tct, volt_limits_g1, volt_limits_g2, title=f"Config {len(configs)} TCT (full)")
            plot_csd(csd_small, v_1, v_2, title=f"Config {len(configs)} CSD small {i}")
            plot_csd(csd, volt_limits_g1, volt_limits_g2, title=f"Config {len(configs)} CSD full {i}")
100%|██████████| 10/10 [00:00<00:00, 36.72it/s]

Create Dataset

# stop here if running whole notebook with only_debug_plots = True
if not only_debug_plots:
    for config in tqdm(configs):
        create_simulated_dataset(
            dataset_path=ds_path,
            simcats_config=config,
            n_runs=num_samples_per_config,
            resolution=resolution,
            volt_range=volt_range,
            num_workers=num_workers,
            progress_bar=False,
            dtype_csd=np.float32,
            dtype_tct=np.uint8,
            dtype_occ=np.float32,
            dtype_line_coordinates=np.float32,
        )
100%|██████████| 10/10 [00:20<00:00,  2.08s/it]

Examplary Plot Images from a Generated Dataset

if not only_debug_plots:
    specific_ids = np.random.choice(a=np.arange(num_configs*num_samples_per_config), size=3, replace=False)
    ds = load_dataset(file=ds_path, load_ids=True, load_tct_masks=True, load_metadata=True, specific_ids=specific_ids)
    for _id, csd, tct, metadata in zip(ds.ids, ds.csds, ds.tct_masks, ds.metadata):
        v_1 = metadata["sweep_range_g1"]
        v_2 = metadata["sweep_range_g2"]
        plot_csd(csd, v_1, v_2, title=f"CSD (id={_id})")
        plot_csd(tct, v_1, v_2, title=f"TCT mask (id={_id})")
../_images/84f1b72ae2fd4ed8b320c2b76ceac2d2801ab094c0d3e24eba11fd6d7564de4c.png ../_images/317e8c1061e2a4270fac01cfa2142d225fb8732e87a261545d74240b38b5000f.png ../_images/da5dbe7f1f3a909acc310bafbf91736b128f46f3d84f620ee6f0b66292044ff2.png ../_images/0f694de05afa5e6cbdcbc075b2a1ec95bf39db726957350fd4eecab67983f19e.png ../_images/02a28bb6505b7d2c6713fb4c8f4a78cae9a37ec89f80aa13c85468c186c57339.png ../_images/db2cd3f0a6c2e06e4099bb03aaf5e6a86d37250a99c9215b14704f14de7ea7c3.png