Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix dump methods #225

Merged
merged 11 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_main_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
run: |
IBMQ_TOKEN=$IBMQ_TOKEN
source env/bin/activate
python -c'from qiskit import IBMQ; import os; IBMQ.save_account(os.environ.get("IBMQ_TOKEN"))'
python -c'from qiskit_ibm_provider import IBMProvider; import os; IBMProvider.save_account(os.environ.get("IBMQ_TOKEN"))'
- name: Setup AWS
env:
ACCESS_KEY: ${{ secrets.ACCESS_KEY }}
Expand Down
54 changes: 27 additions & 27 deletions src/openqaoa-core/algorithms/baseworkflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ def __init__(self, device=DeviceLocal("vectorized")):
"target": None,
"cloud": None,
"client": None,
"qubit_number": None, # it is set automatically in the compilation from the problem object
"execution_time_start": None,
"execution_time_end": None,
}
Expand All @@ -131,13 +130,13 @@ def __setattr__(self, __name, __value):

def set_header(
self,
project_id: str,
description: str,
run_by: str,
provider: str,
target: str,
cloud: str,
client: str,
project_id: str = None,
description: str = None,
run_by: str = None,
provider: str = None,
target: str = None,
cloud: str = None,
client: str = None,
experiment_id: str = None,
):
"""
Expand All @@ -147,16 +146,17 @@ def set_header(
----------
TODO : document the parameters
"""
if project_id is not None:
if not is_valid_uuid(project_id):
raise ValueError(
"The project_id is not a valid uuid, example of a valid uuid: 8353185c-b175-4eda-9628-b4e58cb0e41b"
)

if not is_valid_uuid(project_id):
raise ValueError(
"The project_id is not a valid uuid, example of a valid uuid: 8353185c-b175-4eda-9628-b4e58cb0e41b"
)

if experiment_id != None:
if experiment_id is not None:
if not is_valid_uuid(experiment_id):
raise ValueError(
"The experiment_id is not a valid uuid, example of a valid uuid: 8353185c-b175-4eda-9628-b4e58cb0e41b"
"The experiment_id is not a valid uuid, \
example of a valid uuid: 8353185c-b175-4eda-9628-b4e58cb0e41b"
)
else:
self.header["experiment_id"] = experiment_id
Expand Down Expand Up @@ -335,13 +335,13 @@ def compile(self, problem: QUBO):
self.header["atomic_id"] = generate_uuid()

# header is updated with the qubit number of the problem
self.header["qubit_number"] = self.problem.n
self.set_exp_tags({"qubit_number": self.problem.n})

def optimize():
raise NotImplementedError

def _serializable_dict(
self, complex_to_string: bool = False, intermediate_mesurements: bool = True
self, complex_to_string: bool = False, intermediate_measurements: bool = True
):
"""
Returns a dictionary with all values and attributes of the object that we want to
Expand All @@ -355,7 +355,7 @@ def _serializable_dict(
complex_to_string: bool
If True, converts all complex numbers to strings. This is useful for
JSON serialization, for the `dump(s)` methods.
intermediate_mesurements: bool
intermediate_measurements: bool
If True, intermediate measurements are included in the dump.
If False, intermediate measurements are not included in the dump.
Default is True.
Expand All @@ -381,8 +381,8 @@ def _serializable_dict(
)

data["result"] = (
self.result.asdict(False, complex_to_string, intermediate_mesurements)
if not self.result in [None, {}]
self.result.asdict(False, complex_to_string, intermediate_measurements)
if self.result not in [None, {}]
else None
)

Expand Down Expand Up @@ -437,7 +437,7 @@ def asdict(self, exclude_keys: List[str] = [], options: dict = {}):
complex_to_string : bool
If True, converts complex numbers to strings. If False,
complex numbers are not converted to strings.
intermediate_mesurements : bool
intermediate_measurements : bool
If True, includes the intermediate measurements in the results.
If False, only the final measurements are included.

Expand Down Expand Up @@ -469,7 +469,7 @@ def dumps(self, indent: int = 2, exclude_keys: List[str] = [], options: dict = {
options : dict
A dictionary of options to pass to the method that creates
the dictionary to dump.
intermediate_mesurements : bool
intermediate_measurements : bool
If True, includes the intermediate measurements in the results.
If False, only the final measurements are included.

Expand Down Expand Up @@ -530,7 +530,7 @@ def dump(
raises an error if the file already exists.
options : dict
A dictionary of options to pass to the method that creates the dictionary to dump.
intermediate_mesurements : bool
intermediate_measurements : bool
If True, includes the intermediate measurements in the results.
If False, only the final measurements are included.
"""
Expand All @@ -544,11 +544,11 @@ def dump(
)

