Пример #1
0
def test_load_optimization_results_successfully_loads_optimization_result_from_file():
    result_to_serialize = EXAMPLE_OPTIMIZATION_RESULT

    optimization_result_filename = "test-optimization-result-io.json"

    save_optimization_results(result_to_serialize, optimization_result_filename)

    loaded_data = load_optimization_results(optimization_result_filename)

    assert optimization_results_equal(result_to_serialize, loaded_data)

    os.remove(optimization_result_filename)
Пример #2
0
def optimize_variational_qcbm_circuit(
    distance_measure_specs,
    distance_measure_parameters,
    n_layers,
    n_qubits,
    n_samples,
    topology,
    backend_specs,
    optimizer_specs,
    initial_parameters,
    target_distribution,
    keep_history,
    gradient_type = "finite_difference",
    gradient_kwargs = None,
):

    if isinstance(distance_measure_specs, str):
        distance_measure_specs = json.loads(distance_measure_specs)
    distance_measure = get_func_from_specs(distance_measure_specs)

    ansatz = QCBMAnsatz(n_layers, n_qubits, topology)

    if isinstance(backend_specs, str):
        backend_specs = json.loads(backend_specs)
    backend = create_object(backend_specs)

    if isinstance(optimizer_specs, str):
        optimizer_specs = json.loads(optimizer_specs)
    optimizer = create_object(optimizer_specs)

    initial_parameters = load_array(initial_parameters)
    target_distribution = load_bitstring_distribution(target_distribution)

    if isinstance(distance_measure_parameters, str):
        distance_measure_parameters = json.loads(distance_measure_parameters)

    cost_function = QCBMCostFunction(
        ansatz,
        backend,
        n_samples,
        distance_measure,
        distance_measure_parameters,
        target_distribution,
        gradient_type,
        gradient_kwargs
    )
    opt_results = optimizer.minimize(cost_function, initial_parameters, keep_history)

    save_optimization_results(opt_results, "qcbm-optimization-results.json")
    save_array(opt_results.opt_params, "optimized-parameters.json")
Пример #3
0
def test_save_optimization_results_successfully_saves_optimization_result():
    result_to_serialize = EXAMPLE_OPTIMIZATION_RESULT

    expected_deserialized_result = EXPECTED_DESERIALIZED_RESULT

    optimization_result_filename = "test-optimization-result-io.json"

    # When
    save_optimization_results(result_to_serialize, optimization_result_filename)

    # Then
    with open(optimization_result_filename, "r") as f:
        loaded_data = json.load(f)

    assert loaded_data == expected_deserialized_result

    os.remove(optimization_result_filename)
Пример #4
0
def optimize_variational_qcbm_circuit(
    distance_measure_specs,
    distance_measure_parameters,
    n_layers,
    n_qubits,
    topology,
    backend_specs,
    optimizer_specs,
    initial_parameters,
    target_distribution,
):

    if isinstance(distance_measure_specs, str):
        distance_measure_specs = json.loads(distance_measure_specs)
    distance_measure = get_func_from_specs(distance_measure_specs)

    ansatz = QCBMAnsatz(n_layers, n_qubits, topology)

    if isinstance(backend_specs, str):
        backend_specs = json.loads(backend_specs)
    backend = create_object(backend_specs)

    if isinstance(optimizer_specs, str):
        optimizer_specs = json.loads(optimizer_specs)
    optimizer = create_object(optimizer_specs)

    initial_parameters = load_circuit_template_params(initial_parameters)
    target_distribution = load_bitstring_distribution(target_distribution)

    if isinstance(distance_measure_parameters, str):
        distance_measure_parameters = json.loads(distance_measure_parameters)

    cost_function = QCBMCostFunction(
        ansatz,
        backend,
        distance_measure,
        distance_measure_parameters,
        target_distribution,
    )
    opt_results = optimizer.minimize(cost_function, initial_parameters)
    save_optimization_results(opt_results, "qcbm-optimization-results.json")
    save_circuit_template_params(opt_results.opt_params,
                                 "optimized-parameters.json")
