From 238c94564299d01d25b1f677e04880c708fc3ad7 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 10:26:21 +0200 Subject: [PATCH 01/18] removed redundand badge Signed-off-by: Melvin Strobl --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c13e75..f017c3d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # QML Essentials -[![version](https://img.shields.io/badge/version-0.1.12-green.svg)](https://ea3a0fbb-599f-4d83-86f1-0e71abe27513.ka.bw-cloud-instance.org/lc3267/quantum/) [![Quality](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml) [![Testing](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml) [![Documentation](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml) +[![Quality](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/quality.yml) [![Testing](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/test.yml) [![Documentation](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml/badge.svg)](https://github.com/cirKITers/qml-essentials/actions/workflows/docs.yml) ## 📜 About @@ -22,4 +22,4 @@ You can find details on how to use it and further documentation on the correspon ## 🚧 Contributing -Contributions are very welcome! See [Contribution Guidelines](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md). \ No newline at end of file +Contributions are highly welcome! Take a look at our [Contribution Guidelines](https://github.com/cirKITers/qml-essentials/blob/main/CONTRIBUTING.md). \ No newline at end of file From 95e3b52dd63561bff8435f70dc454efdcdbc7528 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 11:00:37 +0200 Subject: [PATCH 02/18] removed redundant step Signed-off-by: Melvin Strobl --- CONTRIBUTING.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2d7d91..f54d5d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,17 +46,6 @@ Publishing includes - creating a release with the current git tag and automatically generated release notes - publishing the package to PyPI using the stored credentials -## Re-Installing Package - -If you want to overwrite the latest build you can simply push to the package index with the recent changes. -Updating from this index however is then a bit tricky, because poetry keeps a cache of package metadata. -To overwrite an already installed package with the same version (but different content) follow these steps: -1. `poetry remove qml_essentials` -2. `poetry cache clear quantum --all` -3. `poetry add qml_essentials@latest` (or a specific version) - -Note that this will also re-evaluate parts of other dependencies, and thus may change the `poetry.lock` file significantly. - ## Documentation We use MkDocs for our documentation. To run a server locally, run: From a5e85c78231cdbe1dd16d6e76e86570b406a17c9 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 11:02:36 +0200 Subject: [PATCH 03/18] updated welcome Signed-off-by: Melvin Strobl --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f54d5d2..d4ac19b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,11 @@ # Contributing to QML-Essentials -:tada: Welcome! :tada: +Contributions are highly welcome! :hugging_face: -Contributions are highly welcome! Start of by.. 1. Creating an issue using one of the templates (Bug Report, Feature Request) - let's discuss what's going wrong or what should be added - - can you contribute with code? Great! Go ahead! :rocket: + - can you contribute with code? Great! Go ahead! :rocket: 2. Forking the repository and working on your stuff. See the sections below for details on how to set things up. 3. Creating a pull request to the main repository From 5cbc68479075515c61746ee15eab747637a0d473 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 13:58:38 +0200 Subject: [PATCH 04/18] added domain Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 0f950fc..0de79ff 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -24,6 +24,7 @@ def __init__( circuit_type: Union[str, Circuit], data_reupload: bool = True, initialization: str = "random", + initialization_domain: List[float] = [0, 2 * np.pi], output_qubit: Union[List[int], int] = -1, shots: Optional[int] = None, random_seed: int = 1000, @@ -100,9 +101,10 @@ def __init__( # this will also be re-used in the init method, # however, only if nothing is provided self._inialization_strategy = initialization + self._initialization_domain = initialization_domain - # ..here! where we only require a seed - self.initialize_params(random_seed) + # ..here! where we only require a rng + self.initialize_params(np.random.default_rng(random_seed)) # Initialize two circuits, one with the default device and # one with the mixed device @@ -205,9 +207,19 @@ def shots(self, value: Optional[int]) -> None: value = None self._shots = value - def initialize_params(self, random_seed, initialization: str = None) -> None: + def initialize_params( + self, + rng, + repeat: int = None, + initialization: str = None, + initialization_domain: List[float] = None, + ) -> None: + params_shape = ( + self._params_shape if repeat is None else [*self._params_shape, repeat] + ) # use existing strategy if not specified initialization = initialization or self._inialization_strategy + initialization_domain = initialization_domain or self._initialization_domain def set_control_params(params: np.ndarray, value: float) -> np.ndarray: indices = self.pqc.get_control_indices(self.n_qubits) @@ -225,25 +237,22 @@ def set_control_params(params: np.ndarray, value: float) -> np.ndarray: ) return params - rng = np.random.default_rng(random_seed) if initialization == "random": self.params: np.ndarray = rng.uniform( - 0, 2 * np.pi, self._params_shape, requires_grad=True + *initialization_domain, params_shape, requires_grad=True ) elif initialization == "zeros": - self.params: np.ndarray = np.zeros(self._params_shape, requires_grad=True) + self.params: np.ndarray = np.zeros(params_shape, requires_grad=True) elif initialization == "pi": - self.params: np.ndarray = ( - np.ones(self._params_shape, requires_grad=True) * np.pi - ) + self.params: np.ndarray = np.ones(params_shape, requires_grad=True) * np.pi elif initialization == "zero-controlled": self.params: np.ndarray = rng.uniform( - 0, 2 * np.pi, self._params_shape, requires_grad=True + *initialization_domain, params_shape, requires_grad=True ) self.params = set_control_params(self.params, 0) elif initialization == "pi-controlled": self.params: np.ndarray = rng.uniform( - 0, 2 * np.pi, self._params_shape, requires_grad=True + *initialization_domain, params_shape, requires_grad=True ) self.params = set_control_params(self.params, np.pi) else: @@ -254,6 +263,8 @@ def set_control_params(params: np.ndarray, value: float) -> np.ndarray: using strategy {initialization}." ) + return self.params + def _iec( self, inputs: np.ndarray, From ce261a37d8c12d96243cab28a78412496dffcb32 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 13:58:50 +0200 Subject: [PATCH 05/18] updated entanglement calculation Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index 70fa82f..5a77292 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -59,7 +59,7 @@ def _meyer_wallach( guaranteed to be between 0.0 and 1.0 """ assert ( - params.shape[0] == samples + params.shape[-1] == samples ), "Number of samples does not match number of parameters" mw_measure = np.zeros(samples, dtype=complex) @@ -69,7 +69,7 @@ def _meyer_wallach( # 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 = evaluate(params=params[i], execution_type="density", **kwargs) + U = evaluate(params=params[:, :, i], execution_type="density", **kwargs) entropy = 0 @@ -88,7 +88,8 @@ def _meyer_wallach( assert seed is not None, "Seed must be provided when samples > 0" # TODO: maybe switch to JAX rng rng = np.random.default_rng(seed) - params = rng.uniform(0, 2 * np.pi, size=(n_samples, *model.params.shape)) + # params = rng.uniform(0, 2 * np.pi, size=(n_samples, *model.params.shape)) + params = model.initialize_params(rng=rng, repeat=n_samples) else: if seed is not None: log.warning("Seed is ignored when samples is 0") From c360ea9baed7d8d3bfa4ffbf1460d7fece87e88c Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 13:59:00 +0200 Subject: [PATCH 06/18] updated expressibility calculation Signed-off-by: Melvin Strobl --- qml_essentials/expressibility.py | 11 ++++++----- tests/test_entanglement.py | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index 4c09d5d..e0233c1 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -43,10 +43,11 @@ def _sample_state_fidelities( fidelities: np.ndarray = np.zeros((n_x_samples, n_samples)) # Generate random parameter sets - w: np.ndarray = ( - 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, n_samples * 2])) - ) - + # w: np.ndarray = ( + # 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, n_samples * 2])) + # ) + # TODO: why do we need *2? is there anything important regarding the shift abovce? + model.initialize_params(rng=rng, repeat=n_samples * 2) # Batch input samples and parameter sets for efficient computation x_samples_batched: np.ndarray = x_samples.reshape(1, -1).repeat( n_samples * 2, axis=0 @@ -59,7 +60,7 @@ def _sample_state_fidelities( # Execution type is explicitly set to density sv: np.ndarray = model( inputs=x_samples_batched[:, idx], - params=w, + params=model.params, execution_type="density", **kwargs, ) diff --git a/tests/test_entanglement.py b/tests/test_entanglement.py index 8fd4752..5f06776 100644 --- a/tests/test_entanglement.py +++ b/tests/test_entanglement.py @@ -22,8 +22,8 @@ def test_entanglement() -> None: "circuit_type": "Strongly_Entangling", "n_qubits": 2, "n_layers": 1, - "n_samples": 1000, - "result": 0.3985, + "n_samples": 2000, + "result": 0.3912, }, ] From 9d724a30c09a78d68df8d9cbf80cfec3c9a4ddb4 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 14:22:52 +0200 Subject: [PATCH 07/18] cleanup Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 97 ++++++++++------------------------ 1 file changed, 29 insertions(+), 68 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index 5a77292..29af7a8 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -2,6 +2,7 @@ import pennylane as qml import pennylane.numpy as np +from qml_essentials.model import Model import logging log = logging.getLogger(__name__) @@ -11,20 +12,14 @@ class Entanglement: @staticmethod def meyer_wallach( - model: Callable, # type: ignore - n_samples: int, - seed: Optional[int], - **kwargs: Any + model: Model, n_samples: int, seed: Optional[int], **kwargs: Any # type: ignore ) -> float: """ Calculates the entangling capacity of a given quantum circuit using Meyer-Wallach measure. Args: - model (Callable): Function that models the quantum circuit. It must - have a `n_qubits` attribute representing the number of qubits. - It must accept a `params` argument representing the parameters - of the circuit. + model (Callable): Function that models the quantum circuit. n_samples (int): Number of samples per qubit. seed (Optional[int]): Seed for the random number generator. kwargs (Any): Additional keyword arguments for the model function. @@ -33,74 +28,40 @@ def meyer_wallach( float: Entangling capacity of the given circuit. It is guaranteed to be between 0.0 and 1.0. """ - - def _meyer_wallach( - evaluate: Callable[[np.ndarray], np.ndarray], - n_qubits: int, - samples: int, - params: np.ndarray, - ) -> float: - """ - Calculates the Meyer-Wallach sampling of the entangling capacity - of a quantum circuit. - - Args: - evaluate (Callable[[np.ndarray], np.ndarray]): Callable that - evaluates the quantum circuit It must accept a `params` - argument representing the parameters of the circuit and may - accept additional keyword arguments. - n_qubits (int): Number of qubits in the circuit - samples (int): Number of samples to be taken - params (np.ndarray): Parameters of the instructor. Shape: - (samples, *model.params.shape) - - Returns: - float: Entangling capacity of the given circuit. It is - guaranteed to be between 0.0 and 1.0 - """ - assert ( - params.shape[-1] == samples - ), "Number of samples does not match number of parameters" - - mw_measure = np.zeros(samples, dtype=complex) - qb = list(range(n_qubits)) - - for i in range(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 = evaluate(params=params[:, :, i], execution_type="density", **kwargs) - - entropy = 0 - - for j in range(n_qubits): - density = qml.math.partial_trace(U, qb[:j] + qb[j + 1 :]) - entropy += np.trace((density @ density).real) - - mw_measure[i] = 1 - entropy / n_qubits - - mw = 2 * np.sum(mw_measure.real) / samples - - # catch floating point errors - return min(max(mw, 0.0), 1.0) - + rng = np.random.default_rng(seed) if n_samples > 0: assert seed is not None, "Seed must be provided when samples > 0" # TODO: maybe switch to JAX rng - rng = np.random.default_rng(seed) # params = rng.uniform(0, 2 * np.pi, size=(n_samples, *model.params.shape)) - params = model.initialize_params(rng=rng, repeat=n_samples) + model.initialize_params(rng=rng, repeat=n_samples) else: if seed is not None: log.warning("Seed is ignored when samples is 0") n_samples = 1 - params = model.params.reshape(1, *model.params.shape) + model.initialize_params(rng=rng, repeat=1) + + samples = model.params.shape[-1] + mw_measure = np.zeros(samples, dtype=complex) + qb = list(range(model.n_qubits)) + + # TODO: vectorize in future iterations + for i in range(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) + + entropy = 0 + + for j in range(model.n_qubits): + density = qml.math.partial_trace(U, qb[:j] + qb[j + 1 :]) + entropy += np.trace((density @ density).real) + + mw_measure[i] = 1 - entropy / model.n_qubits + + mw = 2 * np.sum(mw_measure.real) / samples - entangling_capability = _meyer_wallach( - evaluate=model, - n_qubits=model.n_qubits, - samples=n_samples, - params=params, - ) + # catch floating point errors + entangling_capability = min(max(mw, 0.0), 1.0) return float(entangling_capability) From 0c5516dd54e617a17ca93813070488f1e0f4f0ca Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 14:22:59 +0200 Subject: [PATCH 08/18] cleanup Signed-off-by: Melvin Strobl --- qml_essentials/expressibility.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index e0233c1..8671b9f 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -4,13 +4,13 @@ from scipy.special import rel_entr import os +from qml_essentials.model import Model + class Expressibility: @staticmethod def _sample_state_fidelities( - model: Callable[ - [np.ndarray, np.ndarray], np.ndarray - ], # type: ignore[name-defined] + model: Model, # type: ignore[name-defined] x_samples: np.ndarray, n_samples: int, seed: int, @@ -20,10 +20,7 @@ def _sample_state_fidelities( Compute the fidelities for each pair of input samples and parameter sets. Args: - model (Callable[[np.ndarray, np.ndarray], np.ndarray]): - Function that evaluates the model. It must accept inputs - and params as arguments - and return an array of shape (n_samples, n_features). + model (Callable): Function that models the quantum circuit. x_samples (np.ndarray): Array of shape (n_input_samples, n_features) containing the input samples. n_samples (int): Number of parameter sets to generate. From cd8ab8b24dbe038de586b9d1513befe4a912ef9f Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 14:23:07 +0200 Subject: [PATCH 09/18] added docstring Signed-off-by: Melvin Strobl --- qml_essentials/model.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 0de79ff..5fcf214 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -214,6 +214,18 @@ def initialize_params( initialization: str = None, initialization_domain: List[float] = None, ) -> None: + """ + Initializes the parameters of the model. + + Args: + rng: A random number generator to use for initialization. + repeat: The number of times to repeat the parameters. If None, the number of layers is used. + initialization: The strategy to use for parameter initialization. If None, the strategy specified in the constructor is used. + initialization_domain: The domain to use for parameter initialization. If None, the domain specified in the constructor is used. + + Returns: + None + """ params_shape = ( self._params_shape if repeat is None else [*self._params_shape, repeat] ) @@ -263,8 +275,6 @@ def set_control_params(params: np.ndarray, value: float) -> np.ndarray: using strategy {initialization}." ) - return self.params - def _iec( self, inputs: np.ndarray, From 1a9b6c98088b675e4cfe2db59a83cc92cb649fb9 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 14:59:15 +0200 Subject: [PATCH 10/18] adjusted values due to init range Signed-off-by: Melvin Strobl --- tests/test_expressiblity.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/test_expressiblity.py b/tests/test_expressiblity.py index 9722cd8..f9e1fa1 100644 --- a/tests/test_expressiblity.py +++ b/tests/test_expressiblity.py @@ -46,21 +46,21 @@ def test_expressibility() -> None: test_cases = [ { "circuit_type": "Circuit_1", - "n_qubits": 2, - "n_layers": 3, + "n_qubits": 3, + "n_layers": 1, "n_bins": 10, - "n_samples": 200, - "n_input_samples": 2, - "result": 1.858, + "n_samples": 400, + "n_input_samples": 10, + "result": 2.905, }, { "circuit_type": "Circuit_9", - "n_qubits": 2, - "n_layers": 3, + "n_qubits": 3, + "n_layers": 1, "n_bins": 10, - "n_samples": 200, - "n_input_samples": 2, - "result": 2.629, + "n_samples": 400, + "n_input_samples": 10, + "result": 6.641, }, ] @@ -69,9 +69,6 @@ def test_expressibility() -> None: n_qubits=test_case["n_qubits"], n_layers=test_case["n_layers"], circuit_type=test_case["circuit_type"], - data_reupload=True, - initialization="random", - output_qubit=0, ) _, _, z = Expressibility.state_fidelities( @@ -80,13 +77,11 @@ def test_expressibility() -> None: n_input_samples=test_case["n_input_samples"], seed=1000, model=model, - cache=False, ) _, y_haar = Expressibility.haar_integral( n_qubits=test_case["n_qubits"], n_bins=test_case["n_bins"], - cache=False, ) # Calculate the mean (over all inputs, if required) From a5e6ac9e06700de906412a1807015ed9bb8a9bd3 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:06:22 +0200 Subject: [PATCH 11/18] added re-init test Signed-off-by: Melvin Strobl --- tests/test_model.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/test_model.py b/tests/test_model.py index 318553d..84e033a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,7 +1,6 @@ from qml_essentials.model import Model from qml_essentials.ansaetze import Ansaetze, Circuit import pytest -import numpy as np import logging import inspect import shutil @@ -9,6 +8,7 @@ import hashlib from typing import Optional import pennylane as qml +import pennylane.numpy as np logger = logging.getLogger(__name__) @@ -207,6 +207,27 @@ def test_initialization() -> None: ) +@pytest.mark.unittest +def test_re_initialization() -> None: + model = Model( + n_qubits=2, + n_layers=1, + circuit_type="Circuit_19", + initialization_domain=[-2 * np.pi, 0], + random_seed=1000, + ) + + assert model.params.max() <= 0, "Parameters should be in [-2pi, 0]!" + + temp_params = model.params.copy() + + model.initialize_params(rng=np.random.default_rng(seed=1001)) + + assert not np.allclose( + model.params, temp_params, atol=1e-3 + ), "Re-Initialization failed!" + + @pytest.mark.smoketest def test_ansaetze() -> None: ansatz_cases = Ansaetze.get_available() From 04b4dd7cbbc62c5cc9d637d84ea8f0de2411ec30 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:09:59 +0200 Subject: [PATCH 12/18] added no sampling test Signed-off-by: Melvin Strobl --- tests/test_entanglement.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_entanglement.py b/tests/test_entanglement.py index 5f06776..a354f29 100644 --- a/tests/test_entanglement.py +++ b/tests/test_entanglement.py @@ -45,3 +45,16 @@ def test_entanglement() -> None: ), f"Entangling capacity is not {test_case['result']}\ for circuit ansatz {test_case['circuit_type']}.\ Was {ent_cap} instead" + + +@pytest.mark.smoketest +def test_no_sampling() -> None: + model = Model( + n_qubits=2, + n_layers=1, + circuit_type="Hardware_Efficient", + data_reupload=True, + initialization="random", + ) + + _ = Entanglement.meyer_wallach(model, n_samples=-1, seed=1000, cache=False) From 70ccc915acfbcc6e1ba8b07802f60618e27f0a02 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:11:59 +0200 Subject: [PATCH 13/18] fixed qa Signed-off-by: Melvin Strobl --- qml_essentials/expressibility.py | 6 ++++-- qml_essentials/model.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index 8671b9f..ab19e62 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -41,7 +41,8 @@ def _sample_state_fidelities( # Generate random parameter sets # w: np.ndarray = ( - # 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, n_samples * 2])) + # 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, \ + # n_samples * 2])) # ) # TODO: why do we need *2? is there anything important regarding the shift abovce? model.initialize_params(rng=rng, repeat=n_samples * 2) @@ -91,7 +92,8 @@ def state_fidelities( Args: n_bins (int): Number of histogram bins. n_samples (int): Number of parameter sets to generate. - n_input_samples (int): Number of samples for the input domain in [-pi, pi] + n_input_samples (int): Number of samples for the input domain + in [-pi, pi] seed (int): Random number generator seed. model (Callable): Function that models the quantum circuit. kwargs (Any): Additional keyword arguments for the model function. diff --git a/qml_essentials/model.py b/qml_essentials/model.py index 5fcf214..7e54d10 100644 --- a/qml_essentials/model.py +++ b/qml_essentials/model.py @@ -219,9 +219,12 @@ def initialize_params( Args: rng: A random number generator to use for initialization. - repeat: The number of times to repeat the parameters. If None, the number of layers is used. - initialization: The strategy to use for parameter initialization. If None, the strategy specified in the constructor is used. - initialization_domain: The domain to use for parameter initialization. If None, the domain specified in the constructor is used. + repeat: The number of times to repeat the parameters. + If None, the number of layers is used. + initialization: The strategy to use for parameter initialization. + If None, the strategy specified in the constructor is used. + initialization_domain: The domain to use for parameter initialization. + If None, the domain specified in the constructor is used. Returns: None From 82489445cd80d1c76e81e8bcfb594a8e2e99eac6 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:16:43 +0200 Subject: [PATCH 14/18] fixed qa rev2 Signed-off-by: Melvin Strobl --- qml_essentials/entanglement.py | 2 +- qml_essentials/expressibility.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index 29af7a8..b5b769e 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -1,4 +1,4 @@ -from typing import Callable, Optional, Any +from typing import Optional, Any import pennylane as qml import pennylane.numpy as np diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index ab19e62..69ac50e 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -44,7 +44,8 @@ def _sample_state_fidelities( # 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, \ # n_samples * 2])) # ) - # TODO: why do we need *2? is there anything important regarding the shift abovce? + # TODO: why do we need *2? is there anything important + # regarding the shift abovce? model.initialize_params(rng=rng, repeat=n_samples * 2) # Batch input samples and parameter sets for efficient computation x_samples_batched: np.ndarray = x_samples.reshape(1, -1).repeat( From 0211db96c2316998ef80dab5476bf1e57a5d2a95 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:32:58 +0200 Subject: [PATCH 15/18] updated docs Signed-off-by: Melvin Strobl --- docs/entanglement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/entanglement.md b/docs/entanglement.md index e497418..800c5f4 100644 --- a/docs/entanglement.md +++ b/docs/entanglement.md @@ -20,7 +20,7 @@ ent_cap = Entanglement.meyer_wallach( ) ``` -Here, `n_samples` is the number of samples for the input domain in $[0, 2\pi]$, and `seed` is the random number generator seed. +Here, `n_samples` is the number of samples for the parameters, sampled according to the default initialization strategy of the model, and `seed` is the random number generator seed. Note, that every function in this class accepts keyword-arguments which are being passed to the model call, so you could e.g. enable caching by ```python ent_cap = Entanglement.meyer_wallach( From 3de03c18644984c5361b48f081f1a14486f45fae Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:33:04 +0200 Subject: [PATCH 16/18] updated docs Signed-off-by: Melvin Strobl --- docs/expressibility.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/expressibility.md b/docs/expressibility.md index 25b4481..bbbc856 100644 --- a/docs/expressibility.md +++ b/docs/expressibility.md @@ -9,15 +9,16 @@ model = Model( ) input_domain, bins, dist_circuit = Expressibility.state_fidelities( - n_bins=10, + seed=1000, n_samples=200, + n_bins=10, n_input_samples=5, - seed=1000, + input_domain=[0, 2*np.pi], model=model, ) ``` -Here, `n_bins` is the number of bins that you want to use in the histogram, `n_samples` is the number of parameter sets to generate, `n_input_samples` is the number of samples for the input domain in $[-\pi, \pi]$, and `seed` is the random number generator seed. +Here, `n_bins` is the number of bins that you want to use in the histogram, `n_samples` is the number of parameter sets to generate (using the default initialization strategy of the model), `n_input_samples` is the number of samples for the input domain in $[0, 2\pi]$, and `seed` is the random number generator seed. Note that `state_fidelities` accepts keyword arguments that are being passed to the model call. This allows you to utilize e.g. caching. From 7294041c6e061fffa7ea36864f260c4aa0291e79 Mon Sep 17 00:00:00 2001 From: Melvin Strobl Date: Mon, 7 Oct 2024 15:33:14 +0200 Subject: [PATCH 17/18] added input domain as required parameter Signed-off-by: Melvin Strobl --- qml_essentials/expressibility.py | 22 +++++++++++----------- tests/test_expressiblity.py | 6 ++++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index 69ac50e..14e0eb3 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -1,5 +1,5 @@ import pennylane.numpy as np -from typing import Tuple, Callable, Any +from typing import Tuple, List, Any from scipy import integrate from scipy.special import rel_entr import os @@ -10,7 +10,7 @@ class Expressibility: @staticmethod def _sample_state_fidelities( - model: Model, # type: ignore[name-defined] + model: Model, x_samples: np.ndarray, n_samples: int, seed: int, @@ -80,22 +80,23 @@ def _sample_state_fidelities( @staticmethod def state_fidelities( - n_bins: int, + seed: int, n_samples: int, + n_bins: int, n_input_samples: int, - seed: int, - model: Callable, # type: ignore + input_domain: List[float], + model: Model, **kwargs: Any, ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Sample the state fidelities and histogram them into a 2D array. Args: - n_bins (int): Number of histogram bins. - n_samples (int): Number of parameter sets to generate. - n_input_samples (int): Number of samples for the input domain - in [-pi, pi] seed (int): Random number generator seed. + n_samples (int): Number of parameter sets to generate. + n_bins (int): Number of histogram bins. + n_input_samples (int): Number of input samples. + input_domain (List[float]): Input domain. model (Callable): Function that models the quantum circuit. kwargs (Any): Additional keyword arguments for the model function. @@ -104,8 +105,7 @@ def state_fidelities( input samples, bin edges, and histogram values. """ - x_domain = [-1 * np.pi, 1 * np.pi] - x = np.linspace(x_domain[0], x_domain[1], n_input_samples, requires_grad=False) + x = np.linspace(*input_domain, n_input_samples, requires_grad=False) fidelities = Expressibility._sample_state_fidelities( x_samples=x, diff --git a/tests/test_expressiblity.py b/tests/test_expressiblity.py index f9e1fa1..d7b8671 100644 --- a/tests/test_expressiblity.py +++ b/tests/test_expressiblity.py @@ -1,6 +1,7 @@ from qml_essentials.model import Model from qml_essentials.expressibility import Expressibility +import pennylane.numpy as np import logging import math import pytest @@ -60,7 +61,7 @@ def test_expressibility() -> None: "n_bins": 10, "n_samples": 400, "n_input_samples": 10, - "result": 6.641, + "result": 6.670, }, ] @@ -72,10 +73,11 @@ def test_expressibility() -> None: ) _, _, z = Expressibility.state_fidelities( + seed=1000, n_bins=test_case["n_bins"], n_samples=test_case["n_samples"], n_input_samples=test_case["n_input_samples"], - seed=1000, + input_domain=[0, 2 * np.pi], model=model, ) From bc68951e6b907b7327584e3d14543818f245c962 Mon Sep 17 00:00:00 2001 From: majafranz Date: Tue, 8 Oct 2024 08:36:32 +0200 Subject: [PATCH 18/18] cleanup and comment on duplicate samples --- qml_essentials/entanglement.py | 1 - qml_essentials/expressibility.py | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/qml_essentials/entanglement.py b/qml_essentials/entanglement.py index b5b769e..1bb68f6 100644 --- a/qml_essentials/entanglement.py +++ b/qml_essentials/entanglement.py @@ -32,7 +32,6 @@ def meyer_wallach( if n_samples > 0: assert seed is not None, "Seed must be provided when samples > 0" # TODO: maybe switch to JAX rng - # params = rng.uniform(0, 2 * np.pi, size=(n_samples, *model.params.shape)) model.initialize_params(rng=rng, repeat=n_samples) else: if seed is not None: diff --git a/qml_essentials/expressibility.py b/qml_essentials/expressibility.py index 14e0eb3..924ef67 100644 --- a/qml_essentials/expressibility.py +++ b/qml_essentials/expressibility.py @@ -40,12 +40,8 @@ def _sample_state_fidelities( fidelities: np.ndarray = np.zeros((n_x_samples, n_samples)) # Generate random parameter sets - # w: np.ndarray = ( - # 2 * np.pi * (1 - 2 * rng.random(size=[*model.params.shape, \ - # n_samples * 2])) - # ) - # TODO: why do we need *2? is there anything important - # regarding the shift abovce? + # We need two sets of parameters, as we are computing fidelities for a + # pair of random state vectors model.initialize_params(rng=rng, repeat=n_samples * 2) # Batch input samples and parameter sets for efficient computation x_samples_batched: np.ndarray = x_samples.reshape(1, -1).repeat(