Skip to content

Commit

Permalink
Merge pull request #88 from qua-platform/feat/set-at-reference
Browse files Browse the repository at this point in the history
Feat/Add `QuamBase.set_at_reference`
  • Loading branch information
nulinspiratie authored Dec 12, 2024
2 parents 4009624 + d336321 commit 31dff5d
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 4 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
## [Unreleased]
### Added
- Added `QuamBase.set_at_reference` to set a value at a reference
- Added `string_reference.get_parent_reference` to get the parent reference of a string reference

### Changed
- `Pulse.integration_weights` now defaults to `#./default_integration_weights`, which returns [(1, pulse.length)]


## [0.3.8]
### Added
- Added time tagging to channels
Expand Down
40 changes: 38 additions & 2 deletions quam/core/quam_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,42 @@ def print_summary(self, indent: int = 0):
else:
print(" " * (indent + 2) + f"{attr}: {val}")

def set_at_reference(self, attr: str, value: Any):
"""Follow the reference of an attribute and set the value at the reference
Args:
attr: The attribute to set the value at the reference of.
value: The value to set.
Raises:
ValueError: If the attribute is not a reference.
ValueError: If the reference is invalid, e.g. "#./" since it has no
attribute.
"""
raw_value = self.get_unreferenced_value(attr)
if not string_reference.is_reference(raw_value):
raise ValueError(
f"Cannot set at reference because attr '{attr}' is not a reference. "
f"'{attr}' = {raw_value}"
)

parent_reference, ref_attr = string_reference.split_reference(raw_value)
if not ref_attr:
raise ValueError(
f"Unsuccessful attempt to set the value at reference {raw_value} for "
f"attribute {attr} because the reference is invalid as it has no "
"attribute"
)

parent_obj = self._get_referenced_value(parent_reference)
raw_referenced_value = parent_obj.get_unreferenced_value(ref_attr)
if string_reference.is_reference(raw_referenced_value) and isinstance(
parent_obj, QuamBase
):
parent_obj.set_at_reference(ref_attr, value)
else:
setattr(parent_obj, ref_attr, value)


# Type annotation for QuamRoot, can be replaced by typing.Self from Python 3.11
QuamRootType = TypeVar("QuamRootType", bound="QuamRoot")
Expand Down Expand Up @@ -682,8 +718,8 @@ def generate_config(self) -> Dict[str, Any]:

return qua_config

def get_unreferenced_value(self, attr: str):
return getattr(self, attr)
# def get_unreferenced_value(self, attr: str):
# return getattr(self, attr)


