| 1 | ========================= |
|---|
| 2 | The ``parameters`` module |
|---|
| 3 | ========================= |
|---|
| 4 | |
|---|
| 5 | We consider it to be best practice to cleanly separate the parameters of a model |
|---|
| 6 | from the model itself. At the least, parameters should be defined in a separate |
|---|
| 7 | section at the start of a file. Ideally, they should be defined in a separate |
|---|
| 8 | file entirely. This makes version control easier, since the model code typically |
|---|
| 9 | changes less often than the parameters, and makes it easier to track a |
|---|
| 10 | simulation project, since the parameter sets can be stored in a database, |
|---|
| 11 | displayed in a GUI, etc. |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | ---------- |
|---|
| 15 | Parameters |
|---|
| 16 | ---------- |
|---|
| 17 | |
|---|
| 18 | At their simplest, individual parameters consist of a name and a value. The |
|---|
| 19 | value is either a simple type such as a numerical value or a string, or an |
|---|
| 20 | aggregate of such simple types, such as a set, list or array. |
|---|
| 21 | |
|---|
| 22 | However, we may also wish to specify the physical dimensions of the parameter, |
|---|
| 23 | i.e., its units, and the range of permissible values. |
|---|
| 24 | |
|---|
| 25 | It is also often useful to specify an object that generates numerical values or |
|---|
| 26 | strings, such as a random number generator, and treat that object as the |
|---|
| 27 | parameter. |
|---|
| 28 | |
|---|
| 29 | To support all these uses, we define the ``Parameter`` and ``ParameterRange`` |
|---|
| 30 | classes, and various subclasses of the ``ParameterDist`` abstract class, such as |
|---|
| 31 | ``GammaDist``, ``NormalDist`` and ``UniformDist``. |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | The ``Parameter`` class |
|---|
| 35 | ----------------------- |
|---|
| 36 | |
|---|
| 37 | Here are some examples of creating ``Parameter`` objects:: |
|---|
| 38 | |
|---|
| 39 | >>> i1 = Parameter(3) |
|---|
| 40 | >>> f1 = Parameter(6.2) |
|---|
| 41 | >>> f2 = Parameter(-65.3, "mV") |
|---|
| 42 | >>> s1 = Parameter("hello", name="message_to_the_world") |
|---|
| 43 | |
|---|
| 44 | The parameter name, units, value and type can be accessed as attributes:: |
|---|
| 45 | |
|---|
| 46 | >>> i1.value |
|---|
| 47 | 3 |
|---|
| 48 | >>> f1.type |
|---|
| 49 | <type 'float'> |
|---|
| 50 | >>> f2.units |
|---|
| 51 | 'mV' |
|---|
| 52 | >>> s1.name |
|---|
| 53 | 'message_to_the_world' |
|---|
| 54 | |
|---|
| 55 | ``Parameter`` objects are not hugely useful at the moment. The units are not |
|---|
| 56 | used for checking dimensional consistency, for example, and ``Parameter`` |
|---|
| 57 | objects are not drop-in replacements for numerical values - you must always use |
|---|
| 58 | the ``value`` attribute to access the value, whereas it might be nice to define, |
|---|
| 59 | for example, a class ``IntegerParameter`` which was a subclass of the built-in |
|---|
| 60 | ``int`` type. |
|---|
| 61 | |
|---|
| 62 | |
|---|
| 63 | The ``ParameterRange`` class |
|---|
| 64 | ---------------------------- |
|---|
| 65 | |
|---|
| 66 | When investigating the behaviour of a model or in doing sensitivity analysis, it |
|---|
| 67 | is often useful to run a model several times using a different value for a |
|---|
| 68 | certain parameter each time (also see the ``iter_range_keys()`` and similar |
|---|
| 69 | methods of the ``ParameterSet`` class, below). The ``ParameterRange`` class |
|---|
| 70 | supports this. Some usage examples:: |
|---|
| 71 | |
|---|
| 72 | >>> tau_m_range = ParameterRange([10.0, 15.0, 20.0], "ms", "tau_m") |
|---|
| 73 | >>> tau_m_range.name |
|---|
| 74 | 'tau_m' |
|---|
| 75 | >>> tau_m_range.next() |
|---|
| 76 | 10.0 |
|---|
| 77 | >>> tau_m_range.next() |
|---|
| 78 | 15.0 |
|---|
| 79 | >>> [2*tau_m for tau_m in tau_m_range] |
|---|
| 80 | [20.0, 30.0, 40.0] |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | The ``ParameterDist`` classes |
|---|
| 84 | ----------------------------- |
|---|
| 85 | |
|---|
| 86 | As with taking parameter values from a series or range, it is often useful to |
|---|
| 87 | pick values from a particular random distribution. Three classes are available: |
|---|
| 88 | ``UniformDist``, ``GammaDist`` and ``NormalDist``. Examples:: |
|---|
| 89 | |
|---|
| 90 | >>> ud = UniformDist(min=-1.0, max=1.0) |
|---|
| 91 | >>> gd = GammaDist(mean=0.5, std=1.0) |
|---|
| 92 | >>> nd = NormalDist(mean=-70, std=5.0) |
|---|
| 93 | >>> ud.next() |
|---|
| 94 | array([-0.56342352]) |
|---|
| 95 | >>> gd.next(3) |
|---|
| 96 | array([ 0.04061142, 0.05550265, 0.23469344]) |
|---|
| 97 | >>> nd.next(2) |
|---|
| 98 | array([-76.18506715, -68.71229944]) |
|---|
| 99 | |
|---|
| 100 | [Note that very similar functionality is available with the ``RandomDistribution`` |
|---|
| 101 | class in the `pyNN.random` module. We should look at the best ways to avoid |
|---|
| 102 | duplication]. |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | -------------- |
|---|
| 106 | Parameter sets |
|---|
| 107 | -------------- |
|---|
| 108 | |
|---|
| 109 | A problem with parameter sets for large-scale, detailed models is that the list |
|---|
| 110 | of parameters gets very long and unwieldy, and due to the typically hierarchical |
|---|
| 111 | nature of such models, the individual parameter names can also get very long, |
|---|
| 112 | e.g., ``v1_layer5_pyramidal_apical_dend_gbar_na``. |
|---|
| 113 | |
|---|
| 114 | A solution to this is to give the parameter set a hierarchical structure as well, |
|---|
| 115 | which allows the top-level list of parameters to be very short (e.g. ``v1``, |
|---|
| 116 | ``retina`` and ``lgn`` for a visual system simulation) since the top-level |
|---|
| 117 | parameters are themselves parameter sets. |
|---|
| 118 | |
|---|
| 119 | The simplest way to implement this in Python is using nested dicts. One |
|---|
| 120 | disadvantage of this is that accessing deeply-nested parameters can be very |
|---|
| 121 | verbose, e.g. ``v1['layer5']['pyramidal']['apical_dend']['na']['gbar']``. A |
|---|
| 122 | second disadvantage is that it is tedious to flatten the hierarchy when |
|---|
| 123 | this becomes necessary, e.g. for serialisation - writing to file, etc. |
|---|
| 124 | |
|---|
| 125 | For these reasons we have created a ``ParameterSet`` class, which: |
|---|
| 126 | |
|---|
| 127 | 1. allows a more convenient notation; |
|---|
| 128 | |
|---|
| 129 | 2. enables subsets of the parameters, lower in the hierarchy, to be passed |
|---|
| 130 | around by themselves; |
|---|
| 131 | |
|---|
| 132 | 3. provides convenient methods for reading from/writing to file and for |
|---|
| 133 | determining the differences between two different parameter sets. |
|---|
| 134 | |
|---|
| 135 | An example of the notation is ``v1.layer5.pyramidal.apical_dend.na.gbar``, which |
|---|
| 136 | requires only a single `.` for each level in the hierarchy rather than two |
|---|
| 137 | "``'``"s, a "``[``" and a "``]``". This is not much shorter than |
|---|
| 138 | ``v1_layer5_pyramidal_apical_dend_gbar_na`` - the difference is that |
|---|
| 139 | ``v1.layer5.pyramidal`` is itself a ``ParameterSet`` object that can be passed |
|---|
| 140 | as an argument to the pyramidal cell object, which doesn't care about |
|---|
| 141 | ``v1.layer4.spinystellate``, let alone ``retina.ganglioncell.magno.tau_m`` |
|---|
| 142 | (while ``v1_layer5_pyramidal`` is just a ``NameError``). |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | The ``ParameterSet`` class |
|---|
| 146 | -------------------------- |
|---|
| 147 | |
|---|
| 148 | Creation |
|---|
| 149 | ~~~~~~~~ |
|---|
| 150 | |
|---|
| 151 | ``ParameterSet`` objects may be created from a dict:: |
|---|
| 152 | |
|---|
| 153 | >>> sim_params = ParameterSet({'dt': 0.11, 'tstop': 1000.0}) |
|---|
| 154 | |
|---|
| 155 | or loaded from a URL:: |
|---|
| 156 | |
|---|
| 157 | >>> exc_cell_params = ParameterSet("https://neuralensemble.org/svn/NeuroTools/trunk/doc/example.param") |
|---|
| 158 | |
|---|
| 159 | They may be nested:: |
|---|
| 160 | |
|---|
| 161 | >>> inh_cell_params = ParameterSet({'tau_m': 15.0, 'cm': 0.5}) |
|---|
| 162 | >>> network_params = ParameterSet({'excitatory_cells': exc_cell_params, 'inhibitory_cells': inh_cell_params}) |
|---|
| 163 | >>> P = ParameterSet({'sim': sim_params, 'network': network_params}, label="my_params") |
|---|
| 164 | |
|---|
| 165 | Note that although we show here only numerical parameter values, |
|---|
| 166 | ``Parameter``, ``ParameterRange`` and ``ParameterDist`` objects, as well as |
|---|
| 167 | strings, may also be parameter values. |
|---|
| 168 | |
|---|
| 169 | Navigation |
|---|
| 170 | ~~~~~~~~~~ |
|---|
| 171 | |
|---|
| 172 | Individual parameters may be accessed/set using dot notation:: |
|---|
| 173 | |
|---|
| 174 | >>> P.sim.dt |
|---|
| 175 | 0.11 |
|---|
| 176 | >>> P.network.inhibitory_cells.tau_m |
|---|
| 177 | 15.0 |
|---|
| 178 | >>> P.network.inhibitory_cells.cm = 0.75 |
|---|
| 179 | |
|---|
| 180 | or the usual dictionary access notation:: |
|---|
| 181 | |
|---|
| 182 | >>> P['network']['inhibitory_cells']['cm'] |
|---|
| 183 | 0.75 |
|---|
| 184 | |
|---|
| 185 | or mixing the two (which may be required if some of the parameter names contain |
|---|
| 186 | spaces):: |
|---|
| 187 | |
|---|
| 188 | >>> P['network'].excitatory_cells['tau_m'] |
|---|
| 189 | 10.0 |
|---|
| 190 | |
|---|
| 191 | Viewing and saving |
|---|
| 192 | ~~~~~~~~~~~~~~~~~~ |
|---|
| 193 | |
|---|
| 194 | To see the entire parameter set at once, nicely formatted use the ``pretty()`` |
|---|
| 195 | method:: |
|---|
| 196 | |
|---|
| 197 | >>> print P.pretty() |
|---|
| 198 | { |
|---|
| 199 | "network": { |
|---|
| 200 | "excitatory_cells": url("https://neuralensemble.org/svn/NeuroTools/trunk/doc/example.param") |
|---|
| 201 | "inhibitory_cells": { |
|---|
| 202 | "tau_m": 15.0, |
|---|
| 203 | "cm": 0.75, |
|---|
| 204 | }, |
|---|
| 205 | }, |
|---|
| 206 | "sim": { |
|---|
| 207 | "tstop": 1000.0, |
|---|
| 208 | "dt": 0.11, |
|---|
| 209 | }, |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | By default, if the ``ParameterSet`` contains other ``ParameterSet``\s that were |
|---|
| 213 | loaded from URLs, these will be represented with a ``url()`` function in the |
|---|
| 214 | output, but there is also the option to expand all URLs and show the full |
|---|
| 215 | contents:: |
|---|
| 216 | |
|---|
| 217 | >>> print P.pretty(expand_urls=True) |
|---|
| 218 | { |
|---|
| 219 | "network": { |
|---|
| 220 | "excitatory_cells": { |
|---|
| 221 | "tau_refrac": 0.11, |
|---|
| 222 | "tau_m": 10.0, |
|---|
| 223 | "cm": 0.25, |
|---|
| 224 | "synI": { |
|---|
| 225 | "tau": 10.0, |
|---|
| 226 | "E": -75.0, |
|---|
| 227 | }, |
|---|
| 228 | "synE": { |
|---|
| 229 | "tau": 1.5, |
|---|
| 230 | "E": 0.0, |
|---|
| 231 | }, |
|---|
| 232 | "v_thresh": -57.0, |
|---|
| 233 | "v_reset": -70.0, |
|---|
| 234 | "v_rest": -70.0, |
|---|
| 235 | }, |
|---|
| 236 | "inhibitory_cells": { |
|---|
| 237 | "tau_m": 15.0, |
|---|
| 238 | "cm": 0.75, |
|---|
| 239 | }, |
|---|
| 240 | }, |
|---|
| 241 | "sim": { |
|---|
| 242 | "tstop": 1000.0, |
|---|
| 243 | "dt": 0.11, |
|---|
| 244 | }, |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | If a ``ParameterSet`` was loaded from a URL, it may be modified then saved back |
|---|
| 248 | to the same URL, provided the protocol supports writing:: |
|---|
| 249 | |
|---|
| 250 | >>> exc_cell_params.save() |
|---|
| 251 | Traceback (most recent call last): |
|---|
| 252 | File "<stdin>", line 1, in ? |
|---|
| 253 | File "parameters.py", line 266, in save |
|---|
| 254 | raise Exception("Saving using the %s protocol is not implemented" % scheme) |
|---|
| 255 | Exception: Saving using the https protocol is not implemented |
|---|
| 256 | |
|---|
| 257 | or saved to a different URL:: |
|---|
| 258 | |
|---|
| 259 | >>> exc_cell_params.save(url="file:///tmp/exc_params") |
|---|
| 260 | |
|---|
| 261 | The file format is the same as that produced by the ``pretty()`` method. |
|---|
| 262 | |
|---|
| 263 | Copying and converting |
|---|
| 264 | ~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 265 | |
|---|
| 266 | A ``ParameterSet`` can be used simply as a dictionary, but can also be |
|---|
| 267 | converted explicitly to a ``dict`` if required:: |
|---|
| 268 | |
|---|
| 269 | >>> print sim_params.as_dict() |
|---|
| 270 | {'tstop': 1000.0, 'dt': 0.11} |
|---|
| 271 | |
|---|
| 272 | [need to say something about ``tree_copy()``] |
|---|
| 273 | |
|---|
| 274 | Iteration |
|---|
| 275 | ~~~~~~~~~ |
|---|
| 276 | |
|---|
| 277 | There are several different ways to iterate over all or part of the |
|---|
| 278 | ``ParameterSet`` object. ``keys()``, ``values()`` and ``items()`` work as for |
|---|
| 279 | ``dict``s. For the sake of more readable code, ``names()`` is provided as an |
|---|
| 280 | alias for ``keys()`` and ``parameters()`` as an alias for ``items()``:: |
|---|
| 281 | |
|---|
| 282 | >>> P.names() |
|---|
| 283 | ['network', 'sim'] |
|---|
| 284 | >>> exc_cell_params.parameters() |
|---|
| 285 | [('tau_refrac', 0.11), ('tau_m', 10.0), ('cm', 0.25), |
|---|
| 286 | ('synI', {'tau': 10.0, 'E': -75.0}), ('synE', {'tau': 1.5, 'E': 0.0}), |
|---|
| 287 | ('v_thresh', -57.0), ('v_reset', -70.0), ('v_rest', -70.0)] |
|---|
| 288 | |
|---|
| 289 | To flatten nested parameter sets, i.e., the iterate recursively over all |
|---|
| 290 | branches of the tree, the the ``flatten()`` method returns a ``dict`` with keys |
|---|
| 291 | created by joining the names at each hierarchical level with a separator |
|---|
| 292 | character ('.' by default):: |
|---|
| 293 | |
|---|
| 294 | >>> network_params.flatten() |
|---|
| 295 | {'excitatory_cells.synI.E': -75.0, 'excitatory_cells.v_rest': -70.0, |
|---|
| 296 | 'excitatory_cells.tau_refrac': 0.11, 'excitatory_cells.v_reset': -70.0, |
|---|
| 297 | 'excitatory_cells.v_thresh': -57.0, 'excitatory_cells.tau_m': 10.0, |
|---|
| 298 | 'excitatory_cells.synI.tau': 10.0, 'excitatory_cells.cm': 0.25, |
|---|
| 299 | 'inhibitory_cells.cm': 0.75, 'excitatory_cells.synE.tau': 1.5, |
|---|
| 300 | 'excitatory_cells.synE.E': 0.0, 'inhibitory_cells.tau_m': 15.0} |
|---|
| 301 | |
|---|
| 302 | while the ``flat()`` method returns a generator which yields |
|---|
| 303 | ``(name, value)`` tuples.:: |
|---|
| 304 | |
|---|
| 305 | >>> for x in network_params.flat(): |
|---|
| 306 | ... print x |
|---|
| 307 | ... |
|---|
| 308 | |
|---|
| 309 | |
|---|
| 310 | The ``ParameterSpace`` class |
|---|
| 311 | ---------------------------- |
|---|
| 312 | |
|---|
| 313 | The ``ParameterSpace`` class is a subclass of ``ParameterSet`` that is |
|---|
| 314 | allowed to contain ``ParameterRange`` and ``ParameterDist`` objects as |
|---|
| 315 | parameters. This turns the single point in parameter space represented by a |
|---|
| 316 | ``ParameterSet`` into a set of points. For example, the following definition |
|---|
| 317 | creates a set of six points in parameter space, which can be obtained in turn |
|---|
| 318 | using the ``iter_inner()`` method:: |
|---|
| 319 | |
|---|
| 320 | >>> PS = ParameterSpace({ |
|---|
| 321 | ... 'x': 999, |
|---|
| 322 | ... 'y': ParameterRange([10, 20]), |
|---|
| 323 | ... 'z': ParameterRange([-1, 0, 1]) |
|---|
| 324 | ... }) |
|---|
| 325 | >>> for P in PS.iter_inner(): |
|---|
| 326 | ... print P |
|---|
| 327 | {'y': 10, 'x': 999, 'z': -1} |
|---|
| 328 | {'y': 20, 'x': 999, 'z': -1} |
|---|
| 329 | {'y': 10, 'x': 999, 'z': 0} |
|---|
| 330 | {'y': 20, 'x': 999, 'z': 0} |
|---|
| 331 | {'y': 10, 'x': 999, 'z': 1} |
|---|
| 332 | {'y': 20, 'x': 999, 'z': 1} |
|---|
| 333 | |
|---|
| 334 | Putting parameter distribution objects inside a ``ParameterSpace`` allows an |
|---|
| 335 | essentially infinite number of points to be generated:: |
|---|
| 336 | |
|---|
| 337 | >>> PS2 = ParameterSpace({ |
|---|
| 338 | ... 'x': UniformDist(min=-1.0, max=1.0), |
|---|
| 339 | ... 'y': GammaDist(mean=0.5, std=1.0), |
|---|
| 340 | ... 'z': NormalDist(mean=-70, std=5.0) |
|---|
| 341 | ... }) |
|---|
| 342 | >>> for P in PS2.realize_dists(n=3): |
|---|
| 343 | ... print P |
|---|
| 344 | {'y': 1.81311773668, 'x': 0.883293989399, 'z': -73.5871002759} |
|---|
| 345 | {'y': 0.299391158731, 'x': 0.371474054049, 'z': -68.6936045978} |
|---|
| 346 | {'y': 2.90108202422, 'x': -0.388218831787, 'z': -68.6681724449} |
|---|
| 347 | |
|---|
| 348 | |
|---|
| 349 | *This document was last updated for r274.* |
|---|