Skip to content

ParameterSpace

Parameter space

Source code in neurolib/utils/parameterSpace.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
class ParameterSpace:
    """
    Parameter space
    """

    def __init__(self, parameters, parameterValues=None, kind=None, allow_star_notation=False):
        """
        Initialize parameter space. Parameter space can be initialized in two ways:
        Either a `parameters` is a dictionary of the form `{"parName1" : [0, 1, 2], "parName2" : [3, 4]}`,
        or `parameters` is a list of names and `parameterValues` are values of each parameter.

        :param parameters: parameter dictionary or list of names of parameters e.g. `['x', 'y']`
        :type parameters: `dict, list[str, str]`
        :param parameterValues: list of parameter values (must be floats) e.g. `[[x_min, x_max], [y_min, y_max], ...]`
        :type parameterValues: `list[list[float, float]]`
        :param kind: string describing the kind of parameter space:
            - `point`: a single point in parameter space
            - `bound`: a bound in parameter space, i.e. two values per parameter
            - `grid`: a cartesian product over parameters
            - `sequence`: a sequence of univariate parameter changes - only one will change at the time, other
                parameters will stay as default
            - `explicit`: explicitely define a parameter space, i.e lists of all parameters have to have the same length
            - None: parameterSpace tries to auto-detect the correct kind
        :type kind: str
        :param allow_star_notation: whether to allow star notation in parameter names - MultiModel
        :type allow_star_notation: bool
        """
        assert kind in SUPPORTED_KINDS
        self.kind = kind
        self.parameters = parameters
        self.star = allow_star_notation
        # in case a parameter dictionary was given
        if parameterValues is None:
            assert isinstance(
                parameters, dict
            ), "Parameters must be a dict, if no values are given in `parameterValues`"
        else:
            # check if all names are strings
            assert np.all([isinstance(pn, str) for pn in parameters]), "Parameter names must all be strings."
            # check if all parameter values are lists
            assert np.all([isinstance(pv, (list, tuple)) for pv in parameterValues]), "Parameter values must be a list."
            parameters = self._parameterListsToDict(parameters, parameterValues)

        self.parameters = self._processParameterDict(parameters)
        self.parameterNames = list(self.parameters.keys())
        self.parameterValues = list(self.parameters.values())

        # let's create a named tuple of the parameters
        # Note: evolution.py implementation relies on named tuples
        self.named_tuple_constructor = namedtuple("ParameterSpace", sanitize_dot_dict(parameters))
        self.named_tuple = self.named_tuple_constructor(*self.parameterValues)

        # set attributes of this class to make it accessible
        for i, p in enumerate(self.parameters):
            setattr(self, p, self.parameterValues[i])

    def __str__(self):
        """Print the named_tuple object"""
        return str(self.parameters)

    def __getitem__(self, key):
        return self.parameters[key]

    def __setitem__(self, key, value):
        self.parameters[key] = value
        self._processParameterDict(self.parameters)

    def dict(self):
        """Returns the parameter space as a dicitonary of lists.
        :rtype: dict
        """
        return self.parameters

    def get_parametrization(self):
        assert self.kind is not None
        if self.kind in ["point", "bound", "explicit"]:
            # check same length
            it = iter(self.parameters.values())
            length = len(next(it))
            assert all(len(l) == length for l in it)
            # just return as dict
            return self.parameters
        elif self.kind == "grid":
            # cartesian product
            return pypet.cartesian_product(self.parameters)
        elif self.kind == "sequence":
            # return as sequence
            return self._inflate_to_sequence(self.parameters)

    @staticmethod
    def _inflate_to_sequence(param_dict):
        """
        Inflate dict of parameters to a sequence of same length, using None as
        placeholder when a particular parameter should not change.
        {"a": [1, 2], "b": [3, 4, 5]} ->
            {"a": [1, 2, None, None, None], "b": [None, None, 3, 4, 5]}
        """
        return {
            k: [None] * sum([len(tmp) for tmp in list(param_dict.values())[:i]])
            + v
            + [None] * sum([len(tmp) for tmp in list(param_dict.values())[i + 1 :]])
            for i, (k, v) in enumerate(param_dict.items())
        }

    def getRandom(self, safe=False):
        """This function returns a random single parameter from the whole space
        in the form of { "par1" : 1, "par2" : 2}.

        This function is used by neurolib/optimize/exploarion.py
        to add parameters of the space to pypet (for initialization)

        :param safe: Return a "safe" parameter or the original. Safe refers to
        returning python floats, not, for example numpy.float64 (necessary for pypet).
        ;type safe: bool
        """
        randomPar = {}
        if safe:
            for key, value in self.parameters.items():
                random_value = np.random.choice(value)
                if isinstance(random_value, np.float64):
                    random_value = float(random_value)
                elif isinstance(random_value, np.int64):
                    random_value = int(random_value)
                randomPar[key] = random_value
        else:
            for key, value in self.parameters.items():
                randomPar[key] = np.random.choice(value)
        return randomPar

    @property
    def lowerBound(self):
        """Returns lower bound of all parameters as a list"""
        return [np.min(p) for p in self.parameterValues]

    @property
    def upperBound(self):
        """Returns upper bound of all parameters as a list"""
        return [np.max(p) for p in self.parameterValues]

    @property
    def ndims(self):
        """Number of dimensions (parameters)"""
        return len(self.parameters)

    @staticmethod
    def _validate_single_bound(single_bound):
        """
        Validate single bound.
        :param single_bound: single coordinate bound to validate
        :type single_bound: list|tuple
        """
        assert isinstance(
            single_bound, (list, tuple)
        ), "An error occured while validating the ParameterSpace of kind 'bound': Pass parameter bounds as a list or tuple!"
        assert (
            len(single_bound) == 2
        ), "An error occured while validating the ParameterSpace of kind 'bound': Only two bounds (min and max) are allowed"
        assert (
            single_bound[1] > single_bound[0]
        ), "An error occured while validating the ParameterSpace of kind 'bound': Minimum parameter value can't be larger than the maximum!"

    def _validate_param_bounds(self, param_bounds):
        """
        Validate param bounds.
        :param param_bounds: parameter bounds to validate
        :type param_bounds: list|None
        """
        assert param_bounds is not None
        assert isinstance(param_bounds, (list, tuple))
        # check every single parameter bound
        for single_bound in param_bounds:
            self._validate_single_bound(single_bound)

    def _processParameterDict(self, parameters):
        """Processes all parameters and do checks. Determine the kind of the parameter space.
        :param parameters: parameter dictionary
        :type param: dict

        :retun: processed parameter dictionary
        :rtype: dict
        """

        # convert all parameter arrays into lists
        for key, value in parameters.items():
            if isinstance(value, np.ndarray):
                assert len(value.shape) == 1, f"Parameter {key} is not one-dimensional."
                value = value.tolist()
                parameters[key] = value

        # auto detect the parameter kind
        if self.kind is None:
            for key, value in parameters.items():
                # auto detect what kind of space we have
                # kind = "point" is a single point in parameter space, one value only
                # kind = "bound" is a bounded parameter space with 2 values: min and max
                # kind = "grid" is a grid space with as many values on each axis as wished

                # first, we assume grid
                self.kind = "grid"
                parameterLengths = [len(value) for key, value in parameters.items()]
                # if all parameters have the same length
                if parameterLengths.count(parameterLengths[0]) == len(parameterLengths):
                    if parameterLengths[0] == 1:
                        self.kind = "point"
                    elif parameterLengths[0] == 2:
                        self.kind = "bound"
            logging.info(f'Assuming parameter kind "{self.kind}"')

        # do some kind-specific tests
        if self.kind == "bound":
            # check the boundaries
            self._validate_param_bounds(list(parameters.values()))

        # set all parameters as attributes for easy access
        for key, value in parameters.items():
            setattr(self, key, value)

        return parameters

    def _parameterListsToDict(self, keys, values):
        parameters = {}
        assert len(keys) == len(values), "Names and values of parameters are not same length."
        for key, value in zip(keys, values):
            parameters[key] = value
        return parameters

