From 7bb95e670f2bb4cf1509709bae48187762a9abca Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 24 Feb 2026 20:52:06 -0600 Subject: [PATCH 01/10] Created abstract Synthesizer class with generate(), noise(), and abstract model() Co-Authored-By: Claude Sonnet 4.6 --- labcore/data/synthesizer.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 labcore/data/synthesizer.py diff --git a/labcore/data/synthesizer.py b/labcore/data/synthesizer.py new file mode 100644 index 0000000..3dc8b59 --- /dev/null +++ b/labcore/data/synthesizer.py @@ -0,0 +1,16 @@ + +import numpy as np + +from abc import ABC, abstractmethod + + +class Synthesizer(ABC): + @abstractmethod + def model(self, coordinates, *args, **kwargs): + pass + + def generate(self, coordinates): + return self.model(coordinates) + self.noise() + + def noise(self, std = 1.0): + return np.random.normal(scale = std) \ No newline at end of file From 83fbf38abf4e89d2ee561ff4f548ae80b014909e Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Wed, 25 Feb 2026 19:34:56 -0600 Subject: [PATCH 02/10] fixed parameter issues/multiple datasets being passed. added exponential, sine, and gaussian class implementations --- labcore/data/synthesizer.py | 56 ++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/labcore/data/synthesizer.py b/labcore/data/synthesizer.py index 3dc8b59..46727c1 100644 --- a/labcore/data/synthesizer.py +++ b/labcore/data/synthesizer.py @@ -3,14 +3,62 @@ from abc import ABC, abstractmethod +""" + +Implementation would work like this given x (n-dimensional array): + +sine() = SineSynthesizer() +model = sine.generate(x, noise_std = 0.5, A = 2, f = 3) + +------------- +These two lines would create a synthesizer representing a sine wave and generate models +for each set of data stored in x. + +Std for Gaussian distribution passed to noise() is 0.5 --> maybe add way to have multiple + distributions for different data sets? + +A, f are passed to SineSynthesizer's model() as kwargs + +Synthesizer's generate() then applies the noise to the model and returns an array with +the same dimension that was passed + +""" + class Synthesizer(ABC): @abstractmethod def model(self, coordinates, *args, **kwargs): pass - def generate(self, coordinates): - return self.model(coordinates) + self.noise() + def generate(self, coordinates, noise_std = 1.0, **model_kwargs): + + one_d = coordinates.ndim == 1 + coordinates = np.atleast_2d(coordinates) + model_outputs = np.array([self.model(coords, **model_kwargs) + self.noise(noise_std) + for coords in coordinates]) + if (one_d): + return model_outputs.squeeze() + + return model_outputs - def noise(self, std = 1.0): - return np.random.normal(scale = std) \ No newline at end of file + def noise(self, std): + return np.random.normal(scale = std) + + +class ExponentialSynthesizer(Synthesizer): + + def model(self, coordinates, base = np.e): + return base ** coordinates + + + +class SineSynthesizer(Synthesizer): + + def model(self, coordinates, A = 1, f = 1, phi = 0, of = 0): + return A * np.sin(2 * np.pi * coordinates * f + phi) + of + + +class GaussianSynthesizer(Synthesizer): + + def model(self, coordinates, x0 = 0, sigma = 1, A = 1, of = 0): + return A * np.exp(-((coordinates - x0) ** 2) / (2 * sigma ** 2)) + of \ No newline at end of file From f4d11a059ad71ad70f0856fb56bf7392b0134c17 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Sat, 28 Feb 2026 01:47:14 -0600 Subject: [PATCH 03/10] Refactor DataGen classes from ABC to dataclasses; model() and noise() are now static methods. Parameters can be set at instantiation or overridden in generate(). --- labcore/data/datagen.py | 103 ++++++++++++++++++++++++++++++++++++ labcore/data/synthesizer.py | 64 ---------------------- 2 files changed, 103 insertions(+), 64 deletions(-) create mode 100644 labcore/data/datagen.py delete mode 100644 labcore/data/synthesizer.py diff --git a/labcore/data/datagen.py b/labcore/data/datagen.py new file mode 100644 index 0000000..35f76a0 --- /dev/null +++ b/labcore/data/datagen.py @@ -0,0 +1,103 @@ + +import numpy as np + +import dataclasses + +""" + +Implementation would work like this given x (n-dimensional array): + +sine() = SineDataGen() +model = sine.generate(x, noise_std = 0.5, A = 2, f = 3) + +------------- +These two lines would create a synthesizer representing a sine wave and generate models +for each set of data stored in x. + +Std for Gaussian distribution passed to noise() is 0.5 --> maybe add way to have multiple + distributions for different data sets? + +A, f are passed to SineDataGen's model() as kwargs + +DataGen's generate() then applies the noise to the model and returns an array with +the same dimension that was passed + +""" + +""" +generate should now work such that the following can be done: + +x = [np array of coordinates] +sine = SineDataGen(A = 2, f = 3) + +coords = sine.generate() --> uses A = 2, f = 3 +coords = sine.generate(A = 5) --> uses A = 5, f = 2 + +""" + +@dataclasses.dataclass +class DataGen: + noise_std : float = 1.0 + + @staticmethod + def model(coordinates, *args, **kwargs): + pass + + def generate(self, coordinates, **kwargs): + + # updates previously set dataclass fields + # coords = + params = dataclasses.asdict(self) + params.update(kwargs) + noise_std = params.pop('noise_std') + + one_d = coordinates.ndim == 1 + coordinates = np.atleast_2d(coordinates) + model_outputs = np.array([self.model(coords, **params) + + self.noise(coords, noise_std) + for coords in coordinates]) + if (one_d): + return model_outputs.squeeze() + + return model_outputs + + @staticmethod + def noise(coordinates, std): + return np.random.normal(scale = std, size = len(coordinates)) + + +@dataclasses.dataclass +class ExponentialDataGen(DataGen): + + base: float = np.e + + @staticmethod + def model(coordinates, base = np.e): + return base ** coordinates + + + +@dataclasses.dataclass +class SineDataGen(DataGen): + + A : float = 1 + f : float = 1 + phi : float = 0 + of : float = 0 + + @staticmethod + def model(coordinates, A = 1, f = 1, phi = 0, of = 0): + return A * np.sin(2 * np.pi * coordinates * f + phi) + of + + +@dataclasses.dataclass +class GaussianDataGen(DataGen): + + x0 : float = 0 + sigma : float = 1 + A : float = 1 + of : float = 0 + + @staticmethod + def model(coordinates, x0 = 0, sigma = 1, A = 1, of = 0): + return A * np.exp(-((coordinates - x0) ** 2) / (2 * sigma ** 2)) + of \ No newline at end of file diff --git a/labcore/data/synthesizer.py b/labcore/data/synthesizer.py deleted file mode 100644 index 46727c1..0000000 --- a/labcore/data/synthesizer.py +++ /dev/null @@ -1,64 +0,0 @@ - -import numpy as np - -from abc import ABC, abstractmethod - -""" - -Implementation would work like this given x (n-dimensional array): - -sine() = SineSynthesizer() -model = sine.generate(x, noise_std = 0.5, A = 2, f = 3) - -------------- -These two lines would create a synthesizer representing a sine wave and generate models -for each set of data stored in x. - -Std for Gaussian distribution passed to noise() is 0.5 --> maybe add way to have multiple - distributions for different data sets? - -A, f are passed to SineSynthesizer's model() as kwargs - -Synthesizer's generate() then applies the noise to the model and returns an array with -the same dimension that was passed - -""" - - -class Synthesizer(ABC): - @abstractmethod - def model(self, coordinates, *args, **kwargs): - pass - - def generate(self, coordinates, noise_std = 1.0, **model_kwargs): - - one_d = coordinates.ndim == 1 - coordinates = np.atleast_2d(coordinates) - model_outputs = np.array([self.model(coords, **model_kwargs) + self.noise(noise_std) - for coords in coordinates]) - if (one_d): - return model_outputs.squeeze() - - return model_outputs - - def noise(self, std): - return np.random.normal(scale = std) - - -class ExponentialSynthesizer(Synthesizer): - - def model(self, coordinates, base = np.e): - return base ** coordinates - - - -class SineSynthesizer(Synthesizer): - - def model(self, coordinates, A = 1, f = 1, phi = 0, of = 0): - return A * np.sin(2 * np.pi * coordinates * f + phi) + of - - -class GaussianSynthesizer(Synthesizer): - - def model(self, coordinates, x0 = 0, sigma = 1, A = 1, of = 0): - return A * np.exp(-((coordinates - x0) ** 2) / (2 * sigma ** 2)) + of \ No newline at end of file From 73999136165c02e4322713095fb7b47f7f5eb991 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 3 Mar 2026 23:53:39 -0600 Subject: [PATCH 04/10] switch datagen back to abstract and removed default values from child class model() signatures --- labcore/data/datagen.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/labcore/data/datagen.py b/labcore/data/datagen.py index 35f76a0..27f3717 100644 --- a/labcore/data/datagen.py +++ b/labcore/data/datagen.py @@ -2,6 +2,7 @@ import numpy as np import dataclasses +from abc import ABC, abstractmethod """ @@ -36,17 +37,16 @@ """ @dataclasses.dataclass -class DataGen: +class DataGen(ABC): noise_std : float = 1.0 - @staticmethod + @abstractmethod def model(coordinates, *args, **kwargs): pass - def generate(self, coordinates, **kwargs): - + def generate(self, coordinates, **kwargs): + # updates previously set dataclass fields - # coords = params = dataclasses.asdict(self) params.update(kwargs) noise_std = params.pop('noise_std') @@ -72,7 +72,7 @@ class ExponentialDataGen(DataGen): base: float = np.e @staticmethod - def model(coordinates, base = np.e): + def model(coordinates, base): return base ** coordinates @@ -86,7 +86,7 @@ class SineDataGen(DataGen): of : float = 0 @staticmethod - def model(coordinates, A = 1, f = 1, phi = 0, of = 0): + def model(coordinates, A, f, phi, of): return A * np.sin(2 * np.pi * coordinates * f + phi) + of @@ -99,5 +99,5 @@ class GaussianDataGen(DataGen): of : float = 0 @staticmethod - def model(coordinates, x0 = 0, sigma = 1, A = 1, of = 0): + def model(coordinates, x0, sigma, A, of): return A * np.exp(-((coordinates - x0) ** 2) / (2 * sigma ** 2)) + of \ No newline at end of file From 457df29ebb078ad8cf7b959a22b23c7dbc0088b1 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Thu, 5 Mar 2026 15:22:14 -0600 Subject: [PATCH 05/10] changed dataclass import :) --- labcore/data/datagen.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/labcore/data/datagen.py b/labcore/data/datagen.py index 27f3717..4e3dd79 100644 --- a/labcore/data/datagen.py +++ b/labcore/data/datagen.py @@ -1,7 +1,7 @@ import numpy as np -import dataclasses +from dataclasses import dataclass, asdict from abc import ABC, abstractmethod """ @@ -36,7 +36,7 @@ """ -@dataclasses.dataclass +@dataclass class DataGen(ABC): noise_std : float = 1.0 @@ -47,7 +47,7 @@ def model(coordinates, *args, **kwargs): def generate(self, coordinates, **kwargs): # updates previously set dataclass fields - params = dataclasses.asdict(self) + params = asdict(self) params.update(kwargs) noise_std = params.pop('noise_std') @@ -66,7 +66,7 @@ def noise(coordinates, std): return np.random.normal(scale = std, size = len(coordinates)) -@dataclasses.dataclass +@dataclass class ExponentialDataGen(DataGen): base: float = np.e @@ -77,7 +77,7 @@ def model(coordinates, base): -@dataclasses.dataclass +@dataclass class SineDataGen(DataGen): A : float = 1 @@ -90,7 +90,7 @@ def model(coordinates, A, f, phi, of): return A * np.sin(2 * np.pi * coordinates * f + phi) + of -@dataclasses.dataclass +@dataclass class GaussianDataGen(DataGen): x0 : float = 0 From 2d9328a699ab6f4c007feb82a71280e0fe4a8ec4 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 7 Apr 2026 21:10:16 -0500 Subject: [PATCH 06/10] update: brought datagen.py up to new codebase standards and added new classes. changed naming style to exclude 'DataGen' --- src/labcore/data/datagen.py | 73 ++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/labcore/data/datagen.py b/src/labcore/data/datagen.py index d5e0573..b2f92e3 100644 --- a/src/labcore/data/datagen.py +++ b/src/labcore/data/datagen.py @@ -69,7 +69,8 @@ def generate(self, coordinates: NDArray[Any], **kwargs: Any) -> NDArray[Any]: @staticmethod def noise(coordinates: NDArray[Any], std: float) -> NDArray[Any]: - return np.random.normal(scale=std, size=len(coordinates)) + n = len(coordinates) + return np.random.normal(scale=std, size=n) + 1j * np.random.normal(scale=std, size=n) @dataclass @@ -107,3 +108,73 @@ def model( coordinates: NDArray[Any], x0: float, sigma: float, A: float, of: float ) -> NDArray[Any]: return A * np.exp(-((coordinates - x0) ** 2) / (2 * sigma**2)) + of + + +@dataclass +class ExponentialDecay(DataGen): + A: float = 1 + tau: float = 1 + of: float = 0 + + @staticmethod + def model( + coordinates: NDArray[Any], A: float, tau: float, of: float + ) -> NDArray[Any]: + return A * np.exp(-coordinates / tau) + of + + +@dataclass +class ExponentialDecayingSine(DataGen): + A: float = 1 + f: float = 1 + phi: float = 0 + tau: float = 1 + of: float = 0 + + @staticmethod + def model( + coordinates: NDArray[Any], A: float, f: float, phi: float, tau: float, of: float + ) -> NDArray[Any]: + return A * np.exp(-coordinates / tau) * np.sin(2 * np.pi * f * coordinates + phi) + of + + +@dataclass +class Lorentzian(DataGen): + A: float = 1 + x0: float = 0 + gamma: float = 1 + of: float = 0 + + @staticmethod + def model( + coordinates: NDArray[Any], A: float, x0: float, gamma: float, of: float + ) -> NDArray[Any]: + return A * (gamma**2) / ((coordinates - x0) ** 2 + gamma**2) + of + + +@dataclass +class HangerResonator(DataGen): + A: float = 1 + Qc: float = 1000 + Qi: float = 1000 + f0: float = 1e9 + phi: float = 0 + + @staticmethod + def model( + coordinates: NDArray[Any], A: float, Qc: float, Qi: float, f0: float, phi: float + ) -> NDArray[Any]: + Q_l = 1.0 / (1.0 / Qc + 1.0 / Qi) + Q_e_complex = Qc * np.exp(-1j * phi) + return A * (1 - (Q_l / Q_e_complex) / (1 + 2j * Q_l * (coordinates - f0) / f0)) + + +@dataclass +class PowerRabi(DataGen): + pi_amp: float = 1 + + @staticmethod + def model(coordinates: NDArray[Any], pi_amp: float) -> NDArray[Any]: + val = np.cos(2 * np.pi * coordinates / (2 * pi_amp)) + 2 + return val - 1j * val + From 31291582ba3950da9238e1825f9e96c07565a934 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 7 Apr 2026 21:24:20 -0500 Subject: [PATCH 07/10] fixing ruff errors --- src/labcore/data/datagen.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/labcore/data/datagen.py b/src/labcore/data/datagen.py index b2f92e3..b98ddd4 100644 --- a/src/labcore/data/datagen.py +++ b/src/labcore/data/datagen.py @@ -70,7 +70,9 @@ def generate(self, coordinates: NDArray[Any], **kwargs: Any) -> NDArray[Any]: @staticmethod def noise(coordinates: NDArray[Any], std: float) -> NDArray[Any]: n = len(coordinates) - return np.random.normal(scale=std, size=n) + 1j * np.random.normal(scale=std, size=n) + return np.random.normal(scale=std, size=n) + 1j * np.random.normal( + scale=std, size=n + ) @dataclass @@ -135,7 +137,10 @@ class ExponentialDecayingSine(DataGen): def model( coordinates: NDArray[Any], A: float, f: float, phi: float, tau: float, of: float ) -> NDArray[Any]: - return A * np.exp(-coordinates / tau) * np.sin(2 * np.pi * f * coordinates + phi) + of + return ( + A * np.exp(-coordinates / tau) * np.sin(2 * np.pi * f * coordinates + phi) + + of + ) @dataclass @@ -177,4 +182,3 @@ class PowerRabi(DataGen): def model(coordinates: NDArray[Any], pi_amp: float) -> NDArray[Any]: val = np.cos(2 * np.pi * coordinates / (2 * pi_amp)) + 2 return val - 1j * val - From 0fa25912b5c4d839af0bf192e766b5806db5bc92 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 7 Apr 2026 21:56:07 -0500 Subject: [PATCH 08/10] naming inconsistencies --- src/labcore/data/datagen.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/labcore/data/datagen.py b/src/labcore/data/datagen.py index b98ddd4..b8e7307 100644 --- a/src/labcore/data/datagen.py +++ b/src/labcore/data/datagen.py @@ -76,7 +76,7 @@ def noise(coordinates: NDArray[Any], std: float) -> NDArray[Any]: @dataclass -class ExponentialDataGen(DataGen): +class Exponential(DataGen): base: float = np.e @staticmethod @@ -85,7 +85,7 @@ def model(coordinates: NDArray[Any], base: float) -> NDArray[Any]: @dataclass -class SineDataGen(DataGen): +class Sine(DataGen): A: float = 1 f: float = 1 phi: float = 0 @@ -99,7 +99,7 @@ def model( @dataclass -class GaussianDataGen(DataGen): +class Gaussian(DataGen): x0: float = 0 sigma: float = 1 A: float = 1 From ef013f3c7b3fec72e2335d6772691ea194e9e096 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Tue, 14 Apr 2026 19:30:28 -0500 Subject: [PATCH 09/10] moved non generic functions to cqedtoolbox. changed how noise is applied: only real noise is applied to functions that just have real components. --- src/labcore/data/datagen.py | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/labcore/data/datagen.py b/src/labcore/data/datagen.py index b8e7307..b35f97e 100644 --- a/src/labcore/data/datagen.py +++ b/src/labcore/data/datagen.py @@ -41,6 +41,7 @@ @dataclass class DataGen(ABC): noise_std: float = 1.0 + imaginary: bool = False @staticmethod @abstractmethod @@ -53,12 +54,15 @@ def generate(self, coordinates: NDArray[Any], **kwargs: Any) -> NDArray[Any]: params = asdict(self) params.update(kwargs) noise_std = params.pop("noise_std") + imaginary = params.pop("imaginary") one_d = coordinates.ndim == 1 coordinates = np.atleast_2d(coordinates) model_outputs = np.array( [ - self.model(coords, **params) + self.noise(coords, noise_std) + self.model(coords, **params) + + self.noise(coords, noise_std) + + (1j * self.noise(coords, noise_std) if imaginary else 0) for coords in coordinates ] ) @@ -70,9 +74,7 @@ def generate(self, coordinates: NDArray[Any], **kwargs: Any) -> NDArray[Any]: @staticmethod def noise(coordinates: NDArray[Any], std: float) -> NDArray[Any]: n = len(coordinates) - return np.random.normal(scale=std, size=n) + 1j * np.random.normal( - scale=std, size=n - ) + return np.random.normal(scale=std, size=n) @dataclass @@ -157,28 +159,14 @@ def model( return A * (gamma**2) / ((coordinates - x0) ** 2 + gamma**2) + of -@dataclass -class HangerResonator(DataGen): - A: float = 1 - Qc: float = 1000 - Qi: float = 1000 - f0: float = 1e9 - phi: float = 0 - - @staticmethod - def model( - coordinates: NDArray[Any], A: float, Qc: float, Qi: float, f0: float, phi: float - ) -> NDArray[Any]: - Q_l = 1.0 / (1.0 / Qc + 1.0 / Qi) - Q_e_complex = Qc * np.exp(-1j * phi) - return A * (1 - (Q_l / Q_e_complex) / (1 + 2j * Q_l * (coordinates - f0) / f0)) - - @dataclass class PowerRabi(DataGen): + A: float = 1 pi_amp: float = 1 + of: float = 0 + imaginary: bool = True @staticmethod - def model(coordinates: NDArray[Any], pi_amp: float) -> NDArray[Any]: - val = np.cos(2 * np.pi * coordinates / (2 * pi_amp)) + 2 + def model(coordinates: NDArray[Any], A: float, pi_amp: float, of: float) -> NDArray[Any]: + val = A * np.cos(2 * np.pi * coordinates / (2 * pi_amp)) + of return val - 1j * val From 0376147bed14d7b28a45fbe110b521a89ad72976 Mon Sep 17 00:00:00 2001 From: olivers3uiuc Date: Thu, 23 Apr 2026 22:26:06 -0500 Subject: [PATCH 10/10] progress commit --- src/labcore/data/datagen.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/labcore/data/datagen.py b/src/labcore/data/datagen.py index b35f97e..0c7eedd 100644 --- a/src/labcore/data/datagen.py +++ b/src/labcore/data/datagen.py @@ -157,16 +157,3 @@ def model( coordinates: NDArray[Any], A: float, x0: float, gamma: float, of: float ) -> NDArray[Any]: return A * (gamma**2) / ((coordinates - x0) ** 2 + gamma**2) + of - - -@dataclass -class PowerRabi(DataGen): - A: float = 1 - pi_amp: float = 1 - of: float = 0 - imaginary: bool = True - - @staticmethod - def model(coordinates: NDArray[Any], A: float, pi_amp: float, of: float) -> NDArray[Any]: - val = A * np.cos(2 * np.pi * coordinates / (2 * pi_amp)) + of - return val - 1j * val