class QuamComponent(QuamBase):
Expand Down
4 changes: 2 additions & 2 deletions quam/utils/reference_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def _is_reference(self, attr: str) -> bool:
"""
raise NotImplementedError

def get_unreferenced_value(self, attr: str) -> bool:
"""Check if an attribute is a reference"""
def get_unreferenced_value(self, attr: str) -> Any:
"""Get the raw value of an attribute, returning the reference if it is one"""
return super().__getattribute__(attr)

def __getattribute__(self, attr: str) -> Any:
Expand Down
33 changes: 33 additions & 0 deletions quam/utils/string_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,36 @@ def get_referenced_value(obj, string: str, root=None) -> Any:
return get_relative_reference_value(obj, string)
except (AttributeError, KeyError) as e:
raise ValueError(f"String {string} is not a valid reference, Error: {e}") from e


def split_reference(string: str) -> Tuple[str, str]:
"""Split a string reference into its parent reference and attribute
Args:
string: The reference string
Returns:
A tuple containing the parent reference string and the attribute
Raises:
ValueError: If the string is not a valid reference
ValueError: If the string equals "#/"
Examples:
split_reference("#/a/b/c") == ("#/a/b", "c")
split_reference("#/a/b") == ("#/a", "b")
split_reference("#/a") == ("#/", "a")
"""
if not is_reference(string):
raise ValueError(f"String {string} is not a reference")
if string == "#/":
raise ValueError(f"String {string} has no parent")
if string == "#./":
return "#../", ""
if string == "#../":
return "#../../", ""

parent_reference, attr = string.rsplit("/", 1)
if parent_reference in ("#", "#.", "#.."):
parent_reference += "/"
return parent_reference, attr
157 changes: 157 additions & 0 deletions tests/quam_base/test_set_at_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import pytest
from quam.core.quam_classes import QuamBase, QuamRoot, quam_dataclass
from typing import Optional


@quam_dataclass
class ChildQuam(QuamBase):
value: int = 0


@quam_dataclass
class ParentQuam(QuamBase):
child: ChildQuam = None
ref_value: str = "#./child/value"
normal_value: int = 42


@quam_dataclass
class RootQuam(QuamRoot):
parent: ParentQuam = None
abs_ref: str = "#/parent/child/value"


def test_set_at_reference():
"""Test setting a value through a reference"""
parent = ParentQuam(child=ChildQuam())

# Set value through reference
parent.set_at_reference("ref_value", 123)

# Check that the value was set correctly
assert parent.child.value == 123
# Reference string should remain unchanged
assert parent.get_unreferenced_value("ref_value") == "#./child/value"


def test_set_at_reference_non_reference():
"""Test that setting a non-reference attribute raises ValueError"""
parent = ParentQuam(child=ChildQuam())

with pytest.raises(ValueError, match="is not a reference"):
parent.set_at_reference("normal_value", 123)


def test_set_at_reference_invalid_reference():
"""Test handling of invalid references"""
parent = ParentQuam(child=ChildQuam())
parent.ref_value = "#./nonexistent/value"

with pytest.raises(AttributeError):
parent.set_at_reference("ref_value", 123)


def test_unreferenced_value():
root = RootQuam(parent=ParentQuam(child=ChildQuam()))
assert root.get_unreferenced_value("abs_ref") == "#/parent/child/value"
assert root.parent.get_unreferenced_value("ref_value") == "#./child/value"


def test_set_at_absolute_reference():
"""Test setting a value through an absolute reference"""
root = RootQuam(parent=ParentQuam(child=ChildQuam()))

# Set value through absolute reference
root.set_at_reference("abs_ref", 456)

# Check that the value was set correctly
assert root.parent.child.value == 456
# Reference string should remain unchanged
assert root.get_unreferenced_value("abs_ref") == "#/parent/child/value"


def test_set_at_absolute_reference_invalid():
"""Test handling of invalid absolute references"""
root = RootQuam(parent=ParentQuam(child=ChildQuam()))
root.abs_ref = "#/nonexistent/path"

with pytest.raises(AttributeError):
root.set_at_reference("abs_ref", 456)


@quam_dataclass
class DoubleChildQuam(ChildQuam):
value: int = 0
child: Optional[ChildQuam] = None

def test_set_double_reference():
"""Test setting a value through a double reference"""
double_child = DoubleChildQuam(child=ChildQuam(value=42), value="#./child/value")
parent = ParentQuam(child=double_child, ref_value="#./child/value")

assert parent.ref_value == 42
assert parent.get_unreferenced_value("ref_value") == "#./child/value"
assert parent.child.get_unreferenced_value("value") == "#./child/value"

# Set value through double reference
parent.set_at_reference("ref_value", 789)

# Check that the value was set correctly in the nested child
assert double_child.child.value == 789
assert double_child.value == 789
assert parent.ref_value == 789

# Reference string should remain unchanged
assert parent.get_unreferenced_value("ref_value") == "#./child/value"
assert double_child.get_unreferenced_value("value") == "#./child/value"


def test_set_nonexistent_double_reference():
"""Test setting a value where the double reference does not exist"""
double_child = DoubleChildQuam(child=ChildQuam(value=42), value="#./child/nonexistent")
parent = ParentQuam(child=double_child, ref_value="#./child/nonexistent")

with pytest.raises(AttributeError):
parent.set_at_reference("ref_value", 789)


def test_set_double_reference_to_nonexistent_item():
"""Test setting a value through a double reference to a nonexistent item"""
double_child = DoubleChildQuam(child=ChildQuam(value=42), value="#./nonexistent/value")
parent = ParentQuam(child=double_child, ref_value="#./nonexistent/value")

with pytest.raises(AttributeError):
parent.set_at_reference("ref_value", 789)


def test_set_double_reference_with_invalid_reference():
"""Test setting a value through a double reference with an invalid reference"""
double_child = DoubleChildQuam(child=ChildQuam(value=42), value="#./child/invalid")
parent = ParentQuam(child=double_child, ref_value="#./child/invalid")

with pytest.raises(AttributeError):
parent.set_at_reference("ref_value", 789)

def test_set_triple_reference():
"""Test setting a value through a triple reference"""
triple_child = DoubleChildQuam(child=DoubleChildQuam(child=ChildQuam(value=42), value="#./child/value"), value="#./child/value")
parent = ParentQuam(child=triple_child, ref_value="#./child/value")

assert parent.ref_value == 42
assert parent.get_unreferenced_value("ref_value") == "#./child/value"
assert parent.child.get_unreferenced_value("value") == "#./child/value"
assert parent.child.child.get_unreferenced_value("value") == "#./child/value"

# Set value through triple reference
parent.set_at_reference("ref_value", 789)

# Check that the value was set correctly in the nested child
assert triple_child.child.child.value == 789
assert triple_child.child.value == 789
assert triple_child.value == 789
assert parent.ref_value == 789

# Reference string should remain unchanged
assert parent.get_unreferenced_value("ref_value") == "#./child/value"
assert triple_child.get_unreferenced_value("value") == "#./child/value"
assert triple_child.child.get_unreferenced_value("value") == "#./child/value"
50 changes: 50 additions & 0 deletions tests/utils/test_string_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,53 @@ def test_delimiter():
assert transmon.xy.name == "q1$xy"
finally:
quam.utils.string_reference.DELIMITER = "."


def test_get_parent_reference_absolute():
parent, attr = split_reference("#/a/b")
assert parent == "#/a"
assert attr == "b"

parent, attr = split_reference("#/a/b/c")
assert parent == "#/a/b"
assert attr == "c"

parent, attr = split_reference("#/a")
assert parent == "#/"
assert attr == "a"

with pytest.raises(ValueError):
split_reference("#/")


def test_get_parent_reference_relative():
parent, attr = split_reference("#./a/b")
assert parent == "#./a"
assert attr == "b"

parent, attr = split_reference("#../a/b")
assert parent == "#../a"
assert attr == "b"

parent, attr = split_reference("#./a")
assert parent == "#./"
assert attr == "a"

parent, attr = split_reference("#../a")
assert parent == "#../"
assert attr == "a"

parent, attr = split_reference("#./")
assert parent == "#../"
assert attr == ""

parent, attr = split_reference("#../")
assert parent == "#../../"
assert attr == ""


def test_get_parent_reference_invalid():
with pytest.raises(ValueError):
split_reference("a")
with pytest.raises(ValueError):
split_reference("#")

0 comments on commit 31dff5d

Please sign in to comment.