Пример #5
0
def optimize_parametrized_circuit_for_ground_state_of_operator(
    optimizer_specs: Specs,
    target_operator: Union[SymbolicOperator, str],
    parametrized_circuit: Union[Circuit, str],
    backend_specs: Specs,
    estimator_specs: Optional[Specs] = None,
    estimator_kwargs: Optional[Union[Dict, str]] = None,
    initial_parameters: Union[str, np.ndarray, List[float]] = None,
    fixed_parameters: Optional[Union[np.ndarray, str]] = None,
    parameter_precision: Optional[float] = None,
    parameter_precision_seed: Optional[int] = None,
):
    """Optimize the parameters of a parametrized quantum circuit to prepare the ground state of a target operator.

    Args:
        optimizer_specs (Union[Dict, str]): The specs of the optimizer to use to refine the parameter values
        target_operator (Union[SymbolicOperator, str]): The operator of which to prepare the ground state
        parametrized_circuit (Union[Circuit, str]): The parametrized quantum circuit that prepares trial states
        backend_specs (Union[Dict, str]): The specs of the quantum backend (or simulator) to use to run the circuits
        estimator_specs (Union[Dict, str]): The estimator to use to estimate the expectation value of the operator.
            The default is the BasicEstimator.
        estimator_kwargs (dict): kwargs required to run get_estimated_expectation_values method of the estimator.
        initial_parameters (Union[str, np.ndarray, List[float]]): The initial parameter values to begin optimization
        fixed_parameters (Optional[Union[np.ndarray, str]]): values for the circuit parameters that should be fixed.
        parameter_precision (float): the standard deviation of the Gaussian noise to add to each parameter, if any.
        parameter_precision_seed (int): seed for randomly generating parameter deviation if using parameter_precision

        initial_parameters (Union[str, np.ndarray, List[float]] = None,
    """
    if estimator_kwargs is not None:
        if isinstance(estimator_kwargs, str):
            estimator_kwargs = json.loads(estimator_kwargs)
        estimator = create_object(estimator_kwargs)
    else:
        estimator_kwargs = {}

    if isinstance(optimizer_specs, str):
        optimizer_specs = json.loads(optimizer_specs)
    optimizer = create_object(optimizer_specs)

    if isinstance(target_operator, str):
        with open(target_operator, "r") as f:
            target_operator = json.loads(f.read())

    if isinstance(parametrized_circuit, str):
        parametrized_circuit = load_circuit(parametrized_circuit)

    if isinstance(backend_specs, str):
        backend_specs = json.loads(backend_specs)
    backend = create_object(backend_specs)

    if estimator_specs is not None:
        if isinstance(estimator_specs, str):
            estimator_specs = json.loads(estimator_specs)
        estimator = create_object(estimator_specs)
    else:
        estimator = BasicEstimator()

    if initial_parameters is not None:
        if isinstance(initial_parameters, str):
            initial_parameters = load_circuit_template_params(
                initial_parameters)

    if fixed_parameters is not None:
        if isinstance(fixed_parameters, str):
            fixed_parameters = load_circuit_template_params(fixed_parameters)

    cost_function = get_ground_state_cost_function(
        target_operator,
        parametrized_circuit,
        backend,
        estimator=estimator,
        estimator_kwargs=estimator_kwargs,
        fixed_parameters=fixed_parameters,
        parameter_precision=parameter_precision,
        parameter_precision_seed=parameter_precision_seed,
    )

    optimization_results = optimizer.minimize(cost_function,
                                              initial_parameters)

    save_optimization_results(optimization_results,
                              "optimization_results.json")
