Skip to content

Commit

Permalink
Add active_validation parameter to CommandTable
Browse files Browse the repository at this point in the history
  • Loading branch information
markheik committed Oct 17, 2022
1 parent 4de2b44 commit 0b18aec
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Scope Module
* Sweeper Module
* Renamed `zhinst.toolkit.nodetree.nodes.WildcardResult` to `zhinst.toolkit.nodetree.helpers.NodeDict`
* Added `active_validation` argument to `CommandTable`. By disabling it, `CommandTable` does not actively
validate the inputs and therefore it improves the speed for command table creation.

## Version 0.4.3
* Fix issue that prevented correct compilation of sequences for AWG cores other than the first one.
Expand Down
40 changes: 40 additions & 0 deletions examples/awg.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,46 @@ ct.table[1].amplitude1.increment = True
awg_node.commandtable.upload_to_device(ct)
```

### Command table ctive validation
Command table validates the given arguments by default. The feature has overhead and can
be turned off to improve production code runtimes. Disabling it is good especially when creating a large
command table with multiple table indexes. The next example shows the effect on active
validation during a large command table creation (maximum number of table entries in the device).

```python
from time import perf_counter_ns

ct.clear()
start = perf_counter_ns()
ct.active_validation = True
for i in range(ct.table.range[0], ct.table.range[-1]):
ct.table[i].waveform.index = 0
ct.table[i].amplitude0.value = 0.0
ct.table[i].amplitude0.increment = False
ct.table[i].amplitude1.value = -0.0
ct.table[i].amplitude1.increment = False
stop = perf_counter_ns()
active_validation_on_duration = stop - start

ct.clear()
ct.active_validation = False
start = perf_counter_ns()
for i in range(ct.table.range[0], ct.table.range[-1]):
ct.table[i].waveform.index = 0
ct.table[i].amplitude0.value = 0.0
ct.table[i].amplitude0.increment = False
ct.table[i].amplitude1.value = -0.0
ct.table[i].amplitude1.increment = False
stop = perf_counter_ns()
active_validation_off_duration = stop - start

def diff_percentage(current, previous):
return (abs(current - previous) / previous) * 100.0

