|
1 | | -"""Parameter scaler classes""" |
| 1 | +"""Module that generates Arbor's iexpr expression language.""" |
2 | 2 |
|
3 | 3 | """ |
4 | | -Copyright (c) 2016-2020, EPFL/Blue Brain Project |
| 4 | +Copyright (c) 2016-2022, EPFL/Blue Brain Project |
5 | 5 |
|
6 | 6 | This file is part of BluePyOpt <https://github.com/BlueBrain/BluePyOpt> |
7 | 7 |
|
|
19 | 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | 20 | """ |
21 | 21 |
|
22 | | -# pylint: disable=W0511 |
23 | | - |
24 | | -import string |
25 | 22 | import ast |
26 | 23 |
|
27 | | -from bluepyopt.ephys.base import BaseEPhys |
28 | | -from bluepyopt.ephys.serializer import DictMixin |
29 | | -from bluepyopt.ephys.morphologies import ArbFileMorphology |
30 | | - |
31 | | -FLOAT_FORMAT = '%.17g' |
32 | | - |
33 | | - |
34 | | -def format_float(value): |
35 | | - """Return formatted float string""" |
36 | | - return FLOAT_FORMAT % value |
37 | | - |
38 | | - |
39 | | -class MissingFormatDict(dict): |
40 | | - |
41 | | - """Extend dict for string formatting with missing values""" |
42 | | - |
43 | | - def __missing__(self, key): # pylint: disable=R0201 |
44 | | - """Return string with format key for missing keys""" |
45 | | - return '{' + key + '}' |
46 | | - |
47 | | - |
48 | | -class ParameterScaler(BaseEPhys): |
49 | | - |
50 | | - """Parameter scalers""" |
51 | | - pass |
52 | | - |
53 | | -# TODO get rid of the 'segment' here |
54 | | - |
55 | | - |
56 | | -class NrnSegmentLinearScaler(ParameterScaler, DictMixin): |
57 | | - |
58 | | - """Linear scaler""" |
59 | | - SERIALIZED_FIELDS = ('name', 'comment', 'multiplier', 'offset', ) |
60 | | - |
61 | | - def __init__( |
62 | | - self, |
63 | | - name=None, |
64 | | - multiplier=1.0, |
65 | | - offset=0.0, |
66 | | - comment=''): |
67 | | - """Constructor |
68 | | -
|
69 | | - Args: |
70 | | - name (str): name of this object |
71 | | - multiplier (float): slope of the linear scaler |
72 | | - offset (float): intercept of the linear scaler |
73 | | - """ |
74 | | - |
75 | | - super(NrnSegmentLinearScaler, self).__init__(name, comment) |
76 | | - self.multiplier = multiplier |
77 | | - self.offset = offset |
78 | | - |
79 | | - def scale(self, value, segment=None, sim=None): # pylint: disable=W0613 |
80 | | - """Scale a value based on a segment""" |
81 | | - |
82 | | - return self.multiplier * value + self.offset |
83 | | - |
84 | | - def __str__(self): |
85 | | - """String representation""" |
86 | | - |
87 | | - return '%s * value + %s' % (self.multiplier, self.offset) |
88 | | - |
89 | | - |
90 | | -class NrnSegmentSomaDistanceScaler(ParameterScaler, DictMixin): |
91 | | - |
92 | | - """Scaler based on distance from soma""" |
93 | | - SERIALIZED_FIELDS = ('name', 'comment', 'distribution', ) |
94 | | - |
95 | | - def __init__( |
96 | | - self, |
97 | | - name=None, |
98 | | - distribution=None, |
99 | | - comment='', |
100 | | - dist_param_names=None, |
101 | | - soma_ref_location=0.5): |
102 | | - """Constructor |
103 | | -
|
104 | | - Args: |
105 | | - name (str): name of this object |
106 | | - distribution (str): distribution of parameter dependent on distance |
107 | | - from soma. string can contain `distance` and/or `value` as |
108 | | - placeholders for the distance to the soma and parameter value |
109 | | - respectivily |
110 | | - dist_params (list): list of names of parameters that parametrise |
111 | | - the distribution. These names will become attributes of this |
112 | | - object. |
113 | | - The distribution string should contain these names, and they |
114 | | - will be replaced by values of the corresponding attributes |
115 | | - soma_ref_location (float): location along the soma used as origin |
116 | | - from which to compute the distances. Expressed as a fraction |
117 | | - (between 0.0 and 1.0). |
118 | | - """ |
119 | | - |
120 | | - super(NrnSegmentSomaDistanceScaler, self).__init__(name, comment) |
121 | | - self.distribution = distribution |
122 | | - |
123 | | - self.dist_param_names = dist_param_names |
124 | | - self.soma_ref_location = soma_ref_location |
125 | | - |
126 | | - if not (0. <= self.soma_ref_location <= 1.): |
127 | | - raise ValueError('soma_ref_location must be between 0 and 1.') |
128 | | - |
129 | | - if self.dist_param_names is not None: |
130 | | - for dist_param_name in self.dist_param_names: |
131 | | - if dist_param_name not in self.distribution: |
132 | | - raise ValueError( |
133 | | - 'NrnSegmentSomaDistanceScaler: "{%s}" ' |
134 | | - 'missing from distribution string "%s"' % |
135 | | - (dist_param_name, distribution)) |
136 | | - setattr(self, dist_param_name, None) |
137 | | - |
138 | | - @property |
139 | | - def inst_distribution(self): |
140 | | - """The instantiated distribution""" |
141 | | - |
142 | | - dist_dict = MissingFormatDict() |
143 | | - |
144 | | - if self.dist_param_names is not None: |
145 | | - for dist_param_name in self.dist_param_names: |
146 | | - dist_param_value = getattr(self, dist_param_name) |
147 | | - if dist_param_value is None: |
148 | | - raise ValueError('NrnSegmentSomaDistanceScaler: %s ' |
149 | | - 'was uninitialised' % dist_param_name) |
150 | | - dist_dict[dist_param_name] = dist_param_value |
151 | | - |
152 | | - # Use this special formatting to bypass missing keys |
153 | | - return string.Formatter().vformat(self.distribution, (), dist_dict) |
154 | | - |
155 | | - def eval_dist(self, value, distance): |
156 | | - """Create the final dist string""" |
157 | | - |
158 | | - scale_dict = {} |
159 | | - scale_dict['distance'] = format_float(distance) |
160 | | - scale_dict['value'] = format_float(value) |
161 | | - |
162 | | - return self.inst_distribution.format(**scale_dict) |
163 | | - |
164 | | - def scale(self, value, segment, sim=None): |
165 | | - """Scale a value based on a segment""" |
166 | | - |
167 | | - # TODO soma needs other addressing scheme |
168 | | - |
169 | | - soma = segment.sec.cell().soma[0] |
170 | | - |
171 | | - # Initialise origin |
172 | | - sim.neuron.h.distance(0, self.soma_ref_location, sec=soma) |
173 | | - |
174 | | - distance = sim.neuron.h.distance(1, segment.x, sec=segment.sec) |
175 | | - |
176 | | - # Find something to generalise this |
177 | | - import math # pylint:disable=W0611 #NOQA |
178 | | - |
179 | | - # This eval is unsafe (but is it ever dangerous ?) |
180 | | - # pylint: disable=W0123 |
181 | | - return eval(self.eval_dist(value, distance)) |
182 | | - |
183 | | - def acc_scale_iexpr(self, value, constant_formatter=format_float): |
184 | | - """Generate Arbor scale iexpr for a given value""" |
185 | | - |
186 | | - iexpr = self.inst_distribution |
187 | | - |
188 | | - variables = dict( |
189 | | - value=value, |
190 | | - distance='(distance %s)' % # could be a ctor param if required |
191 | | - ArbFileMorphology.region_labels['somatic'].ref |
192 | | - ) |
193 | | - |
194 | | - return generate_arbor_iexpr(iexpr, variables, constant_formatter) |
195 | | - |
196 | | - def __str__(self): |
197 | | - """String representation""" |
198 | | - |
199 | | - return self.distribution |
200 | | - |
201 | 24 |
|
202 | 25 | # Utilities to generate Arbor S-expressions for morphologically |
203 | 26 | # inhomogeneous parameter scalers |
@@ -410,7 +233,6 @@ def generate_arbor_iexpr(iexpr, variables, constant_formatter): |
410 | 233 |
|
411 | 234 | # Parse expression |
412 | 235 | scaler_ast = ast.parse(scaler_expr) |
413 | | - |
414 | 236 | # Turn into scaling expression, replacing non-linear occurrences of value |
415 | 237 | value_eliminator = ArbIExprValueEliminator( |
416 | 238 | variable_name='_arb_parse_iexpr_value', |
|
0 commit comments