Skip to content

Serialization

Serialization

SerializationModel(d=dict()) dataclass

A serialization model class to serialize data from QuantumModels,.

torch.nn.Module and similar structures. The data included in the serialization logic includes: the AbstractBlock and its children classes, QuantumCircuit, Register, and sympy expressions (including Parameter class from qadence.parameters).

A children class must define the value attribute type and how to handle it, since it is the main property for the class to be used by the serialization process. For instance:

@dataclass
class QuantumCircuitSerialization(SerializationModel):
    value: QuantumCircuit = dataclass_field(init=False)

    def __post_init__(self) -> None:
        self.value = (
            QuantumCircuit._from_dict(self.d)
            if isinstance(self.d, dict)
            else self.d
        )

deserialize(d, as_torch=False)

Supported Types:

AbstractBlock | QuantumCircuit | QuantumModel | Register | torch.nn.Module Deserializes a dict to one of the supported types.

PARAMETER DESCRIPTION
d

A dict containing a serialized object.

TYPE: dict

as_torch

Whether to transform to torch for the deserialized object.

TYPE: bool DEFAULT: False

Returns: AbstractBlock, QuantumCircuit, QuantumModel, Register, torch.nn.Module.

Examples:

import torch
from qadence import serialize, deserialize, hea, hamiltonian_factory, Z
from qadence import QuantumCircuit, QuantumModel

n_qubits = 2
myblock = hea(n_qubits=n_qubits, depth=1)
block_dict = serialize(myblock)
print(block_dict)

## Lets use myblock in a QuantumCircuit and serialize it.

qc = QuantumCircuit(n_qubits, myblock)
qc_dict = serialize(qc)
qc_deserialized = deserialize(qc_dict)
assert qc == qc_deserialized

## Finally, let's wrap it in a QuantumModel
obs = hamiltonian_factory(n_qubits, detuning = Z)
qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')

