Skip to content

Commit

Permalink
Merge pull request #58 from cirKITers/ent_param_init
Browse files Browse the repository at this point in the history
Ent param init
  • Loading branch information
stroblme authored Nov 8, 2024
2 parents 04fc961 + 57a03a2 commit 3fc704b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 21 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "qml-essentials"
version = "0.1.15"
version = "0.1.16"
description = ""
authors = ["Melvin Strobl <[email protected]>", "Maja Franz <[email protected]>"]
readme = "README.md"
Expand Down
27 changes: 18 additions & 9 deletions qml_essentials/entanglement.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ 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,
) -> float:
"""
Calculates the entangling capacity of a given quantum circuit
Expand All @@ -21,6 +24,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.
Expand All @@ -29,26 +33,31 @@ 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)
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

Expand All @@ -58,7 +67,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)
Expand Down
35 changes: 24 additions & 11 deletions qml_essentials/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import hashlib
import os
import warnings
from autograd.numpy import numpy_boxes

from qml_essentials.ansaetze import Ansaetze, Circuit

Expand Down Expand Up @@ -441,8 +442,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,
Expand All @@ -452,9 +453,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.
Expand Down Expand Up @@ -488,8 +491,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,
Expand All @@ -499,9 +502,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.
Expand Down Expand Up @@ -533,6 +538,14 @@ def _forward(
if execution_type is not None:
self.execution_type = execution_type

if params is None:
params = self.params
else:
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
hs = hashlib.md5(
Expand All @@ -542,7 +555,7 @@ def _forward(
"n_layers": self.n_layers,
"pqc": self.pqc.__class__.__name__,
"dru": self.data_reupload,
"params": params,
"params": self.params, # use safe-params
"noise_params": self.noise_params,
"execution_type": self.execution_type,
"inputs": inputs,
Expand All @@ -568,7 +581,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=params, # use arraybox params
inputs=inputs,
)
else:
Expand All @@ -578,7 +591,7 @@ def _forward(
)
else:
result = self.circuit(
params=params,
params=params, # use arraybox params
inputs=inputs,
)

Expand Down
23 changes: 23 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -645,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)

0 comments on commit 3fc704b

Please sign in to comment.