From a20b7ec9cbaa65eef8d5aee44ddb7317b2b42217 Mon Sep 17 00:00:00 2001 From: jimnel Date: Mon, 7 Apr 2025 14:16:35 +0100 Subject: [PATCH 1/9] refactor --- qse/calc/__init__.py | 109 +---------------------------------------- qse/calc/abc.py | 65 ------------------------ qse/calc/calculator.py | 41 ++-------------- qse/calc/signal.py | 94 +++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 211 deletions(-) delete mode 100644 qse/calc/abc.py create mode 100644 qse/calc/signal.py diff --git a/qse/calc/__init__.py b/qse/calc/__init__.py index 29cd5c5..28c247b 100644 --- a/qse/calc/__init__.py +++ b/qse/calc/__init__.py @@ -1,116 +1,9 @@ """Interface to different QSE calculators.""" __all__ = ["abc", "calculator", "signal"] -from typing import Union -import qse.calc.abc import qse.calc.calculator - -np = qse.np - - -class signal(object): - """ - signal class to represent a a signal with a duration. - It has two components: values, and duration. - Instantiation of this class supports '+' in two ways. - If w1, w2 are instantiation of signal, then - - w1 + 3 gives a signal with values, w1.values + 3 and - same duration. - - w = w1 + w2 gives signal with concatenated values, i.e., - w.values = [w1.values, w2.values], and added duration. - w.duration = w1.duration + w2.duration - - Note: Currently, the object gets created for multi-dim - arrays as well. However, it should be used for 1D only, - we haven't made it useful or consistent for multi-dim usage. - """ - - # - # __slots__ = ('duration', 'values') - def __init__(self, values, duration=None) -> None: - self.values = np.asarray(values) - self._duration = len(self.values) if duration is None else int(duration) - - # - @property - def duration(self): - """time duration of signal""" - return self._duration - - @duration.setter - def duration(self, value): - self._duration = value - - def __iter__(self): - return iter(self.values) - - # - def __getitem__(self, i): - return self.values[i] - - # - def __eq__(self, other) -> bool: - return (self.duration == other.duration) and (self.values == other.values).all() - - # - def __add__(self, other): - if isinstance(other, signal): - res = signal( - values=np.append(self.values, other.values), - duration=self.duration + other.duration, - ) - else: - if isinstance(other, Union[float, int]): - res = signal(values=self.values + other, duration=self.duration) - else: - raise TypeError(f"wrong type for operand {type(other)}") - return res - - # - def __radd__(self, other): - return self.__add__(other) - - # - def __iadd__(self, other): - if isinstance(other, signal): - self.values = np.append(self.values, other.values) - self.duration += other.duration - else: - if isinstance(other, Union[float, int]): - self.values += other - else: - raise TypeError(f"wrong type for operand {type(other)}") - return self - - # - def __mul__(self, other): - if isinstance(other, Union[float, int]): - res = signal(values=self.values * other, duration=self.duration) - else: - raise TypeError(f"wrong type for operand {type(other)}") - return res - - # - def __rmul__(self, other): - return self.__mul__(other) - - # - def __imul__(self, other): - if isinstance(other, Union[float, int]): - self.values *= other - else: - raise TypeError(f"wrong type for operand {type(other)}") - return self - - # - def __repr__(self) -> str: - return f"signal(duration={self.duration}, values={self.values})" - - # we need to define interpolating scheme to resample points if duration is changed externally. - - -# +from qse.calc.signal import Signal from .myqlm import Myqlm from .pulser import Pulser diff --git a/qse/calc/abc.py b/qse/calc/abc.py deleted file mode 100644 index 0341438..0000000 --- a/qse/calc/abc.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -This module defines abstract helper classes with the objective of reducing -boilerplace method definitions (i.e. duplication) in calculators. -""" - -from abc import ABC, abstractmethod -from typing import Any, Mapping - - -class GetPropertiesMixin(ABC): - """Mixin class which provides get_forces(), get_stress() and so on. - - Inheriting class must implement get_property().""" - - @abstractmethod - def get_property(self, name, qbits=None, allow_calculation=True): - """Get the named property.""" - - # def get_energies(self, qbits=None): - # return self.get_property('energies', qbits) - - # def get_moment(self, qbits=None): - # return self.get_property('magmom', qbits) - - -""" class GetOutputsMixin(ABC): - #Mixin class for providing get_fermi_level() and others. - # - #Effectively this class expresses data in calc.results as - #methods such as get_fermi_level(). - # - #Inheriting class must implement _outputmixin_get_results(), - #typically returning self.results, which must be a mapping - #using the naming defined in ase.outputs.Properties. - - @abstractmethod - def _outputmixin_get_results(self) -> Mapping[str, Any]: - #Return Mapping of names to result value. - #This may be called many times and should hence not be - #expensive (except possibly the first time). - - def _get(self, name): - # Cyclic import, should restructure. - from ase.calculators.calculator import PropertyNotPresent - dct = self._outputmixin_get_results() - try: - return dct[name] - except KeyError: - raise PropertyNotPresent(name) - - def get_fermi_level(self): - return self._get('fermi_level') - - def get_eigenvalues(self, kpt=0, spin=0): - eigs = self._get('eigenvalues') - return eigs[kpt, spin] - - def _eigshape(self): - # We don't need this if we already have a Properties object. - return self._get('eigenvalues').shape - - def get_occupation_numbers(self, kpt=0, spin=0): - occs = self._get('occupations') - return occs[kpt, spin] - """ diff --git a/qse/calc/calculator.py b/qse/calc/calculator.py index bf0aac3..d70c8ef 100644 --- a/qse/calc/calculator.py +++ b/qse/calc/calculator.py @@ -3,15 +3,10 @@ import pathlib import subprocess import warnings -from math import pi, sqrt from typing import Any, Dict, List, Optional, Set, Union import numpy as np -from ase.cell import Cell from ase.outputs import Properties, all_outputs -from ase.utils import jsonable - -from qse.calc.abc import GetPropertiesMixin class CalculatorError(RuntimeError): @@ -192,37 +187,6 @@ def equal(a, b, tol=None, rtol=None, atol=None): return np.allclose(a, b, rtol=rtol, atol=atol) -# def kptdensity2monkhorstpack(atoms, kptdensity=3.5, even=True): """Convert k-point density to Monkhorst-Pack grid size. - -""" -class EigenvalOccupationMixin: - #Define 'eigenvalues' and 'occupations' properties on class. - # - #eigenvalues and occupations will be arrays of shape (spin, kpts, nbands). - # - #Classes must implement the old-fashioned get_eigenvalues and - #get_occupations methods. - - @property - def eigenvalues(self): - return self.build_eig_occ_array(self.get_eigenvalues) - - @property - def occupations(self): - return self.build_eig_occ_array(self.get_occupation_numbers) - - def build_eig_occ_array(self, getter): - nspins = self.get_number_of_spins() - nkpts = len(self.get_ibz_k_points()) - nbands = self.get_number_of_bands() - arr = np.zeros((nspins, nkpts, nbands)) - for s in range(nspins): - for k in range(nkpts): - arr[s, k, :] = getter(spin=s, kpt=k) - return arr -""" - - class Parameters(dict): """Dictionary for parameters. @@ -278,8 +242,9 @@ def write(self, filename): pathlib.Path(filename).write_text(self.tostring()) -class Calculator(GetPropertiesMixin): - """Base-class for all QSE calculators, adapted from ASE calculators. +class Calculator: + """ + Base-class for all QSE calculators, adapted from ASE calculators. A calculator must raise PropertyNotImplementedError if asked for a property that it can't calculate. So, if calculation of the diff --git a/qse/calc/signal.py b/qse/calc/signal.py new file mode 100644 index 0000000..57b9943 --- /dev/null +++ b/qse/calc/signal.py @@ -0,0 +1,94 @@ +"""The Signal class.""" + +from typing import Union + +import numpy as np + + +class Signal(object): + """ + Signal class to represent a a signal with a duration. + It has two components: values, and duration. + Instantiation of this class supports '+' in two ways. + If w1, w2 are instantiation of signal, then + - w1 + 3 gives a signal with values, w1.values + 3 and + same duration. + - w = w1 + w2 gives signal with concatenated values, i.e., + w.values = [w1.values, w2.values], and added duration. + w.duration = w1.duration + w2.duration + + Note: Currently, the object gets created for multi-dim + arrays as well. However, it should be used for 1D only, + we haven't made it useful or consistent for multi-dim usage. + """ + + def __init__(self, values, duration=None) -> None: + self.values = np.asarray(values) + self._duration = len(self.values) if duration is None else int(duration) + + @property + def duration(self): + """time duration of signal""" + return self._duration + + @duration.setter + def duration(self, value): + self._duration = value + + def __iter__(self): + return iter(self.values) + + def __getitem__(self, i): + return self.values[i] + + def __eq__(self, other) -> bool: + return (self.duration == other.duration) and (self.values == other.values).all() + + def __add__(self, other): + if isinstance(other, Signal): + res = Signal( + values=np.append(self.values, other.values), + duration=self.duration + other.duration, + ) + else: + if isinstance(other, Union[float, int]): + res = Signal(values=self.values + other, duration=self.duration) + else: + raise TypeError(f"wrong type for operand {type(other)}") + return res + + def __radd__(self, other): + return self.__add__(other) + + def __iadd__(self, other): + if isinstance(other, Signal): + self.values = np.append(self.values, other.values) + self.duration += other.duration + else: + if isinstance(other, Union[float, int]): + self.values += other + else: + raise TypeError(f"wrong type for operand {type(other)}") + return self + + def __mul__(self, other): + if isinstance(other, Union[float, int]): + res = Signal(values=self.values * other, duration=self.duration) + else: + raise TypeError(f"wrong type for operand {type(other)}") + return res + + def __rmul__(self, other): + return self.__mul__(other) + + def __imul__(self, other): + if isinstance(other, Union[float, int]): + self.values *= other + else: + raise TypeError(f"wrong type for operand {type(other)}") + return self + + def __repr__(self) -> str: + return f"signal(duration={self.duration}, values={self.values})" + + # we need to define interpolating scheme to resample points if duration is changed externally. From d977d75472879746056c73f60ae1d14bc132e75f Mon Sep 17 00:00:00 2001 From: James Nelson Date: Mon, 7 Apr 2025 15:00:33 +0100 Subject: [PATCH 2/9] Update qse/calc/signal.py --- qse/calc/signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qse/calc/signal.py b/qse/calc/signal.py index 57b9943..b51679d 100644 --- a/qse/calc/signal.py +++ b/qse/calc/signal.py @@ -1,4 +1,4 @@ -"""The Signal class.""" +"""Definition of the Signal class.""" from typing import Union From 0c47e78d167743b6dbf419392c06da09c15a71d9 Mon Sep 17 00:00:00 2001 From: jimnel Date: Mon, 7 Apr 2025 16:39:09 +0100 Subject: [PATCH 3/9] create messages.py --- qse/calc/calculator.py | 59 ++------------------------------------ qse/calc/messages.py | 65 ++++++++++++++++++++++++++++++++++++++++++ qse/calc/myqlm.py | 2 +- qse/calc/pulser.py | 14 ++------- 4 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 qse/calc/messages.py diff --git a/qse/calc/calculator.py b/qse/calc/calculator.py index d70c8ef..4adfbb6 100644 --- a/qse/calc/calculator.py +++ b/qse/calc/calculator.py @@ -3,66 +3,11 @@ import pathlib import subprocess import warnings -from typing import Any, Dict, List, Optional, Set, Union +from typing import Any, Dict, List, Optional, Set import numpy as np from ase.outputs import Properties, all_outputs - - -class CalculatorError(RuntimeError): - """Base class of error types related to ASE calculators.""" - - -class CalculatorSetupError(CalculatorError): - """Calculation cannot be performed with the given parameters. - - Reasons to raise this errors are: - * The calculator is not properly configured - (missing executable, environment variables, ...) - * The given qbits object is not supported - * Calculator parameters are unsupported - - Typically raised before a calculation.""" - - -class EnvironmentError(CalculatorSetupError): - """Raised if calculator is not properly set up with ASE. - May be missing an executable or environment variables.""" - - -class InputError(CalculatorSetupError): - """Raised if inputs given to the calculator were incorrect. - Bad input keywords or values, or missing pseudopotentials. - This may be raised before or during calculation, depending on - when the problem is detected.""" - - -class CalculationFailed(CalculatorError): - """Calculation failed unexpectedly. - - Reasons to raise this error are: - * Calculation did not converge - * Calculation ran out of memory - * Segmentation fault or other abnormal termination - * Arithmetic trouble (singular matrices, NaN, ...) - - Typically raised during calculation.""" - - -class ReadError(CalculatorError): - """Unexpected irrecoverable error while reading calculation results.""" - - -class PropertyNotImplementedError(NotImplementedError): - """Raised if a calculator does not implement the requested property.""" - - -class PropertyNotPresent(CalculatorError): - """Requested property is missing. - - Maybe it was never calculated, or for some reason was not extracted - with the rest of the results, without being a fatal ReadError.""" - +from qse.calc.messages import PropertyNotImplementedError, PropertyNotPresent, CalculatorSetupError, CalculationFailed def compare_qbits(qbits1, qbits2, tol=1e-15, excluded_properties=None): """Check for system changes since last calculation. Properties in diff --git a/qse/calc/messages.py b/qse/calc/messages.py new file mode 100644 index 0000000..8dd5861 --- /dev/null +++ b/qse/calc/messages.py @@ -0,0 +1,65 @@ +"""Messages that are thrown when using the calculator.""" + +class CalculatorError(RuntimeError): + """Base class of error types related to QSE calculators.""" + + +class CalculatorSetupError(CalculatorError): + """ + Calculation cannot be performed with the given parameters. + + Reasons to raise this errors are: + * The calculator is not properly configured + (missing executable, environment variables, ...) + * The given qbits object is not supported + * Calculator parameters are unsupported + + Typically raised before a calculation. + """ + + +class EnvironmentError(CalculatorSetupError): + """ + Raised if calculator is not properly set up with QSE. + May be missing an executable or environment variables. + """ + + +class InputError(CalculatorSetupError): + """ + Raised if inputs given to the calculator were incorrect. + Bad input keywords or values, or missing pseudopotentials. + This may be raised before or during calculation, depending on + when the problem is detected. + """ + + +class CalculationFailed(CalculatorError): + """ + Calculation failed unexpectedly. + + Reasons to raise this error are: + * Calculation did not converge + * Calculation ran out of memory + * Segmentation fault or other abnormal termination + * Arithmetic trouble (singular matrices, NaN, ...) + + Typically raised during calculation. + """ + + +class ReadError(CalculatorError): + """Unexpected irrecoverable error while reading calculation results.""" + + +class PropertyNotImplementedError(NotImplementedError): + """Raised if a calculator does not implement the requested property.""" + + +class PropertyNotPresent(CalculatorError): + """ + Requested property is missing. + + Maybe it was never calculated, or for some reason was not extracted + with the rest of the results, without being a fatal ReadError. + """ diff --git a/qse/calc/myqlm.py b/qse/calc/myqlm.py index 61855ed..c3d72cd 100644 --- a/qse/calc/myqlm.py +++ b/qse/calc/myqlm.py @@ -7,7 +7,7 @@ import numpy as np -from qse.calc.calculator import Calculator, all_changes +from qse.calc.calculator import Calculator qat_available = False qlmaas_available = False diff --git a/qse/calc/pulser.py b/qse/calc/pulser.py index bba6580..41464df 100644 --- a/qse/calc/pulser.py +++ b/qse/calc/pulser.py @@ -4,17 +4,8 @@ ASE calculator. https://pulser.readthedocs.io/en/stable/ """ - -import os -import os.path -from abc import ABCMeta -from subprocess import PIPE, Popen -from warnings import warn - -import ase.io import numpy as np import pulser -import pulser.pulse import pulser.waveforms # from pulser_simulation import Simulation, SimConfig, QutipEmulator @@ -24,8 +15,9 @@ from qse.calc.calculator import ( Calculator, CalculatorSetupError, - Parameters, - all_changes, +) +from qse.calc.messages import ( + CalculatorSetupError, ) # from ase.calculators.calculator import (Calculator, all_changes, Parameters, CalculatorSetupError) From 7b5a82b0ee5788c1e56b8bffbce665fe1e4220fb Mon Sep 17 00:00:00 2001 From: jimnel Date: Mon, 7 Apr 2025 16:40:35 +0100 Subject: [PATCH 4/9] update __init__ --- qse/calc/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qse/calc/__init__.py b/qse/calc/__init__.py index 28c247b..34bd7ba 100644 --- a/qse/calc/__init__.py +++ b/qse/calc/__init__.py @@ -1,9 +1,10 @@ """Interface to different QSE calculators.""" -__all__ = ["abc", "calculator", "signal"] +__all__ = ["abc", "calculator", "Signal", "PropertyNotImplementedError", "PropertyNotPresent", "CalculatorSetupError", "CalculationFailed"] import qse.calc.calculator from qse.calc.signal import Signal +from qse.calc.messages import PropertyNotImplementedError, PropertyNotPresent, CalculatorSetupError, CalculationFailed from .myqlm import Myqlm from .pulser import Pulser From 1b3ad4615d550ae0807449c868bb25b67323f646 Mon Sep 17 00:00:00 2001 From: jimnel Date: Mon, 7 Apr 2025 16:42:23 +0100 Subject: [PATCH 5/9] isort + black --- qse/calc/__init__.py | 17 +++++++++++++++-- qse/calc/calculator.py | 9 ++++++++- qse/calc/messages.py | 1 + qse/calc/pulser.py | 10 +++------- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/qse/calc/__init__.py b/qse/calc/__init__.py index 34bd7ba..78399e4 100644 --- a/qse/calc/__init__.py +++ b/qse/calc/__init__.py @@ -1,10 +1,23 @@ """Interface to different QSE calculators.""" -__all__ = ["abc", "calculator", "Signal", "PropertyNotImplementedError", "PropertyNotPresent", "CalculatorSetupError", "CalculationFailed"] +__all__ = [ + "abc", + "calculator", + "Signal", + "PropertyNotImplementedError", + "PropertyNotPresent", + "CalculatorSetupError", + "CalculationFailed", +] import qse.calc.calculator +from qse.calc.messages import ( + CalculationFailed, + CalculatorSetupError, + PropertyNotImplementedError, + PropertyNotPresent, +) from qse.calc.signal import Signal -from qse.calc.messages import PropertyNotImplementedError, PropertyNotPresent, CalculatorSetupError, CalculationFailed from .myqlm import Myqlm from .pulser import Pulser diff --git a/qse/calc/calculator.py b/qse/calc/calculator.py index 4adfbb6..ac81c90 100644 --- a/qse/calc/calculator.py +++ b/qse/calc/calculator.py @@ -7,7 +7,14 @@ import numpy as np from ase.outputs import Properties, all_outputs -from qse.calc.messages import PropertyNotImplementedError, PropertyNotPresent, CalculatorSetupError, CalculationFailed + +from qse.calc.messages import ( + CalculationFailed, + CalculatorSetupError, + PropertyNotImplementedError, + PropertyNotPresent, +) + def compare_qbits(qbits1, qbits2, tol=1e-15, excluded_properties=None): """Check for system changes since last calculation. Properties in diff --git a/qse/calc/messages.py b/qse/calc/messages.py index 8dd5861..2c663f5 100644 --- a/qse/calc/messages.py +++ b/qse/calc/messages.py @@ -1,5 +1,6 @@ """Messages that are thrown when using the calculator.""" + class CalculatorError(RuntimeError): """Base class of error types related to QSE calculators.""" diff --git a/qse/calc/pulser.py b/qse/calc/pulser.py index 41464df..e901808 100644 --- a/qse/calc/pulser.py +++ b/qse/calc/pulser.py @@ -4,6 +4,7 @@ ASE calculator. https://pulser.readthedocs.io/en/stable/ """ + import numpy as np import pulser import pulser.waveforms @@ -12,13 +13,8 @@ from pulser_simulation import QutipEmulator from qse.calc import signal -from qse.calc.calculator import ( - Calculator, - CalculatorSetupError, -) -from qse.calc.messages import ( - CalculatorSetupError, -) +from qse.calc.calculator import Calculator, CalculatorSetupError +from qse.calc.messages import CalculatorSetupError # from ase.calculators.calculator import (Calculator, all_changes, Parameters, CalculatorSetupError) From ecf31b4070de0c334bb7e8218fbb841791cb4683 Mon Sep 17 00:00:00 2001 From: jimnel Date: Mon, 7 Apr 2025 16:46:33 +0100 Subject: [PATCH 6/9] remove abc --- qse/calc/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qse/calc/__init__.py b/qse/calc/__init__.py index 78399e4..6ca6135 100644 --- a/qse/calc/__init__.py +++ b/qse/calc/__init__.py @@ -1,7 +1,6 @@ """Interface to different QSE calculators.""" __all__ = [ - "abc", "calculator", "Signal", "PropertyNotImplementedError", From ed4acc9a9835fd597e7d9bc508e28e3f24083ae7 Mon Sep 17 00:00:00 2001 From: jimnel Date: Wed, 9 Apr 2025 14:36:27 +0100 Subject: [PATCH 7/9] move signal --- qse/__init__.py | 1 + qse/calc/__init__.py | 1 - qse/calc/pulser.py | 2 +- qse/calc/signal.py | 94 -------------------------------------------- 4 files changed, 2 insertions(+), 96 deletions(-) delete mode 100644 qse/calc/signal.py diff --git a/qse/__init__.py b/qse/__init__.py index f1dba64..7f2bf66 100644 --- a/qse/__init__.py +++ b/qse/__init__.py @@ -15,4 +15,5 @@ from qse import utils from qse.qbit import Qbit from qse.qbits import Qbits +from qse.signal import Signal from qse.visualise import draw diff --git a/qse/calc/__init__.py b/qse/calc/__init__.py index 6ca6135..8ae421c 100644 --- a/qse/calc/__init__.py +++ b/qse/calc/__init__.py @@ -16,7 +16,6 @@ PropertyNotImplementedError, PropertyNotPresent, ) -from qse.calc.signal import Signal from .myqlm import Myqlm from .pulser import Pulser diff --git a/qse/calc/pulser.py b/qse/calc/pulser.py index e901808..ad82538 100644 --- a/qse/calc/pulser.py +++ b/qse/calc/pulser.py @@ -12,7 +12,7 @@ # from pulser_simulation import Simulation, SimConfig, QutipEmulator from pulser_simulation import QutipEmulator -from qse.calc import signal +from qse import signal from qse.calc.calculator import Calculator, CalculatorSetupError from qse.calc.messages import CalculatorSetupError diff --git a/qse/calc/signal.py b/qse/calc/signal.py deleted file mode 100644 index b51679d..0000000 --- a/qse/calc/signal.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Definition of the Signal class.""" - -from typing import Union - -import numpy as np - - -class Signal(object): - """ - Signal class to represent a a signal with a duration. - It has two components: values, and duration. - Instantiation of this class supports '+' in two ways. - If w1, w2 are instantiation of signal, then - - w1 + 3 gives a signal with values, w1.values + 3 and - same duration. - - w = w1 + w2 gives signal with concatenated values, i.e., - w.values = [w1.values, w2.values], and added duration. - w.duration = w1.duration + w2.duration - - Note: Currently, the object gets created for multi-dim - arrays as well. However, it should be used for 1D only, - we haven't made it useful or consistent for multi-dim usage. - """ - - def __init__(self, values, duration=None) -> None: - self.values = np.asarray(values) - self._duration = len(self.values) if duration is None else int(duration) - - @property - def duration(self): - """time duration of signal""" - return self._duration - - @duration.setter - def duration(self, value): - self._duration = value - - def __iter__(self): - return iter(self.values) - - def __getitem__(self, i): - return self.values[i] - - def __eq__(self, other) -> bool: - return (self.duration == other.duration) and (self.values == other.values).all() - - def __add__(self, other): - if isinstance(other, Signal): - res = Signal( - values=np.append(self.values, other.values), - duration=self.duration + other.duration, - ) - else: - if isinstance(other, Union[float, int]): - res = Signal(values=self.values + other, duration=self.duration) - else: - raise TypeError(f"wrong type for operand {type(other)}") - return res - - def __radd__(self, other): - return self.__add__(other) - - def __iadd__(self, other): - if isinstance(other, Signal): - self.values = np.append(self.values, other.values) - self.duration += other.duration - else: - if isinstance(other, Union[float, int]): - self.values += other - else: - raise TypeError(f"wrong type for operand {type(other)}") - return self - - def __mul__(self, other): - if isinstance(other, Union[float, int]): - res = Signal(values=self.values * other, duration=self.duration) - else: - raise TypeError(f"wrong type for operand {type(other)}") - return res - - def __rmul__(self, other): - return self.__mul__(other) - - def __imul__(self, other): - if isinstance(other, Union[float, int]): - self.values *= other - else: - raise TypeError(f"wrong type for operand {type(other)}") - return self - - def __repr__(self) -> str: - return f"signal(duration={self.duration}, values={self.values})" - - # we need to define interpolating scheme to resample points if duration is changed externally. From 61dc316eaaf7cec27816bf966d060b821253915f Mon Sep 17 00:00:00 2001 From: jimnel Date: Wed, 9 Apr 2025 14:39:16 +0100 Subject: [PATCH 8/9] init --- qse/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qse/__init__.py b/qse/__init__.py index 7f2bf66..1f1b506 100644 --- a/qse/__init__.py +++ b/qse/__init__.py @@ -5,7 +5,7 @@ This package is adapted from Atomic Simulation Environment (ASE). """ -__all__ = ["Qbits", "Qbit"] +__all__ = ["Qbits", "Qbit", "Signal"] __version__ = "0.1.1" # from qse.calc.pulser import Pulser From 870c4188f916923b58a18c43e1ef2d2f9736b265 Mon Sep 17 00:00:00 2001 From: jimnel Date: Wed, 9 Apr 2025 14:40:21 +0100 Subject: [PATCH 9/9] signal --- qse/signal.py | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 qse/signal.py diff --git a/qse/signal.py b/qse/signal.py new file mode 100644 index 0000000..17b576d --- /dev/null +++ b/qse/signal.py @@ -0,0 +1,94 @@ +"""Definition of the Signal class.""" + +from typing import Union + +import numpy as np + + +class Signal: + """ + Signal class to represent a a signal with a duration. + It has two components: values, and duration. + Instantiation of this class supports '+' in two ways. + If w1, w2 are instantiation of signal, then + - w1 + 3 gives a signal with values, w1.values + 3 and + same duration. + - w = w1 + w2 gives signal with concatenated values, i.e., + w.values = [w1.values, w2.values], and added duration. + w.duration = w1.duration + w2.duration + + Note: Currently, the object gets created for multi-dim + arrays as well. However, it should be used for 1D only, + we haven't made it useful or consistent for multi-dim usage. + """ + + def __init__(self, values, duration=None) -> None: + self.values = np.asarray(values) + self._duration = len(self.values) if duration is None else int(duration) + + @property + def duration(self): + """time duration of signal""" + return self._duration + + @duration.setter + def duration(self, value): + self._duration = value + + def __iter__(self): + return iter(self.values) + + def __getitem__(self, i): + return self.values[i] + + def __eq__(self, other) -> bool: + return (self.duration == other.duration) and (self.values == other.values).all() + + def __add__(self, other): + if isinstance(other, Signal): + res = Signal( + values=np.append(self.values, other.values), + duration=self.duration + other.duration, + ) + else: + if isinstance(other, Union[float, int]): + res = Signal(values=self.values + other, duration=self.duration) + else: + raise TypeError(f"wrong type for operand {type(other)}") + return res + + def __radd__(self, other): + return self.__add__(other) + + def __iadd__(self, other): + if isinstance(other, Signal): + self.values = np.append(self.values, other.values) + self.duration += other.duration + else: + if isinstance(other, Union[float, int]): + self.values += other + else: + raise TypeError(f"wrong type for operand {type(other)}") + return self + + def __mul__(self, other): + if isinstance(other, Union[float, int]): + res = Signal(values=self.values * other, duration=self.duration) + else: + raise TypeError(f"wrong type for operand {type(other)}") + return res + + def __rmul__(self, other): + return self.__mul__(other) + + def __imul__(self, other): + if isinstance(other, Union[float, int]): + self.values *= other + else: + raise TypeError(f"wrong type for operand {type(other)}") + return self + + def __repr__(self) -> str: + return f"signal(duration={self.duration}, values={self.values})" + + # we need to define interpolating scheme to resample points if duration is changed externally.