Пример #6
0
def optimize_parametrized_circuit_for_ground_state_of_operator(
    optimizer_specs: Specs,
    target_operator: Union[SymbolicOperator, str],
    parametrized_circuit: Union[Circuit, str],
    backend_specs: Specs,
    estimation_method_specs: Optional[Specs] = None,
    estimation_preprocessors_specs: Optional[List[Specs]] = None,
    initial_parameters: Union[str, np.ndarray, List[float]] = None,
    fixed_parameters: Optional[Union[np.ndarray, str]] = None,
    parameter_precision: Optional[float] = None,
    parameter_precision_seed: Optional[int] = None,
    keep_history: bool = True,
    **kwargs,
):
    """Optimize the parameters of a parametrized quantum circuit to prepare the ground
    state of a target operator.

    Args:
        optimizer_specs: The specs of the optimizer to use to refine the parameter
            values
        target_operator: The operator of which to prepare the ground state
        parametrized_circuit: The parametrized quantum circuit that prepares trial
            states
        backend_specs: The specs of the quantum backend (or simulator) to use to run the
            circuits
        estimation_method_specs: A reference to a callable to use to estimate the
            expectation value of the operator. The default is the
            estimate_expectation_values_by_averaging function.
        estimation_preprocessors_specs: A list of Specs that describe callable functions
            that adhere to the EstimationPreprocessor protocol.
        initial_parameters: The initial parameter values to begin optimization
        fixed_parameters: values for the circuit parameters that should be fixed.
        parameter_precision: the standard deviation of the Gaussian noise to add to each
            parameter, if any.
        parameter_precision_seed: seed for randomly generating parameter deviation if
            using parameter_precision
        keep_history: flag indicating whether to store optimization history.
        kwargs: unused, exists for compatibility
    """
    if isinstance(optimizer_specs, str):
        optimizer_specs = json.loads(optimizer_specs)

    optimizer = create_object(optimizer_specs)

    if isinstance(target_operator, str):
        target_operator = load_qubit_operator(target_operator)

    if isinstance(parametrized_circuit, str):
        with open(parametrized_circuit) as f:
            parametrized_circuit = new_circuits.circuit_from_dict(json.load(f))

    if isinstance(backend_specs, str):
        backend_specs = json.loads(backend_specs)
    backend = create_object(backend_specs)

    if estimation_method_specs is not None:
        if isinstance(estimation_method_specs, str):
            estimation_method_specs = json.loads(estimation_method_specs)
        estimation_method = create_object(estimation_method_specs)
    else:
        estimation_method = estimate_expectation_values_by_averaging

    estimation_preprocessors = []
    if estimation_preprocessors_specs is not None:
        for estimation_preprocessor_specs in estimation_preprocessors_specs:
            if isinstance(estimation_preprocessor_specs, str):
                estimation_preprocessor_specs = json.loads(
                    estimation_preprocessor_specs)
            estimation_preprocessors.append(
                create_object(estimation_preprocessor_specs))

    if initial_parameters is not None:
        if isinstance(initial_parameters, str):
            initial_parameters = load_array(initial_parameters)

    if fixed_parameters is not None:
        if isinstance(fixed_parameters, str):
            fixed_parameters = load_array(fixed_parameters)

    cost_function = get_ground_state_cost_function(
        target_operator,
        parametrized_circuit,
        backend,
        estimation_method=estimation_method,
        estimation_preprocessors=estimation_preprocessors,
        fixed_parameters=fixed_parameters,
        parameter_precision=parameter_precision,
        parameter_precision_seed=parameter_precision_seed,
    )

    optimization_results = optimizer.minimize(cost_function,
                                              initial_parameters, keep_history)

    save_optimization_results(optimization_results,
                              "optimization-results.json")
    save_array(optimization_results.opt_params, "optimized-parameters.json")