qm_dict = serialize(qm)
qm_deserialized = deserialize(qm_dict)
# Lets check if the loaded QuantumModel returns the same expectation
assert torch.isclose(qm.expectation({}), qm_deserialized.expectation({}))
{'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': 'HEA', 'blocks': [{'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RX', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('ff59ec8b-db16-4815-ad4f-9b94bf2a2beb', {'name': 'theta_0', 'expression': "Parameter('theta_0')", 'symbols': {'theta_0': {'name': 'theta_0', 'trainable': 'True', 'value': '0.495582402021744'}}})}}}, {'type': 'RX', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('5915ab3c-a24f-442b-832b-7ddccd3ff5cf', {'name': 'theta_1', 'expression': "Parameter('theta_1')", 'symbols': {'theta_1': {'name': 'theta_1', 'trainable': 'True', 'value': '0.08840193195718948'}}})}}}]}, {'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RY', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('65b89ef4-a66d-4bba-acc3-baa6b2a0a413', {'name': 'theta_2', 'expression': "Parameter('theta_2')", 'symbols': {'theta_2': {'name': 'theta_2', 'trainable': 'True', 'value': '0.017883402670038295'}}})}}}, {'type': 'RY', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('b4cd28b9-b8b4-484e-90ab-402f74f21da5', {'name': 'theta_3', 'expression': "Parameter('theta_3')", 'symbols': {'theta_3': {'name': 'theta_3', 'trainable': 'True', 'value': '0.13607528101808786'}}})}}}]}, {'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RX', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('aac51b7f-3b0a-4cb5-b948-ba81950a8c18', {'name': 'theta_4', 'expression': "Parameter('theta_4')", 'symbols': {'theta_4': {'name': 'theta_4', 'trainable': 'True', 'value': '0.8621633901369974'}}})}}}, {'type': 'RX', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('0d4c745b-a485-4869-8b72-2a0df22b0b43', {'name': 'theta_5', 'expression': "Parameter('theta_5')", 'symbols': {'theta_5': {'name': 'theta_5', 'trainable': 'True', 'value': '0.9714993709424635'}}})}}}]}]}, {'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'CNOT', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'X', 'qubit_support': (1,), 'tag': None}]}]}]}]}

Source code in qadence/serialization.py
def deserialize(d: dict, as_torch: bool = False) -> SUPPORTED_TYPES:
    """
    Supported Types:

    AbstractBlock | QuantumCircuit | QuantumModel | Register | torch.nn.Module
    Deserializes a dict to one of the supported types.

    Arguments:
        d (dict): A dict containing a serialized object.
        as_torch (bool): Whether to transform to torch for the deserialized object.
    Returns:
        AbstractBlock, QuantumCircuit, QuantumModel, Register, torch.nn.Module.

    Examples:
    ```python exec="on" source="material-block" result="json"
    import torch
    from qadence import serialize, deserialize, hea, hamiltonian_factory, Z
    from qadence import QuantumCircuit, QuantumModel

    n_qubits = 2
    myblock = hea(n_qubits=n_qubits, depth=1)
    block_dict = serialize(myblock)
    print(block_dict)

    ## Lets use myblock in a QuantumCircuit and serialize it.

    qc = QuantumCircuit(n_qubits, myblock)
    qc_dict = serialize(qc)
    qc_deserialized = deserialize(qc_dict)
    assert qc == qc_deserialized

    ## Finally, let's wrap it in a QuantumModel
    obs = hamiltonian_factory(n_qubits, detuning = Z)
    qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')

    qm_dict = serialize(qm)
    qm_deserialized = deserialize(qm_dict)
    # Lets check if the loaded QuantumModel returns the same expectation
    assert torch.isclose(qm.expectation({}), qm_deserialized.expectation({}))
    ```
    """
    obj: SerializationModel
    if d.get("expression"):
        obj = ExpressionSerialization(d)
    elif d.get("block") and d.get("register"):
        obj = QuantumCircuitSerialization(d)
    elif d.get("graph"):
        obj = RegisterSerialization(d)
    elif d.get("type"):
        obj = BlockTypeSerialization(d)
    else:
        obj = ModelSerialization(d, as_torch=as_torch)
    return obj.value

load(file_path, map_location='cpu')

Same as serialize/deserialize but for storing/loading files.

Supported types: AbstractBlock | QuantumCircuit | QuantumModel | TransformedModule | Register Loads a .json or .pt file to one of the supported types.

PARAMETER DESCRIPTION
file_path

The name of the file.

TYPE: str

map_location

In case of a .pt file, on which device to load the object (cpu,cuda).

TYPE: str DEFAULT: 'cpu'

Returns: A object of type AbstractBlock, QuantumCircuit, QuantumModel, TransformedModule, Register.

Examples:

import torch
from pathlib import Path
import os

from qadence import save, load, hea, hamiltonian_factory, Z
from qadence import QuantumCircuit, QuantumModel

n_qubits = 2
myblock = hea(n_qubits=n_qubits, depth=1)
qc = QuantumCircuit(n_qubits, myblock)
# Lets store the circuit in a json file
save(qc, '.', 'circ')
loaded_qc = load(Path('circ.json'))
qc == loaded_qc
os.remove('circ.json')
## Let's wrap it in a QuantumModel and store that
obs = hamiltonian_factory(n_qubits, detuning = Z)
qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')
save(qm, folder= '.',file_name= 'quantum_model')
qm_loaded = load('quantum_model.json')
os.remove('quantum_model.json')

Source code in qadence/serialization.py
def load(file_path: str | Path, map_location: str = "cpu") -> SUPPORTED_TYPES:
    """
    Same as serialize/deserialize but for storing/loading files.

    Supported types: AbstractBlock | QuantumCircuit | QuantumModel | TransformedModule | Register
    Loads a .json or .pt file to one of the supported types.

    Arguments:
        file_path (str): The name of the file.
        map_location (str): In case of a .pt file, on which device to load the object (cpu,cuda).
    Returns:
        A object of type AbstractBlock, QuantumCircuit, QuantumModel, TransformedModule, Register.

    Examples:
    ```python exec="on" source="material-block" result="json"
    import torch
    from pathlib import Path
    import os

    from qadence import save, load, hea, hamiltonian_factory, Z
    from qadence import QuantumCircuit, QuantumModel

    n_qubits = 2
    myblock = hea(n_qubits=n_qubits, depth=1)
    qc = QuantumCircuit(n_qubits, myblock)
    # Lets store the circuit in a json file
    save(qc, '.', 'circ')
    loaded_qc = load(Path('circ.json'))
    qc == loaded_qc
    os.remove('circ.json')
    ## Let's wrap it in a QuantumModel and store that
    obs = hamiltonian_factory(n_qubits, detuning = Z)
    qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')
    save(qm, folder= '.',file_name= 'quantum_model')
    qm_loaded = load('quantum_model.json')
    os.remove('quantum_model.json')
    ```
    """
    d = {}
    if isinstance(file_path, str):
        file_path = Path(file_path)
    if not os.path.exists(file_path):
        logger.error(f"File {file_path} not found.")
        raise FileNotFoundError
    FORMAT = file_extension(file_path)
    _, _, load_fn, _ = FORMAT_DICT[FORMAT]  # type: ignore[index]
    try:
        d = load_fn(file_path, map_location)
        logger.debug(f"Successfully loaded {d} from {file_path}.")
    except Exception as e:
        logger.error(f"Unable to load Object from {file_path} due to {e}")
    return deserialize(d)

parse_expr_fn(code)

A parsing expressions function that checks whether a given code is valid on.

the parsing grammar. The grammar is defined to be compatible with sympy expressions, such as Float('-0.33261030434342942', precision=53), while avoiding code injection such as 2*3 or __import__('os').system('ls -la').

PARAMETER DESCRIPTION
code

code to be parsed and checked.

TYPE: str

RETURNS DESCRIPTION
bool

Boolean indicating whether the code matches the defined grammar or not.

Source code in qadence/serialization.py
def parse_expr_fn(code: str) -> bool:
    """
    A parsing expressions function that checks whether a given code is valid on.

    the parsing grammar. The grammar is defined to be compatible with `sympy`
    expressions, such as `Float('-0.33261030434342942', precision=53)`, while
    avoiding code injection such as `2*3` or `__import__('os').system('ls -la')`.

    Args:
        code (str): code to be parsed and checked.

    Returns:
        Boolean indicating whether the code matches the defined grammar or not.
    """

    parser = _parsing_serialize_expr
    try:
        parser.parse(code)
    except NoMatch:
        return False
    else:
        return True

save(obj, folder, file_name='', format=SerializationFormat.JSON)

Same as serialize/deserialize but for storing/loading files.

Supported types: AbstractBlock | QuantumCircuit | QuantumModel | TransformedModule | Register | torch.nn.Module Saves a qadence object to a json/.pt.

PARAMETER DESCRIPTION
obj
Either AbstractBlock, QuantumCircuit, QuantumModel, TransformedModule, Register.

TYPE: AbstractBlock | QuantumCircuit | QuantumModel | Register

file_name

The name of the file.

TYPE: str DEFAULT: ''

format

The type of file to save.

TYPE: str DEFAULT: JSON

Returns: None.

Examples:

import torch
from pathlib import Path
import os

from qadence import save, load, hea, hamiltonian_factory, Z
from qadence import QuantumCircuit, QuantumModel

n_qubits = 2
myblock = hea(n_qubits=n_qubits, depth=1)
qc = QuantumCircuit(n_qubits, myblock)
# Lets store the circuit in a json file
save(qc, '.', 'circ')
loaded_qc = load(Path('circ.json'))
qc == loaded_qc
os.remove('circ.json')
## Let's wrap it in a QuantumModel and store that
obs = hamiltonian_factory(n_qubits, detuning = Z)
qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')
save(qm, folder= '.',file_name= 'quantum_model')
qm_loaded = load('quantum_model.json')
os.remove('quantum_model.json')

Source code in qadence/serialization.py
def save(
    obj: SUPPORTED_TYPES,
    folder: str | Path,
    file_name: str = "",
    format: SerializationFormat = SerializationFormat.JSON,
) -> None:
    """
    Same as serialize/deserialize but for storing/loading files.

    Supported types:
    AbstractBlock | QuantumCircuit | QuantumModel | TransformedModule | Register | torch.nn.Module
    Saves a qadence object to a json/.pt.

    Arguments:
        obj (AbstractBlock | QuantumCircuit | QuantumModel | Register):
                Either AbstractBlock, QuantumCircuit, QuantumModel, TransformedModule, Register.
        file_name (str): The name of the file.
        format (str): The type of file to save.
    Returns:
        None.

    Examples:
    ```python exec="on" source="material-block" result="json"
    import torch
    from pathlib import Path
    import os

    from qadence import save, load, hea, hamiltonian_factory, Z
    from qadence import QuantumCircuit, QuantumModel

    n_qubits = 2
    myblock = hea(n_qubits=n_qubits, depth=1)
    qc = QuantumCircuit(n_qubits, myblock)
    # Lets store the circuit in a json file
    save(qc, '.', 'circ')
    loaded_qc = load(Path('circ.json'))
    qc == loaded_qc
    os.remove('circ.json')
    ## Let's wrap it in a QuantumModel and store that
    obs = hamiltonian_factory(n_qubits, detuning = Z)
    qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')
    save(qm, folder= '.',file_name= 'quantum_model')
    qm_loaded = load('quantum_model.json')
    os.remove('quantum_model.json')
    ```
    """
    if not isinstance(obj, get_args(SUPPORTED_TYPES)):
        logger.error(f"Serialization of object type {type(obj)} not supported.")
    folder = Path(folder)
    if not folder.is_dir():
        logger.error(NotADirectoryError)
    if file_name == "":
        file_name = type(obj).__name__
    try:
        suffix, save_fn, _, save_params = FORMAT_DICT[format]
        d = serialize(obj, save_params)
        file_path = folder / Path(file_name + suffix)
        save_fn(d, file_path)
        logger.debug(f"Successfully saved {obj} from to {folder}.")
    except Exception as e:
        logger.error(f"Unable to write {type(obj)} to disk due to {e}")

serialize(obj, save_params=False)

Supported Types:

AbstractBlock | QuantumCircuit | QuantumModel | torch.nn.Module | Register | Module Serializes a qadence object to a dictionary.

PARAMETER DESCRIPTION
obj

TYPE: AbstractBlock | QuantumCircuit | QuantumModel | Register | Module

Returns: A dict.

Examples:

import torch
from qadence import serialize, deserialize, hea, hamiltonian_factory, Z
from qadence import QuantumCircuit, QuantumModel

n_qubits = 2
myblock = hea(n_qubits=n_qubits, depth=1)
block_dict = serialize(myblock)
print(block_dict)

## Lets use myblock in a QuantumCircuit and serialize it.

qc = QuantumCircuit(n_qubits, myblock)
qc_dict = serialize(qc)
qc_deserialized = deserialize(qc_dict)
assert qc == qc_deserialized

## Finally, let's wrap it in a QuantumModel
obs = hamiltonian_factory(n_qubits, detuning = Z)
qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')

qm_dict = serialize(qm)
qm_deserialized = deserialize(qm_dict)
# Lets check if the loaded QuantumModel returns the same expectation
assert torch.isclose(qm.expectation({}), qm_deserialized.expectation({}))
{'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': 'HEA', 'blocks': [{'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RX', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('a171ebde-9ed2-4d72-8116-c562cd40695b', {'name': 'theta_0', 'expression': "Parameter('theta_0')", 'symbols': {'theta_0': {'name': 'theta_0', 'trainable': 'True', 'value': '0.544649028517595'}}})}}}, {'type': 'RX', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('882fc09d-8133-4e5a-8251-9d584a63ced6', {'name': 'theta_1', 'expression': "Parameter('theta_1')", 'symbols': {'theta_1': {'name': 'theta_1', 'trainable': 'True', 'value': '0.8040643239062514'}}})}}}]}, {'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RY', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('a3308016-ef7f-4f5f-9827-ff49ef5bdfcc', {'name': 'theta_2', 'expression': "Parameter('theta_2')", 'symbols': {'theta_2': {'name': 'theta_2', 'trainable': 'True', 'value': '0.0963085508396101'}}})}}}, {'type': 'RY', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('c5788bef-688d-4e04-b223-6232f041f4ee', {'name': 'theta_3', 'expression': "Parameter('theta_3')", 'symbols': {'theta_3': {'name': 'theta_3', 'trainable': 'True', 'value': '0.629923691597065'}}})}}}]}, {'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'RX', 'qubit_support': (0,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('1f2c30f5-2a62-4204-b581-54cd4af3c323', {'name': 'theta_4', 'expression': "Parameter('theta_4')", 'symbols': {'theta_4': {'name': 'theta_4', 'trainable': 'True', 'value': '0.895849390462047'}}})}}}, {'type': 'RX', 'qubit_support': (1,), 'tag': None, 'parameters': {'_name_dict': {'parameter': ('0f7ab827-07d2-477c-9c64-0f8175ab202e', {'name': 'theta_5', 'expression': "Parameter('theta_5')", 'symbols': {'theta_5': {'name': 'theta_5', 'trainable': 'True', 'value': '0.898787499949708'}}})}}}]}]}, {'type': 'ChainBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'KronBlock', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'CNOT', 'qubit_support': (0, 1), 'tag': None, 'blocks': [{'type': 'X', 'qubit_support': (1,), 'tag': None}]}]}]}]}

