From 12b7ec27dd2f7e6d69928481a904079508d0af91 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 15:59:23 +0100 Subject: [PATCH 1/9] fixed how n_samples affects params Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index 1bb68f6..b7448d4 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -33,22 +33,27 @@ def meyer_wallach( assert seed is not None, "Seed must be provided when samples > 0" # TODO: maybe switch to JAX rng model.initialize_params(rng=rng, repeat=n_samples) + params = model.params else: if seed is not None: log.warning("Seed is ignored when samples is 0") - n_samples = 1 - model.initialize_params(rng=rng, repeat=1) - samples = model.params.shape[-1] - mw_measure = np.zeros(samples, dtype=complex) + if len(model.params.shape) <= 2: + params = model.params.reshape(*model.params.shape, 1) + else: + log.info(f"Using sample size of model params: {model.params.shape[-1]}") + params = model.params + + n_samples = params.shape[-1] + mw_measure = np.zeros(n_samples, dtype=complex) qb = list(range(model.n_qubits)) # TODO: vectorize in future iterations - for i in range(samples): + for i in range(n_samples): # implicitly set input to none in case it's not needed kwargs.setdefault("inputs", None) # explicitly set execution type because everything else won't work - U = model(params=model.params[:, :, i], execution_type="density", **kwargs) + U = model(params=params[:, :, i], execution_type="density", **kwargs) entropy = 0 @@ -58,7 +63,7 @@ def meyer_wallach( mw_measure[i] = 1 - entropy / model.n_qubits - mw = 2 * np.sum(mw_measure.real) / samples + mw = 2 * np.sum(mw_measure.real) / n_samples # catch floating point errors entangling_capability = min(max(mw, 0.0), 1.0) From e2dac342863be8862e6c7655d90234c43cb72206 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 16:00:48 +0100 Subject: [PATCH 2/9] docs Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index b7448d4..7bcd6e0 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -12,7 +12,7 @@ class Entanglement: @staticmethod def meyer_wallach( - model: Model, n_samples: int, seed: Optional[int], **kwargs: Any # type: ignore + model: Model, n_samples: Optional[int | None], seed: Optional[int], **kwargs: Any # type: ignore ) -> float: """ Calculates the entangling capacity of a given quantum circuit @@ -21,6 +21,7 @@ def meyer_wallach( Args: model (Callable): Function that models the quantum circuit. n_samples (int): Number of samples per qubit. + If None or < 0, the current parameters of the model are used seed (Optional[int]): Seed for the random number generator. kwargs (Any): Additional keyword arguments for the model function. @@ -29,7 +30,7 @@ def meyer_wallach( to be between 0.0 and 1.0. """ rng = np.random.default_rng(seed) - if n_samples > 0: + if n_samples is not None and n_samples > 0: assert seed is not None, "Seed must be provided when samples > 0" # TODO: maybe switch to JAX rng model.initialize_params(rng=rng, repeat=n_samples) From 8f7bc9e9843c56105a7fceb477d4de1166bea0a5 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 16:01:05 +0100 Subject: [PATCH 3/9] updating model parameters Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 2030dec..082bd80 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -533,6 +533,9 @@ def _forward( if execution_type is not None: self.execution_type = execution_type + # TODO: dis is important! check dis! + self.params = params if params is not None else self.params + # the qasm representation contains the bound parameters, # thus it is ok to hash that hs = hashlib.md5( @@ -542,7 +545,7 @@ def _forward( "n_layers": self.n_layers, "pqc": self.pqc.__class__.__name__, "dru": self.data_reupload, - "params": params, + "params": self.params, "noise_params": self.noise_params, "execution_type": self.execution_type, "inputs": inputs, @@ -568,7 +571,7 @@ def _forward( # if density matrix requested or noise params used if self.execution_type == "density" or self.noise_params is not None: result = self.circuit_mixed( - params=params, + params=self.params, inputs=inputs, ) else: @@ -578,7 +581,7 @@ def _forward( ) else: result = self.circuit( - params=params, + params=self.params, inputs=inputs, ) From a15f12db2e449344f597ad66ea3e70ccd706741a Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 16:12:27 +0100 Subject: [PATCH 4/9] qa Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index 7bcd6e0..ab5bdf1 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -12,7 +12,10 @@ class Entanglement: @staticmethod def meyer_wallach( - model: Model, n_samples: Optional[int | None], seed: Optional[int], **kwargs: Any # type: ignore + model: Model, + n_samples: Optional[int | None], + seed: Optional[int], + **kwargs: Any, ) -> float: """ Calculates the entangling capacity of a given quantum circuit From ce239cb048fff5e125c3b7ad21c9a47cd8e2deee Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 16:19:16 +0100 Subject: [PATCH 5/9] updated docstring Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 082bd80..0f33d3e 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -441,8 +441,8 @@ def __str__(self) -> str: def __call__( self, - params: np.ndarray, - inputs: np.ndarray, + params: Optional[np.ndarray] = None, + inputs: Optional[np.ndarray] = None, noise_params: Optional[Dict[str, float]] = None, cache: Optional[bool] = False, execution_type: Optional[str] = None, @@ -452,9 +452,11 @@ def __call__( Perform a forward pass of the quantum circuit. Args: - params (np.ndarray): Weight vector of shape + params (Optional[np.ndarray]): Weight vector of shape [n_layers, n_qubits*n_params_per_layer]. - inputs (np.ndarray): Input vector of shape [1]. + If None, model internal parameters are used. + inputs (Optional[np.ndarray]): Input vector of shape [1]. + If None, zeros are used. noise_params (Optional[Dict[str, float]], optional): The noise parameters. Defaults to None which results in the last set noise parameters being used. @@ -488,8 +490,8 @@ def __call__( def _forward( self, - params: np.ndarray, - inputs: np.ndarray, + params: Optional[np.ndarray] = None, + inputs: Optional[np.ndarray] = None, noise_params: Optional[Dict[str, float]] = None, cache: Optional[bool] = False, execution_type: Optional[str] = None, @@ -499,9 +501,11 @@ def _forward( Perform a forward pass of the quantum circuit. Args: - params (np.ndarray): Weight vector of shape + params (Optional[np.ndarray]): Weight vector of shape [n_layers, n_qubits*n_params_per_layer]. - inputs (np.ndarray): Input vector of shape [1]. + If None, model internal parameters are used. + inputs (Optional[np.ndarray]): Input vector of shape [1]. + If None, zeros are used. noise_params (Optional[Dict[str, float]], optional): The noise parameters. Defaults to None which results in the last set noise parameters being used. From 73bbfec6800ec6a6a7ce2195c16edcbfb6f5b5d2 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 16:19:20 +0100 Subject: [PATCH 6/9] added test Signed-off-by: Melvin Strobl --- tests/test_model.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index 0333f9e..fa792cd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -81,6 +81,14 @@ def test_parameters() -> None: }, ] + # Test the most minimal call + model = Model( + n_qubits=2, + n_layers=1, + circuit_type="Circuit_19", + ) + assert (model() == model(model.params)).all() + for test_case in test_cases: model = Model( n_qubits=2, From 019b88a2eb1b9b5b41c427f174f18294bcd0b5b6 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Wed, 6 Nov 2024 21:08:45 +0100 Subject: [PATCH 7/9] fixed arraybox issues Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 13 ++++++++----- tests/test_model.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 0f33d3e..60098db 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -4,6 +4,7 @@ import hashlib import os import warnings +from autograd.numpy import numpy_boxes from qml_essentials.ansaetze import Ansaetze, Circuit @@ -537,8 +538,10 @@ def _forward( if execution_type is not None: self.execution_type = execution_type - # TODO: dis is important! check dis! - self.params = params if params is not None else self.params + if numpy_boxes.ArrayBox == type(params): + self.params = params._value + else: + self.params = params # the qasm representation contains the bound parameters, # thus it is ok to hash that @@ -549,7 +552,7 @@ def _forward( "n_layers": self.n_layers, "pqc": self.pqc.__class__.__name__, "dru": self.data_reupload, - "params": self.params, + "params": self.params, # use safe-params "noise_params": self.noise_params, "execution_type": self.execution_type, "inputs": inputs, @@ -575,7 +578,7 @@ def _forward( # if density matrix requested or noise params used if self.execution_type == "density" or self.noise_params is not None: result = self.circuit_mixed( - params=self.params, + params=params, # use arraybox params inputs=inputs, ) else: @@ -585,7 +588,7 @@ def _forward( ) else: result = self.circuit( - params=self.params, + params=params, # use arraybox params inputs=inputs, ) diff --git a/tests/test_model.py b/tests/test_model.py index fa792cd..c451d01 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -653,3 +653,18 @@ def test_parity() -> None: assert not np.allclose( result_a, result_b ), f"Models should be different! Got {result_a} and {result_b}" + + +@pytest.mark.smoketest +def test_params_store() -> None: + model = Model( + n_qubits=2, + n_layers=1, + circuit_type="Circuit_1", + ) + opt = qml.AdamOptimizer(stepsize=0.01) + + def cost(params): + return model(params=params, inputs=np.array([0])).mean()._value + + params, cost = opt.step_and_cost(cost, model.params) From 335ec6cc046e3b7ea505fa76974ed736bac70f28 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Thu, 7 Nov 2024 11:04:49 +0100 Subject: [PATCH 8/9] catching none Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 60098db..e935868 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -538,10 +538,13 @@ def _forward( if execution_type is not None: self.execution_type = execution_type - if numpy_boxes.ArrayBox == type(params): - self.params = params._value + if params is None: + params = self.params else: - self.params = params + if numpy_boxes.ArrayBox == type(params): + self.params = params._value + else: + self.params = params # the qasm representation contains the bound parameters, # thus it is ok to hash that From 57a03a2764af59bb23b1dc3e83cbc20e6a3f01a4 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Fri, 8 Nov 2024 14:02:03 +0100 Subject: [PATCH 9/9] bumped version Signed-off-by: Melvin Strobl --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 82ccb3a..e823371 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "qml-essentials" -version = "0.1.15" +version = "0.1.16" description = "" authors = ["Melvin Strobl ", "Maja Franz "] readme = "README.md"