difference_in_runtime = diff_percentage(active_validation_on_duration, active_validation_off_duration)
print(f"Speed improvement without active validation: {difference_in_runtime} %")
```

## Performance optimzation
Often the limiting factor for an experiment is the delay of the device communication.
If this is the case it is best trying to reduce the number of uploads.
Expand Down
185 changes: 155 additions & 30 deletions src/zhinst/toolkit/command_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@
JSON_SCHEMA_VALIDATOR = jsonschema.Draft4Validator


def _validate_instance(instance, schema, validator=JSON_SCHEMA_VALIDATOR):
def _validate_instance(instance: object, schema: dict, validator=JSON_SCHEMA_VALIDATOR):
"""Validate JSON instance.
Args:
instance: Instance to be validated.
schema: Schema
validation: Validator
Raises:
ValidationError: Validation failed.
"""
try:
jsonschema.validate(
instance=instance,
Expand All @@ -38,16 +48,33 @@ class ParentNode:
Args:
schema: JSON schema of the node.
path: Path representation of the node.
active_validation: Enable active validation.
"""

def __init__(self, schema: dict, path: t.Tuple[str, ...]):
def __init__(
self, schema: dict, path: t.Tuple[str, ...], active_validation: bool = True
):
self._schema = schema
self._path = path
self._childs: t.Dict[t.Union[str, int], t.Any] = {}
self._active_validation = active_validation

def __repr__(self) -> str:
return "/" + "/".join(self._path)

def _validate_instance(self, instance: object, schema: dict):
"""Validate JSON instance.
Args:
instance: Instance to be validated.
schema: Schema
Raises:
ValidationError: Validation failed.
"""
if self._active_validation:
_validate_instance(instance, schema)

def is_empty(self) -> bool:
"""Check if the Node is empty and has no properties.
Expand All @@ -57,6 +84,16 @@ def is_empty(self) -> bool:
return not bool(self._childs)


def _change_active_validation(obj: ParentNode, value: bool):
"""Change object active validation state.
Args:
obj: Object
value: State of validation
"""
obj._active_validation = value


class ParentEntry(ParentNode):
"""Parent entry of the CommandTable.
Expand All @@ -65,10 +102,13 @@ class ParentEntry(ParentNode):
Args:
schema: JSON schema of the node.
path: Path representation of the node.
active_validation: Enable active validation.
"""

def __init__(self, schema: dict, path: t.Tuple[str, ...]):
super().__init__(schema, path)
def __init__(
self, schema: dict, path: t.Tuple[str, ...], active_validation: bool = True
):
super().__init__(schema, path, active_validation)
self._attributes = {}
self._child_props = {}
self._properties: t.Dict[str, t.Any] = {}
Expand Down Expand Up @@ -97,7 +137,9 @@ def __getattr__(self, name: str) -> t.Union["ParentEntry", t.Any]:
except KeyError:
if name in self._child_props:
self._childs[name] = ParentEntry(
self._child_props[name], self._path + (name,)
self._child_props[name],
self._path + (name,),
self._active_validation,
)
return self._childs[name]
if name in self._attributes:
Expand All @@ -112,7 +154,7 @@ def __setattr__(self, name: str, value: t.Any):
self._childs.pop(name, None)
self._properties.pop(name, None)
else:
_validate_instance(value, self._attributes[name])
self._validate_instance(value, self._attributes[name])
self._childs[name] = value
self._properties[name] = value
elif value is None and name in self._childs:
Expand Down Expand Up @@ -161,10 +203,13 @@ class ListEntry(ParentNode):
Args:
schema: JSON schema of the node.
path: Path representation of the node.
active_validation: Enable active validation.
"""

def __init__(self, schema: dict, path: t.Tuple[str, ...]):
super().__init__(schema, path)
def __init__(
self, schema: dict, path: t.Tuple[str, ...], active_validation: bool = True
):
super().__init__(schema, path, active_validation)
self._schema = copy.deepcopy(schema)
self._min_length = schema["minItems"]
self._max_length = schema["maxItems"]
Expand All @@ -176,12 +221,14 @@ def __len__(self):
return len(self._childs)

def __getitem__(self, number: int) -> ParentEntry:
_validate_instance(number, self._index_schema)
self._validate_instance(number, self._index_schema)
try:
return self._childs[number]
except KeyError:
self._childs[number] = ParentEntry(
self._schema["items"], self._path + (str(number),)
self._schema["items"],
self._path + (str(number),),
self._active_validation,
)
return self._childs[number]

Expand Down Expand Up @@ -221,10 +268,17 @@ class HeaderEntry(ParentEntry):
schema: JSON schema of the node.
path: Path representation of the node.
version: JSON schema version
active_validation: Enable active validation.
"""

def __init__(self, schema: dict, path: tuple, version: t.Optional[str] = None):
super().__init__(schema, path)
def __init__(
self,
schema: dict,
path: tuple,
version: t.Optional[str] = None,
active_validation: bool = True,
):
super().__init__(schema, path, active_validation)
# L1 22.08 new schema format
if version:
self._childs["version"] = version
Expand Down Expand Up @@ -257,43 +311,111 @@ def _derefence_json(schema: t.Union[str, dict]) -> t.Any:


class CommandTable:
"""A representation of a ZI device command table.
The class provides functionality to create and modify existing
command tables.
"""Representation of a ZI device command table.
The class provides functionality to create and modify existing command tables.
The CommandTable can be modified by via ``header`` and ``table`` properties.
Args:
json_schema: JSON Schema of the command table.
active_validation: Active validation of table entries. (default = True)
Active validation enabled:
Each time a table entry is accessed, the values are validated
against the given JSON schema. It is suggested to keep disabled in
production code as it will slow the command table creation.
Active validation disabled:
No validation happens during command table entry modifications, thus
making the creation of the command table faster.
.. versionadded:: 0.4.4
The ``active_validation`` parameter was added.
Example:
.. code-block:: python
.. code-block:: python
>>> from zhinst.toolkit import CommandTable
>>> ct = CommandTable(json_schema)
The ``header`` and ``table`` and then be called:
.. code-block:: python
>>> ct.header.version
"1.1"
>>> ct.header.userString = "My table"
>>> ct.table[0].amplitude.value = 1
>>> ct.table[0].amplitude
1
>>> ct.as_dict()
Active validation
from zhinst.toolkit import CommandTable
Using active validation, error raised instantly on incorrect value:
ct = CommandTable(json_schema)
.. code-block:: python
The ``header`` and ``table`` and then be called.
>>> ct = CommandTable(json_schema, active_validation=True)
>>> ct.table[0].amplitude0.value = 999e9
ValidationError
.. code-block:: python
Disabling active validation:
ct.header.version
# "1.1"
ct.header.userString = "My table"
No ``ValidationError`` is raised during the creation of the command table,
but once it is uploaded or called ``as_dict()``, the validation happens.
ct.table[0].amplitude.value = 1
ct.table[0].amplitude
# 1
.. code-block:: python
ct.as_json()
>>> ct = CommandTable(json_schema, active_validation=False)
>>> ct.table[0].amplitude0.value = 999e9 # No errors raised
>>> ct.as_dict()
ValidationError
Disabling active validation improves the speed of large command tables:
.. code-block:: python
>>> for i in range(1024):
>>> ct.table[i].waveform.index = 1
>>> ct.table[i].amplitude0.value = 1
>>> ct.table[i].amplitude1.value = -0.0
>>> ct.table[i].amplitude0.increment = False
>>> ct.table[i].amplitude0.increment = True
"""

def __init__(self, json_schema: t.Union[str, dict]):
def __init__(self, json_schema: t.Union[str, dict], active_validation: bool = True):
self._ct_schema: t.Dict = _derefence_json(json_schema)
self._active_validation = active_validation
self._header: HeaderEntry = self._header_entry()
self._table: ListEntry = self._table_entry()

@property
def active_validation(self) -> bool:
"""State of active validation.
Returns:
True if active validation is enabled.
.. versionadded:: 0.4.4
"""
return self._active_validation

@active_validation.setter
def active_validation(self, value: bool):
"""Active validation.
Args:
value: The state of active validation.
.. versionadded:: 0.4.4
"""
self._active_validation = value
_change_active_validation(self._table, value)
_change_active_validation(self._header, value)

@property
def header(self) -> HeaderEntry:
"""Header of the built command table."""
Expand All @@ -309,10 +431,13 @@ def _header_entry(self) -> HeaderEntry:
self._ct_schema["definitions"]["header"],
("header",),
self._ct_schema.get("version", ""),
self._active_validation,
)

def _table_entry(self) -> ListEntry:
return ListEntry(self._ct_schema["definitions"]["table"], ("table",))
return ListEntry(
self._ct_schema["definitions"]["table"], ("table",), self._active_validation
)

def clear(self) -> None:
"""Clear CommandTable back to its initial state."""
Expand Down
2 changes: 1 addition & 1 deletion src/zhinst/toolkit/driver/nodes/command_table_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@ def load_from_device(self) -> CommandTable:
Returns:
command table.
"""
ct = CommandTable(self.load_validation_schema())
ct = CommandTable(self.load_validation_schema(), active_validation=True)
ct.update(self.data())
return ct
Loading

0 comments on commit 0b18aec

Please sign in to comment.