Пример #7
0
def optimize_ansatz_based_cost_function(
    optimizer_specs: Specs,
    target_operator: Union[SymbolicOperator, str],
    ansatz_specs: Specs,
    backend_specs: Specs,
    estimation_method_specs: Optional[Specs] = None,
    estimation_preprocessors_specs: Optional[List[Specs]] = None,
    initial_parameters: Union[str, np.ndarray, List[float]] = None,
    fixed_parameters: Optional[Union[np.ndarray, str]] = None,
    parameter_precision: Optional[float] = None,
    parameter_precision_seed: Optional[int] = None,
    keep_history: bool = False,
    **kwargs,
):
    """Optimize the parameters of an ansatz circuit to prepare the ground state of a
    target operator.

    Args:
        optimizer_specs: The specs of the optimizer to use to refine the parameter
            values
        target_operator: The operator of which to prepare the ground state
        ansatz_specs: The specs describing an Ansatz which will prepare the quantum
            circuit
        backend_specs: The specs of the quantum backend (or simulator) to use to run the
            circuits
        estimation_method_specs: A reference to a callable to use to estimate the
            expectation value of the operator. The default is the
            estimate_expectation_values_by_averaging function.
        estimation_preprocessors_specs: A list of Specs that describe callable functions
            that adhere to the EstimationPreprocessor protocol.
        initial_parameters: The initial parameter values to begin optimization
        fixed_parameters: values for the circuit parameters that should be fixed.
        parameter_precision: the standard deviation of the Gaussian noise to add to each
            parameter, if any.
        parameter_precision_seed: seed for randomly generating parameter deviation if
            using parameter_precision
        keep_history: flag indicating whether to store optimization history.
        kwargs:
            The following key word arguments are handled explicitly when appropriate:
                - thetas: A list of thetas used to initialize the WarmStartQAOAAnsatz
    """
    if isinstance(optimizer_specs, str):
        optimizer_specs = json.loads(optimizer_specs)

    optimizer = create_object(optimizer_specs)

    if isinstance(target_operator, str):
        target_operator = load_qubit_operator(target_operator)

    if isinstance(ansatz_specs, str):
        ansatz_specs = json.loads(ansatz_specs)

    if "WarmStartQAOAAnsatz" in ansatz_specs["function_name"]:
        ansatz_specs["thetas"] = np.array(load_list(kwargs.pop("thetas")))
        ansatz_specs["cost_hamiltonian"] = target_operator
    elif "QAOA" in ansatz_specs["function_name"]:
        ansatz_specs["cost_hamiltonian"] = target_operator
    ansatz = create_object(ansatz_specs)

    if isinstance(backend_specs, str):
        backend_specs = json.loads(backend_specs)
    backend = create_object(backend_specs)

    if estimation_method_specs is not None:
        if isinstance(estimation_method_specs, str):
            estimation_method_specs = json.loads(estimation_method_specs)
        estimation_method = create_object(estimation_method_specs)
    else:
        estimation_method = estimate_expectation_values_by_averaging

    estimation_preprocessors = []
    if estimation_preprocessors_specs is not None:
        for estimation_preprocessor_specs in estimation_preprocessors_specs:
            if isinstance(estimation_preprocessor_specs, str):
                estimation_preprocessor_specs = json.loads(
                    estimation_preprocessor_specs)
            estimation_preprocessors.append(
                create_object(estimation_preprocessor_specs))

    if initial_parameters is not None:
        if isinstance(initial_parameters, str):
            initial_parameters = load_array(initial_parameters)

    if fixed_parameters is not None:
        if isinstance(fixed_parameters, str):
            fixed_parameters = load_array(fixed_parameters)

    cost_function = AnsatzBasedCostFunction(
        target_operator,
        ansatz,
        backend,
        estimation_method=estimation_method,
        estimation_preprocessors=estimation_preprocessors,
        fixed_parameters=fixed_parameters,
        parameter_precision=parameter_precision,
        parameter_precision_seed=parameter_precision_seed,
    )

    optimization_results = optimizer.minimize(cost_function,
                                              initial_parameters, keep_history)

    save_optimization_results(optimization_results,
                              "optimization-results.json")
    save_array(optimization_results.opt_params, "optimized-parameters.json")
