Source code for braket.circuits.noise

# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from __future__ import annotations

from collections.abc import Iterable, Sequence
from typing import Any, ClassVar, Optional, Union

import numpy as np

from braket.circuits.free_parameter import FreeParameter
from braket.circuits.free_parameter_expression import FreeParameterExpression
from braket.circuits.parameterizable import Parameterizable
from braket.circuits.quantum_operator import QuantumOperator
from braket.circuits.serialization import (
    IRType,
    OpenQASMSerializationProperties,
    SerializationProperties,
)
from braket.registers.qubit_set import QubitSet


[docs] class Noise(QuantumOperator): """Class `Noise` represents a noise channel that operates on one or multiple qubits. Noise are considered as building blocks of quantum circuits that simulate noise. It can be used as an operator in an `Instruction` object. It appears in the diagram when user prints a circuit with `Noise`. This class is considered the noise channel definition containing the metadata that defines what the noise channel is and what it does. """ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): """Initializes a `Noise` object. Args: qubit_count (Optional[int]): Number of qubits this noise channel interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for this noise channel. These are used when printing a diagram of circuits. Length must be the same as `qubit_count`, and index ordering is expected to correlate with target ordering on the instruction. Raises: ValueError: `qubit_count` is less than 1, `ascii_symbols` are None, or length of `ascii_symbols` is not equal to `qubit_count` """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) @property def name(self) -> str: """Returns the name of the quantum operator Returns: str: The name of the quantum operator as a string """ return self.__class__.__name__
[docs] def to_ir( self, target: QubitSet, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties | None = None, ) -> Any: """Returns IR object of quantum operator and target Args: target (QubitSet): target qubit(s) ir_type(IRType) : The IRType to use for converting the noise object to its IR representation. Defaults to IRType.JAQCD. serialization_properties (SerializationProperties | None): The serialization properties to use while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: Any: IR object of the quantum operator and target Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization properties don't correspond to the `ir_type`. """ if ir_type == IRType.JAQCD: return self._to_jaqcd(target) elif ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): raise ValueError( "serialization_properties must be of type OpenQASMSerializationProperties " "for IRType.OPENQASM." ) return self._to_openqasm( target, serialization_properties or OpenQASMSerializationProperties() ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.")
def _to_jaqcd(self, target: QubitSet) -> Any: """Returns the JAQCD representation of the noise. Args: target (QubitSet): target qubit(s). Returns: Any: JAQCD object representing the noise. """ raise NotImplementedError("to_jaqcd has not been implemented yet.") def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties ) -> str: """Returns the openqasm string representation of the noise. Args: target (QubitSet): target qubit(s). serialization_properties (OpenQASMSerializationProperties): The serialization properties to use while serializing the object to the IR representation. Returns: str: Representing the openqasm representation of the noise. """ raise NotImplementedError("to_openqasm has not been implemented yet.")
[docs] def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: """Returns a list of matrices defining the Kraus matrices of the noise channel. Returns: Iterable[ndarray]: list of matrices defining the Kraus matrices of the noise channel. """ raise NotImplementedError("to_matrix has not been implemented yet.")
def __eq__(self, other: Noise): return self.name == other.name if isinstance(other, Noise) else False def __repr__(self): return f"{self.name}('qubit_count': {self.qubit_count})"
[docs] @classmethod def from_dict(cls, noise: dict) -> Noise: """Converts a dictionary representing an object of this class into an instance of this class. Args: noise (dict): A dictionary representation of an object of this class. Returns: Noise: An object of this class that corresponds to the passed in dictionary. """ if "__class__" in noise: noise_name = noise["__class__"] noise_cls = getattr(cls, noise_name) return noise_cls.from_dict(noise) raise NotImplementedError
[docs] @classmethod def register_noise(cls, noise: type[Noise]) -> None: """Register a noise implementation by adding it into the Noise class. Args: noise (type[Noise]): Noise class to register. """ setattr(cls, noise.__name__, noise)
[docs] class SingleProbabilisticNoise(Noise, Parameterizable): """Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits parameterized by a single probability. """ def __init__( self, probability: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], max_probability: float = 0.5, ): """Initializes a `SingleProbabilisticNoise`. Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. max_probability (float): Maximum allowed probability of the noise channel. Default: 0.5 Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count`, `probability` is not `float` or `FreeParameterExpression`, `probability` > 1/2, or `probability` < 0 """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if not isinstance(probability, FreeParameterExpression): _validate_param_value(probability, "probability", max_probability) self._probability = probability @property def probability(self) -> float: """The probability that parametrizes the noise channel. Returns: float: The probability that parametrizes the noise channel. """ return self._probability def __repr__(self): return f"{self.name}('probability': {self.probability}, 'qubit_count': {self.qubit_count})" def __str__(self): return f"{self.name}({self.probability})" @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions or fixed values associated with the object. """ return [self._probability] def __eq__(self, other: SingleProbabilisticNoise): if isinstance(other, SingleProbabilisticNoise): return self.name == other.name and self.probability == other.probability return False
[docs] def bind_values(self, **kwargs) -> SingleProbabilisticNoise: """Takes in parameters and attempts to assign them to values. Returns: SingleProbabilisticNoise: A new Noise object of the same type with the requested parameters bound. Raises: NotImplementedError: Subclasses should implement this function. """ raise NotImplementedError
[docs] def to_dict(self) -> dict: """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ return { "__class__": self.__class__.__name__, "probability": _parameter_to_dict(self.probability), "qubit_count": self.qubit_count, "ascii_symbols": self.ascii_symbols, }
[docs] class SingleProbabilisticNoise_34(SingleProbabilisticNoise): """Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise channels parameterized by a single probability. """ def __init__( self, probability: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """Initializes a `SingleProbabilisticNoise_34`. Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count`, `probability` is not `float` or `FreeParameterExpression`, `probability` > 3/4, or `probability` < 0 """ super().__init__( probability=probability, qubit_count=qubit_count, ascii_symbols=ascii_symbols, max_probability=0.75, )
[docs] class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): """Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel parameterized by a single probability. """ def __init__( self, probability: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """Initializes a `SingleProbabilisticNoise_1516`. Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count`, `probability` is not `float` or `FreeParameterExpression`, `probability` > 15/16, or `probability` < 0 """ super().__init__( probability=probability, qubit_count=qubit_count, ascii_symbols=ascii_symbols, max_probability=0.9375, )
[docs] class MultiQubitPauliNoise(Noise, Parameterizable): """Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, parameterized by up to 4**N - 1 probabilities. """ _allowed_substrings: ClassVar = {"I", "X", "Y", "Z"} def __init__( self, probabilities: dict[str, Union[FreeParameterExpression, float]], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """[summary] Args: probabilities (dict[str, Union[FreeParameterExpression, float]]): A dictionary with Pauli strings as keys and the probabilities as values, i.e. {"XX": 0.1. "IZ": 0.2}. qubit_count (Optional[int]): The number of qubits the Pauli noise acts on. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If `qubit_count` < 1, `ascii_symbols` is `None`, `ascii_symbols` length != `qubit_count`, `probabilities` are not `float`s or FreeParameterExpressions, any of `probabilities` > 1 or `probabilities` < 0, the sum of all probabilities is > 1, if "II" is specified as a Pauli string, if any Pauli string contains invalid strings, or if the length of probabilities is greater than 4**qubit_count. TypeError: If the type of the dictionary keys are not strings. If the probabilities are not floats. """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) self._probabilities = probabilities if not probabilities: raise ValueError("Pauli dictionary must not be empty.") identity = self.qubit_count * "I" if identity in probabilities: raise ValueError( f"{identity} is not allowed as a key. Please enter only non-identity Pauli strings." ) total_prob = 0 for pauli_string, prob in probabilities.items(): MultiQubitPauliNoise._validate_pauli_string( pauli_string, self.qubit_count, self._allowed_substrings ) if not isinstance(prob, FreeParameterExpression): _validate_param_value(prob, f"probability for {pauli_string}") total_prob += prob if not (1.0 >= total_prob >= 0.0): raise ValueError( "Total probability must be a real number in the interval [0, 1]. " f"Total probability was {total_prob}." ) @classmethod def _validate_pauli_string( cls, pauli_str: str, qubit_count: int, allowed_substrings: set[str] ) -> None: if not isinstance(pauli_str, str): raise TypeError(f"Type of {pauli_str} was not a string.") if len(pauli_str) != qubit_count: raise ValueError( "Length of each Pauli string must be equal to number of qubits. " f"{pauli_str} had length {len(pauli_str)} instead of length {qubit_count}." ) if not set(pauli_str) <= allowed_substrings: raise ValueError( "Strings must be Pauli strings consisting of only [I, X, Y, Z]. " f"Received {pauli_str}." ) def __repr__(self): return ( f"{self.name}('probabilities' : {self._probabilities}, " f"'qubit_count': {self.qubit_count})" ) def __str__(self): return f"{self.name}({self._probabilities})" def __eq__(self, other: MultiQubitPauliNoise): if isinstance(other, MultiQubitPauliNoise): return self.name == other.name and self._probabilities == other._probabilities return False @property def probabilities(self) -> dict[str, float]: """A map of a Pauli string to its corresponding probability. Returns: dict[str, float]: A map of a Pauli string to its corresponding probability. """ return self._probabilities @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. Parameters are in alphabetical order of the Pauli strings in `probabilities`. Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions or fixed values associated with the object. """ return [ self._probabilities[pauli_string] for pauli_string in sorted(self._probabilities.keys()) ]
[docs] def bind_values(self, **kwargs) -> MultiQubitPauliNoise: """Takes in parameters and attempts to assign them to values. Returns: MultiQubitPauliNoise: A new Noise object of the same type with the requested parameters bound. Raises: NotImplementedError: Subclasses should implement this function. """ raise NotImplementedError
[docs] def to_dict(self) -> dict: """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ probabilities = { pauli_string: _parameter_to_dict(prob) for pauli_string, prob in self._probabilities.items() } return { "__class__": self.__class__.__name__, "probabilities": probabilities, "qubit_count": self.qubit_count, "ascii_symbols": self.ascii_symbols, }
[docs] class PauliNoise(Noise, Parameterizable): """Class `PauliNoise` represents the a single-qubit Pauli noise channel acting on one qubit. It is parameterized by three probabilities. """ def __init__( self, probX: Union[FreeParameterExpression, float], probY: Union[FreeParameterExpression, float], probZ: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """Initializes a `PauliNoise`. Args: probX (Union[FreeParameterExpression, float]): The X coefficient of the Kraus operators in the channel. probY (Union[FreeParameterExpression, float]): The Y coefficient of the Kraus operators in the channel. probZ (Union[FreeParameterExpression, float]): The Z coefficient of the Kraus operators in the channel. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count`, `probX` or `probY` or `probZ` is not `float` or FreeParameterExpression, `probX` or `probY` or `probZ` > 1.0, or `probX` or `probY` or `probZ` < 0.0, or `probX`+`probY`+`probZ` > 1 """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) total = 0 total += PauliNoise._get_param_float(probX, "probX") total += PauliNoise._get_param_float(probY, "probY") total += PauliNoise._get_param_float(probZ, "probZ") if total > 1: raise ValueError("the sum of probX, probY, probZ cannot be larger than 1") self._parameters = [probX, probY, probZ] @staticmethod def _get_param_float(param: Union[FreeParameterExpression, float], param_name: str) -> float: """Validates the value of a probability and returns its value. If param is a free parameter expression, this method returns 0. Args: param (Union[FreeParameterExpression, float]): The probability to validate param_name (str): The name of the probability parameter Returns: float: The value of the parameter, or 0 if it is a free parameter expression """ if isinstance(param, FreeParameterExpression): return 0 _validate_param_value(param, param_name) return float(param) @property def probX(self) -> Union[FreeParameterExpression, float]: """The probability of a Pauli X error. Returns: Union[FreeParameterExpression, float]: The probability of a Pauli X error. """ return self._parameters[0] @property def probY(self) -> Union[FreeParameterExpression, float]: """The probability of a Pauli Y error. Returns: Union[FreeParameterExpression, float]: The probability of a Pauli Y error. """ return self._parameters[1] @property def probZ(self) -> Union[FreeParameterExpression, float]: """The probability of a Pauli Z error. Returns: Union[FreeParameterExpression, float]: The probability of a Pauli Z error. """ return self._parameters[2] def __repr__(self): return ( f"{self.name}(" f"'probX': {self._parameters[0]}, " f"'probY': {self._parameters[1]}, " f"'probZ': {self._parameters[2]}, " f"'qubit_count': {self.qubit_count}" f")" ) def __str__(self): return f"{self.name}({self._parameters[0]}, {self._parameters[1]}, {self._parameters[2]})" def __eq__(self, other: PauliNoise): if isinstance(other, PauliNoise): return self.name == other.name and self._parameters == other._parameters return False @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. Parameters are in the order [probX, probY, probZ] Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions or fixed values associated with the object. """ return self._parameters
[docs] def bind_values(self, **kwargs) -> PauliNoise: """Takes in parameters and attempts to assign them to values. Returns: PauliNoise: A new Noise object of the same type with the requested parameters bound. Raises: NotImplementedError: Subclasses should implement this function. """ raise NotImplementedError
[docs] def to_dict(self) -> dict: """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ return { "__class__": self.__class__.__name__, "probX": _parameter_to_dict(self.probX), "probY": _parameter_to_dict(self.probY), "probZ": _parameter_to_dict(self.probZ), "qubit_count": self.qubit_count, "ascii_symbols": self.ascii_symbols, }
[docs] class DampingNoise(Noise, Parameterizable): """Class `DampingNoise` represents a damping noise channel on N qubits parameterized by gamma. """ def __init__( self, gamma: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """Initializes a `DampingNoise`. Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If `qubit_count` < 1, `ascii_symbols` is `None`, `len(ascii_symbols)` != `qubit_count`, `gamma` is not `float` or `FreeParameterExpression`, or `gamma` > 1.0 or `gamma` < 0.0. """ super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) if not isinstance(gamma, FreeParameterExpression): _validate_param_value(gamma, "gamma") self._gamma = gamma @property def gamma(self) -> float: """Probability of damping. Returns: float: Probability of damping. """ return self._gamma def __repr__(self): return f"{self.name}('gamma': {self.gamma}, 'qubit_count': {self.qubit_count})" def __str__(self): return f"{self.name}({self.gamma})" @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions or fixed values associated with the object. """ return [self._gamma] def __eq__(self, other: DampingNoise): if isinstance(other, DampingNoise): return self.name == other.name and self.gamma == other.gamma return False
[docs] def bind_values(self, **kwargs) -> DampingNoise: """Takes in parameters and attempts to assign them to values. Returns: DampingNoise: A new Noise object of the same type with the requested parameters bound. Raises: NotImplementedError: Subclasses should implement this function. """ raise NotImplementedError
[docs] def to_dict(self) -> dict: """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ return { "__class__": self.__class__.__name__, "gamma": _parameter_to_dict(self.gamma), "qubit_count": self.qubit_count, "ascii_symbols": self.ascii_symbols, }
[docs] class GeneralizedAmplitudeDampingNoise(DampingNoise): """Class `GeneralizedAmplitudeDampingNoise` represents the generalized amplitude damping noise channel on N qubits parameterized by gamma and probability. """ def __init__( self, gamma: Union[FreeParameterExpression, float], probability: Union[FreeParameterExpression, float], qubit_count: Optional[int], ascii_symbols: Sequence[str], ): """Inits a `GeneralizedAmplitudeDampingNoise`. Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. probability (Union[FreeParameterExpression, float]): Probability of the system being excited by the environment. qubit_count (Optional[int]): The number of qubits to apply noise. ascii_symbols (Sequence[str]): ASCII string symbols for the noise. These are used when printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. Raises: ValueError: If `qubit_count` < 1, `ascii_symbols` is `None`, `len(ascii_symbols)` != `qubit_count`, `probability` or `gamma` is not `float` or `FreeParameterExpression`, `probability` > 1.0 or `probability` < 0.0, or `gamma` > 1.0 or `gamma` < 0.0. """ super().__init__(gamma=gamma, qubit_count=qubit_count, ascii_symbols=ascii_symbols) if not isinstance(probability, FreeParameterExpression): _validate_param_value(probability, "probability") self._probability = probability @property def probability(self) -> float: """Probability of the system being excited by the environment. Returns: float: Probability of the system being excited by the environment. """ return self._probability def __repr__(self): return ( f"{self.name}('gamma': {self.gamma}, " f"'probability': {self.probability}, " f"'qubit_count': {self.qubit_count})" ) def __str__(self): return f"{self.name}({self.gamma}, {self.probability})" @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. Parameters are in the order [gamma, probability] Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions or fixed values associated with the object. """ return [self.gamma, self.probability] def __eq__(self, other: GeneralizedAmplitudeDampingNoise): if isinstance(other, GeneralizedAmplitudeDampingNoise): return ( self.name == other.name and self.gamma == other.gamma and self.probability == other.probability ) return False
[docs] def to_dict(self) -> dict: """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ return { "__class__": self.__class__.__name__, "gamma": _parameter_to_dict(self.gamma), "probability": _parameter_to_dict(self.probability), "qubit_count": self.qubit_count, "ascii_symbols": self.ascii_symbols, }
def _validate_param_value( parameter: Union[FreeParameterExpression, float], param_name: str, maximum: float = 1.0 ) -> None: """Validates the value of a given parameter. Args: parameter (Union[FreeParameterExpression, float]): The parameter to validate param_name (str): The name of the parameter maximum (float): The maximum value of the parameter. Default: 1.0 """ if not isinstance(parameter, float): raise TypeError(f"{param_name} must be float type") if not (maximum >= parameter >= 0.0): raise ValueError(f"{param_name} must be a real number in the interval [0, {maximum}]") def _parameter_to_dict(parameter: Union[FreeParameter, float]) -> Union[dict, float]: """Converts a parameter to a dictionary if it's a FreeParameter, otherwise returns the float. Args: parameter(Union[FreeParameter, float]): The parameter to convert. Returns: Union[dict, float]: A dictionary representation of a FreeParameter if the parameter is a FreeParameter, otherwise returns the float. """ if isinstance(parameter, FreeParameter): return parameter.to_dict() return parameter