lowerBound property

Returns lower bound of all parameters as a list

ndims property

Number of dimensions (parameters)

upperBound property

Returns upper bound of all parameters as a list

__init__(parameters, parameterValues=None, kind=None, allow_star_notation=False)

Initialize parameter space. Parameter space can be initialized in two ways: Either a parameters is a dictionary of the form {"parName1" : [0, 1, 2], "parName2" : [3, 4]}, or parameters is a list of names and parameterValues are values of each parameter.

Parameters:

Name Type Description Default
parameters `dict, list[str, str]`

parameter dictionary or list of names of parameters e.g. ['x', 'y']

required
parameterValues `list[list[float, float]]`

list of parameter values (must be floats) e.g. [[x_min, x_max], [y_min, y_max], ...]

None
kind str

string describing the kind of parameter space: - point: a single point in parameter space - bound: a bound in parameter space, i.e. two values per parameter - grid: a cartesian product over parameters - sequence: a sequence of univariate parameter changes - only one will change at the time, other parameters will stay as default - explicit: explicitely define a parameter space, i.e lists of all parameters have to have the same length - None: parameterSpace tries to auto-detect the correct kind

None
allow_star_notation bool

whether to allow star notation in parameter names - MultiModel

False
Source code in neurolib/utils/parameterSpace.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __init__(self, parameters, parameterValues=None, kind=None, allow_star_notation=False):
    """
    Initialize parameter space. Parameter space can be initialized in two ways:
    Either a `parameters` is a dictionary of the form `{"parName1" : [0, 1, 2], "parName2" : [3, 4]}`,
    or `parameters` is a list of names and `parameterValues` are values of each parameter.

    :param parameters: parameter dictionary or list of names of parameters e.g. `['x', 'y']`
    :type parameters: `dict, list[str, str]`
    :param parameterValues: list of parameter values (must be floats) e.g. `[[x_min, x_max], [y_min, y_max], ...]`
    :type parameterValues: `list[list[float, float]]`
    :param kind: string describing the kind of parameter space:
        - `point`: a single point in parameter space
        - `bound`: a bound in parameter space, i.e. two values per parameter
        - `grid`: a cartesian product over parameters
        - `sequence`: a sequence of univariate parameter changes - only one will change at the time, other
            parameters will stay as default
        - `explicit`: explicitely define a parameter space, i.e lists of all parameters have to have the same length
        - None: parameterSpace tries to auto-detect the correct kind
    :type kind: str
    :param allow_star_notation: whether to allow star notation in parameter names - MultiModel
    :type allow_star_notation: bool
    """
    assert kind in SUPPORTED_KINDS
    self.kind = kind
    self.parameters = parameters
    self.star = allow_star_notation
    # in case a parameter dictionary was given
    if parameterValues is None:
        assert isinstance(
            parameters, dict
        ), "Parameters must be a dict, if no values are given in `parameterValues`"
    else:
        # check if all names are strings
        assert np.all([isinstance(pn, str) for pn in parameters]), "Parameter names must all be strings."
        # check if all parameter values are lists
        assert np.all([isinstance(pv, (list, tuple)) for pv in parameterValues]), "Parameter values must be a list."
        parameters = self._parameterListsToDict(parameters, parameterValues)

    self.parameters = self._processParameterDict(parameters)
    self.parameterNames = list(self.parameters.keys())
    self.parameterValues = list(self.parameters.values())

    # let's create a named tuple of the parameters
    # Note: evolution.py implementation relies on named tuples
    self.named_tuple_constructor = namedtuple("ParameterSpace", sanitize_dot_dict(parameters))
    self.named_tuple = self.named_tuple_constructor(*self.parameterValues)

    # set attributes of this class to make it accessible
    for i, p in enumerate(self.parameters):
        setattr(self, p, self.parameterValues[i])

