Skip to content

Commit

Permalink
Merge pull request #225 from entropicalabs/bugfix_dump_methods
Browse files Browse the repository at this point in the history
Bugfix dump methods
  • Loading branch information
vishal-ph authored Apr 20, 2023
2 parents 97a80ff + 58a5d08 commit 86771b2
Show file tree
Hide file tree
Showing 16 changed files with 482 additions and 360 deletions.
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:
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

0 comments on commit 86771b2

Please sign in to comment.