# get the full name
if prepend_id == False and file_name == "":
if prepend_id is False and file_name == "":
raise ValueError(
"dump method missing argument: 'file_name'. Otherwise 'prepend_id' must be specified as True."
)
elif prepend_id == False:
elif prepend_id is False:
file = file_path + file_name
elif file_name == "":
file = (
Expand Down Expand Up @@ -577,12 +577,12 @@ def dump(
file = file + ".gz" if ".gz" != file[-3:] else file

# checking if the file already exists, and raising an error if it does and overwrite is False
if overwrite == False and exists(file):
if overwrite is False and exists(file):
raise FileExistsError(
f"The file {file} already exists. Please change the name of the file or set overwrite=True."
)

## saving the file
# saving the file
if compresslevel == 0: # if compresslevel is 0, save as json file
with open(file, "w") as f:
if exclude_keys == []:
Expand Down
97 changes: 63 additions & 34 deletions src/openqaoa-core/algorithms/qaoa/qaoa_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@


def most_probable_bitstring(cost_hamiltonian, measurement_outcomes):

"""
Computing the most probable bitstring
"""
mea_out = list(measurement_outcomes.values())
index_likliest_states = np.argwhere(mea_out == np.max(mea_out))
# degeneracy = len(index_likliest_states)
Expand Down Expand Up @@ -54,7 +56,9 @@ def __init__(
cost_hamiltonian: Type[Hamiltonian],
type_backend: Type[QAOABaseBackend],
):

"""
init method
"""
self.__type_backend = type_backend
self.method = method
self.cost_hamiltonian = cost_hamiltonian
Expand Down Expand Up @@ -116,7 +120,7 @@ def asdict(
self,
keep_cost_hamiltonian: bool = True,
complex_to_string: bool = False,
intermediate_mesurements: bool = True,
intermediate_measurements: bool = True,
exclude_keys: List[str] = [],
):
"""
Expand All @@ -132,7 +136,7 @@ def asdict(
complex_to_string: `bool`
If True, the complex numbers are converted to strings. If False, they are kept as complex numbers.
This is useful for the JSON serialization.
intermediate_mesurements: bool, optional
intermediate_measurements: bool, optional
If True, intermediate measurements are included in the dump.
If False, intermediate measurements are not included in the dump.
Default is True.
Expand All @@ -152,7 +156,7 @@ def asdict(
return_dict["evals"] = self.evals
return_dict["most_probable_states"] = self.most_probable_states

complx_to_str = (
complex_to_str = (
lambda x: str(x)
if isinstance(x, np.complex128) or isinstance(x, complex)
else x
Expand All @@ -161,43 +165,67 @@ def asdict(
# if the backend is a statevector backend, the measurement outcomes will be the statevector,
# meaning that it is a list of complex numbers, which is not serializable.
# If that is the case, and complex_to_string is true the complex numbers are converted to strings.
if complex_to_string and issubclass(
self.__type_backend, QAOABaseBackendStatevector
):
if complex_to_string:
return_dict["intermediate"] = {}
for key, value in self.intermediate.items():
if (
intermediate_mesurements == False and "measurement" in key
): # if intermediate_mesurements is false, the intermediate measurements are not included in the dump
return_dict["intermediate"][key] = []
elif "measurement" in key and (
isinstance(value, list) or isinstance(value, np.ndarray)
# Measurements and Cost may require casting
if "measurement" in key:
vishal-ph marked this conversation as resolved.
Show resolved Hide resolved
if len(value) > 0:
if intermediate_measurements is False:
# if intermediate_measurements is false, the intermediate measurements are not included
return_dict["intermediate"][key] = []
elif isinstance(
value[0], np.ndarray
): # Statevector -> convert complex to str
return_dict["intermediate"][key] = [
[complex_to_str(item) for item in list_]
for list_ in value
if (
isinstance(list_, list)
or isinstance(list_, np.ndarray)
)
]
else: # All other case -> cast numpy into
return_dict["intermediate"][key] = [
{k_: int(v_) for k_, v_ in v.items()} for v in value
]
else:
pass
elif "cost" == key and (
isinstance(value[0], np.float64) or isinstance(value[0], np.float32)
):
return_dict["intermediate"][key] = [
[complx_to_str(item) for item in list_]
for list_ in value
if (isinstance(list_, list) or isinstance(list_, np.ndarray))
]
return_dict["intermediate"][key] = [float(item) for item in value]
else:
return_dict["intermediate"][key] = value

return_dict["optimized"] = {}
for key, value in self.optimized.items():
# If wavefunction do complex to str
if "measurement" in key and (
isinstance(value, list) or isinstance(value, np.ndarray)
):
return_dict["optimized"][key] = [
complx_to_str(item) for item in value
complex_to_str(item) for item in value
]
# if dictionary, convert measurement values to integers
elif "measurement" in key and (isinstance(value, dict)):
return_dict["optimized"][key] = {
k: int(v) for k, v in value.items()
}
else:
return_dict["optimized"][key] = value

if "cost" in key and (
isinstance(value, np.float64) or isinstance(value, np.float32)
):
return_dict["optimized"][key] = float(value)
else:
return_dict["intermediate"] = self.intermediate
return_dict["optimized"] = self.optimized

# if we are using a shot adaptive optimizer, we need to add the number of shots to the result,
# so if attribute n_shots is not empty, it is added to the dictionary
if getattr(self, "n_shots", None) != None:
if getattr(self, "n_shots", None) is not None:
return_dict["n_shots"] = self.n_shots

return (
Expand Down Expand Up @@ -233,7 +261,7 @@ def from_dict(
setattr(result, key, value)

# if there is an input cost hamiltonian, it is added to the result
if cost_hamiltonian != None:
if cost_hamiltonian is not None:
result.cost_hamiltonian = cost_hamiltonian

# if the measurement_outcomes are strings, they are converted to complex numbers
Expand Down Expand Up @@ -281,7 +309,7 @@ def get_counts(measurement_outcomes):
the actual measurement counts.
"""

if type(measurement_outcomes) == type(np.array([])):
if isinstance(measurement_outcomes, type(np.array([]))):
measurement_outcomes = qaoa_probabilities(measurement_outcomes)

return measurement_outcomes
Expand Down Expand Up @@ -335,7 +363,6 @@ def plot_probabilities(
color="tab:blue",
ax=None,
):

"""
Helper function to plot the probabilities corresponding to each basis states
(with prob != 0) obtained from the optimized result
Expand All @@ -359,7 +386,7 @@ def plot_probabilities(
outcome = self.optimized["measurement_outcomes"]

# converting to counts dictionary if outcome is statevector
if type(outcome) == type(np.array([])):
if isinstance(outcome, type(np.array([]))):
outcome = self.get_counts(outcome)
# setting norm to 1 since it might differ slightly for statevectors due to numerical preicision
norm = np.float64(1)
Expand Down Expand Up @@ -407,7 +434,7 @@ def plot_probabilities(
]
labels.append("rest")

# represent the bar with the addition of all the remaining probabilites
# represent the bar with the addition of all the remaining probabilities
rest = sum(probs[n_states_to_keep:])

if ax is None:
Expand Down Expand Up @@ -470,7 +497,7 @@ def plot_n_shots(
if ax is None:
ax = plt.subplots(figsize=figsize)[1]

## creating a list of parameters to plot
# creating a list of parameters to plot
# if param_to_plot is not given, plot all the parameters
if param_to_plot is None:
param_to_plot = list(range(len(self.n_shots[0])))
Expand Down Expand Up @@ -504,22 +531,23 @@ def plot_n_shots(
color = [color]

# if param_top_plot is a list and label or color are not lists, raise error
if (type(label) != list) or (type(color) != list and color != None):
if (type(label) != list) or (type(color) != list and color is not None):
raise TypeError("`label` and `color` must be list of str")
# if label is a list, check that all the elements are strings
for l in label:
assert type(l) == str, "`label` must be a list of strings"
for lab in label:
assert type(lab) == str, "`label` must be a list of strings"
# if color is a list, check that all the elements are strings
if color != None:
if color is not None:
for c in color:
assert type(c) == str, "`color` must be a list of strings"

# if label and color are lists, check if they have the same length as param_to_plot
if len(label) != len(param_to_plot) or (
color != None and len(color) != len(param_to_plot)
color is not None and len(color) != len(param_to_plot)
):
raise ValueError(
f"`param_to_plot`, `label` and `color` must have the same length, `param_to_plot` is a list of {len(param_to_plot)} elements"
f"`param_to_plot`, `label` and `color` must have the same length, \
`param_to_plot` is a list of {len(param_to_plot)} elements"
)

# linestyle must be a string or a list of strings, if it is a string, convert it to a list of strings
Expand All @@ -529,7 +557,8 @@ def plot_n_shots(
linestyle = [linestyle for _ in range(len(param_to_plot))]
elif len(linestyle) != len(param_to_plot):
raise ValueError(
f"`linestyle` must have the same length as param_to_plot (length of `param_to_plot` is {len(param_to_plot)}), or be a string"
f"`linestyle` must have the same length as param_to_plot \
(length of `param_to_plot` is {len(param_to_plot)}), or be a string"
)
else:
for ls in linestyle:
Expand Down
Loading