__str__()

Print the named_tuple object

Source code in neurolib/utils/parameterSpace.py
72
73
74
def __str__(self):
    """Print the named_tuple object"""
    return str(self.parameters)

dict()

Returns the parameter space as a dicitonary of lists.

Source code in neurolib/utils/parameterSpace.py
83
84
85
86
87
def dict(self):
    """Returns the parameter space as a dicitonary of lists.
    :rtype: dict
    """
    return self.parameters

getRandom(safe=False)

This function returns a random single parameter from the whole space in the form of { "par1" : 1, "par2" : 2}.

This function is used by neurolib/optimize/exploarion.py to add parameters of the space to pypet (for initialization)

Parameters:

Name Type Description Default
safe

Return a "safe" parameter or the original. Safe refers to returning python floats, not, for example numpy.float64 (necessary for pypet). ;type safe: bool

False
Source code in neurolib/utils/parameterSpace.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def getRandom(self, safe=False):
    """This function returns a random single parameter from the whole space
    in the form of { "par1" : 1, "par2" : 2}.

    This function is used by neurolib/optimize/exploarion.py
    to add parameters of the space to pypet (for initialization)

    :param safe: Return a "safe" parameter or the original. Safe refers to
    returning python floats, not, for example numpy.float64 (necessary for pypet).
    ;type safe: bool
    """
    randomPar = {}
    if safe:
        for key, value in self.parameters.items():
            random_value = np.random.choice(value)
            if isinstance(random_value, np.float64):
                random_value = float(random_value)
            elif isinstance(random_value, np.int64):
                random_value = int(random_value)
            randomPar[key] = random_value
    else:
        for key, value in self.parameters.items():
            randomPar[key] = np.random.choice(value)
    return randomPar