Пример #8
0
def optimize_variational_circuit(
    ansatz_specs,
    backend_specs,
    optimizer_specs,
    cost_function_specs,
    qubit_operator,
    initial_parameters="None",
    fixed_parameters="None",
    noise_model="None",
    device_connectivity="None",
    parameter_grid="None",
    constraint_operator="None",
):
    if initial_parameters != "None":
        initial_params = load_circuit_template_params(initial_parameters)
    else:
        initial_params = None

    if fixed_parameters != "None":
        fixed_params = load_circuit_template_params(fixed_parameters)
    else:
        fixed_params = None

    # Load qubit operator
    operator = load_qubit_operator(qubit_operator)

    if isinstance(ansatz_specs, str):
        ansatz_specs_dict = yaml.load(ansatz_specs, Loader=yaml.SafeLoader)
    else:
        ansatz_specs_dict = ansatz_specs
    if ansatz_specs_dict["function_name"] == "QAOAFarhiAnsatz":
        ansatz = create_object(ansatz_specs_dict, cost_hamiltonian=operator)
    else:
        ansatz = create_object(ansatz_specs_dict)

    # Load parameter grid
    if parameter_grid != "None":
        grid = load_parameter_grid(parameter_grid)
    else:
        grid = None

    # Load optimizer specs
    if isinstance(optimizer_specs, str):
        optimizer_specs_dict = yaml.load(optimizer_specs,
                                         Loader=yaml.SafeLoader)
    else:
        optimizer_specs_dict = optimizer_specs
    if (grid is not None and optimizer_specs_dict["function_name"]
            == "GridSearchOptimizer"):
        optimizer = create_object(optimizer_specs_dict, grid=grid)
    else:
        optimizer = create_object(optimizer_specs_dict)

    # Load backend specs
    if isinstance(backend_specs, str):
        backend_specs_dict = yaml.load(backend_specs, Loader=yaml.SafeLoader)
    else:
        backend_specs_dict = backend_specs
    if noise_model != "None":
        backend_specs_dict["noise_model"] = load_noise_model(noise_model)
    if device_connectivity != "None":
        backend_specs_dict["device_connectivity"] = load_circuit_connectivity(
            device_connectivity)
    backend = create_object(backend_specs_dict)

    # Load cost function specs
    if isinstance(cost_function_specs, str):
        cost_function_specs_dict = yaml.load(cost_function_specs,
                                             Loader=yaml.SafeLoader)
    else:
        cost_function_specs_dict = cost_function_specs
    estimator_specs = cost_function_specs_dict.pop("estimator-specs", None)
    if estimator_specs is not None:
        cost_function_specs_dict["estimator"] = create_object(estimator_specs)
    cost_function_specs_dict["target_operator"] = operator
    cost_function_specs_dict["ansatz"] = ansatz
    cost_function_specs_dict["backend"] = backend
    cost_function_specs_dict["fixed_parameters"] = fixed_params
    cost_function = create_object(cost_function_specs_dict)

    if constraint_operator != "None":
        constraint_op = load_qubit_operator(constraint_operator)
        constraints_cost_function_specs = yaml.load(cost_function_specs,
                                                    Loader=yaml.SafeLoader)
        constraints_estimator_specs = constraints_cost_function_specs.pop(
            "estimator-specs", None)
        if constraints_estimator_specs is not None:
            constraints_cost_function_specs["estimator"] = create_object(
                constraints_estimator_specs)
        constraints_cost_function_specs["ansatz"] = ansatz
        constraints_cost_function_specs["backend"] = backend
        constraints_cost_function_specs["target_operator"] = constraint_op
        constraint_cost_function = create_object(
            constraints_cost_function_specs)
        constraint_cost_function_wrapper = (
            lambda params: constraint_cost_function.evaluate(params).value)
        constraint_functions = ({
            "type": "eq",
            "fun": constraint_cost_function_wrapper
        }, )
        optimizer.constraints = constraint_functions

    opt_results = optimizer.minimize(cost_function, initial_params)

    save_optimization_results(opt_results, "optimization-results.json")
    save_circuit_template_params(opt_results.opt_params,
                                 "optimized-parameters.json")