Source code in qadence/serialization.py
def serialize(obj: SUPPORTED_TYPES, save_params: bool = False) -> dict:
    """
    Supported Types:

    AbstractBlock | QuantumCircuit | QuantumModel | torch.nn.Module | Register | Module
    Serializes a qadence object to a dictionary.

    Arguments:
        obj (AbstractBlock | QuantumCircuit | QuantumModel | Register | torch.nn.Module):
    Returns:
        A dict.

    Examples:
    ```python exec="on" source="material-block" result="json"
    import torch
    from qadence import serialize, deserialize, hea, hamiltonian_factory, Z
    from qadence import QuantumCircuit, QuantumModel

    n_qubits = 2
    myblock = hea(n_qubits=n_qubits, depth=1)
    block_dict = serialize(myblock)
    print(block_dict)

    ## Lets use myblock in a QuantumCircuit and serialize it.

    qc = QuantumCircuit(n_qubits, myblock)
    qc_dict = serialize(qc)
    qc_deserialized = deserialize(qc_dict)
    assert qc == qc_deserialized

    ## Finally, let's wrap it in a QuantumModel
    obs = hamiltonian_factory(n_qubits, detuning = Z)
    qm = QuantumModel(qc, obs, backend='pyqtorch', diff_mode='ad')

    qm_dict = serialize(qm)
    qm_deserialized = deserialize(qm_dict)
    # Lets check if the loaded QuantumModel returns the same expectation
    assert torch.isclose(qm.expectation({}), qm_deserialized.expectation({}))
    ```
    """
    if not isinstance(obj, get_args(SUPPORTED_TYPES)):
        logger.error(TypeError(f"Serialization of object type {type(obj)} not supported."))

    d: dict = dict()
    try:
        if isinstance(obj, core.Expr):
            symb_dict = dict()
            expr_dict = {"name": str(obj), "expression": srepr(obj)}
            symbs: set[Parameter | core.Basic] = obj.free_symbols
            if symbs:
                symb_dict = {"symbols": {str(s): s._to_dict() for s in symbs}}
            d = {**expr_dict, **symb_dict}
        else:
            if hasattr(obj, "_to_dict"):
                model_to_dict: Callable = obj._to_dict
                d = (
                    model_to_dict(save_params)
                    if isinstance(obj, torch.nn.Module)
                    else model_to_dict()
                )
            elif hasattr(obj, "state_dict"):
                d = {type(obj).__name__: obj.state_dict()}
            else:
                raise ValueError(f"Cannot serialize object {obj}.")
    except Exception as e:
        logger.error(f"Serialization of object {obj} failed due to {e}")
    return d