def optimize_variational_circuit(
    ansatz_specs,
    backend_specs,
    optimizer_specs,
    cost_function_specs,
    qubit_operator,
    initial_parameters="None",
    fixed_parameters="None",
    noise_model="None",
    device_connectivity="None",
    parameter_grid="None",
    parameter_values_list=None,
    constraint_operator="None",
    prior_expectation_values: Optional[str] = None,
    keep_history=False,
    thetas=None,
):
    warnings.warn(
        "optimize_variational_circuit will be depreciated in favor of optimize_ansatz_based_cost_function in steps/optimize.py in z-quantum-core.",
        DeprecationWarning,
    )
    if initial_parameters != "None":
        initial_params = load_array(initial_parameters)
    else:
        initial_params = None

    if fixed_parameters != "None":
        fixed_params = load_array(fixed_parameters)
    else:
        fixed_params = None

    # Load qubit operator
    operator = load_qubit_operator(qubit_operator)

    if isinstance(ansatz_specs, str):
        ansatz_specs_dict = yaml.load(ansatz_specs, Loader=yaml.SafeLoader)
    else:
        ansatz_specs_dict = ansatz_specs
    if "WarmStartQAOAAnsatz" in ansatz_specs_dict["function_name"]:
        thetas = np.array(load_list(thetas))
        ansatz = create_object(ansatz_specs_dict,
                               cost_hamiltonian=operator,
                               thetas=thetas)
    elif "QAOA" in ansatz_specs_dict["function_name"]:
        ansatz = create_object(ansatz_specs_dict, cost_hamiltonian=operator)
    else:
        ansatz = create_object(ansatz_specs_dict)

    # Load parameter grid
    if parameter_grid != "None":
        grid = load_parameter_grid(parameter_grid)
    else:
        grid = None

    # Load parameter values list
    if parameter_values_list is not None:
        parameter_values_list = load_array(parameter_values_list)

    # Load optimizer specs
    if isinstance(optimizer_specs, str):
        optimizer_specs_dict = yaml.load(optimizer_specs,
                                         Loader=yaml.SafeLoader)
    else:
        optimizer_specs_dict = optimizer_specs

    if (grid is not None and optimizer_specs_dict["function_name"]
            == "GridSearchOptimizer"):
        optimizer = create_object(optimizer_specs_dict, grid=grid)
    elif (parameter_values_list is not None and
          optimizer_specs_dict["function_name"] == "SearchPointsOptimizer"):
        optimizer = create_object(optimizer_specs_dict,
                                  parameter_values_list=parameter_values_list)
    else:
        optimizer = create_object(optimizer_specs_dict)

    # Load backend specs
    if isinstance(backend_specs, str):
        backend_specs_dict = yaml.load(backend_specs, Loader=yaml.SafeLoader)
    else:
        backend_specs_dict = backend_specs
    if noise_model != "None":
        backend_specs_dict["noise_model"] = load_noise_model(noise_model)
    if device_connectivity != "None":
        backend_specs_dict["device_connectivity"] = load_circuit_connectivity(
            device_connectivity)
    backend = create_object(backend_specs_dict)

    # Load cost function specs
    if isinstance(cost_function_specs, str):
        cost_function_specs_dict = yaml.load(cost_function_specs,
                                             Loader=yaml.SafeLoader)
    else:
        cost_function_specs_dict = cost_function_specs
    estimation_method_specs = cost_function_specs_dict.pop(
        "estimation_method_specs", None)

    if estimation_method_specs is not None:
        if isinstance(estimation_method_specs, str):
            estimation_method_specs = yaml.loads(estimation_method_specs)
        estimation_method = create_object(estimation_method_specs)
    else:
        estimation_method = estimate_expectation_values_by_averaging
    cost_function_specs_dict["estimation_method"] = estimation_method

    estimation_preprocessors_specs_list = cost_function_specs_dict.pop(
        "estimation_preprocessors_specs", None)
    if estimation_preprocessors_specs_list is not None:
        estimation_preprocessors = []
        for estimation_preprocessor_specs in estimation_preprocessors_specs_list:
            if isinstance(estimation_preprocessor_specs, str):
                estimation_preprocessor_specs = yaml.loads(
                    estimation_preprocessor_specs)
            estimation_preprocessors.append(
                create_object(estimation_preprocessor_specs))
        cost_function_specs_dict[
            "estimation_preprocessors"] = estimation_preprocessors

    cost_function_specs_dict["target_operator"] = operator
    cost_function_specs_dict["ansatz"] = ansatz
    cost_function_specs_dict["backend"] = backend
    cost_function_specs_dict["fixed_parameters"] = fixed_params
    cost_function = create_object(cost_function_specs_dict)

    if prior_expectation_values is not None:
        if isinstance(prior_expectation_values, str):
            cost_function.estimator.prior_expectation_values = load_expectation_values(
                prior_expectation_values)

    if constraint_operator != "None":
        constraint_op = load_qubit_operator(constraint_operator)
        constraints_cost_function_specs = yaml.load(cost_function_specs,
                                                    Loader=yaml.SafeLoader)
        constraints_estimator_specs = constraints_cost_function_specs.pop(
            "estimator-specs", None)
        if constraints_estimator_specs is not None:
            constraints_cost_function_specs["estimator"] = create_object(
                constraints_estimator_specs)
        constraints_cost_function_specs["ansatz"] = ansatz
        constraints_cost_function_specs["backend"] = backend
        constraints_cost_function_specs["target_operator"] = constraint_op
        constraint_cost_function = create_object(
            constraints_cost_function_specs)
        constraint_cost_function_wrapper = (
            lambda params: constraint_cost_function.evaluate(params).value)
        constraint_functions = ({
            "type": "eq",
            "fun": constraint_cost_function_wrapper
        }, )
        optimizer.constraints = constraint_functions

    opt_results = optimizer.minimize(cost_function, initial_params,
                                     keep_history)

    save_optimization_results(opt_results, "optimization-results.json")
    save_array(opt_results.opt_params, "optimized-parameters.json")