| 206 | | """ |
| 207 | | Metaclass for building NineMLCellType subclasses |
| 208 | | """ |
| 209 | | def __new__(cls, name, bases, dct): |
| 210 | | # join the neuron and synapse components into a single component |
| 211 | | combined_model = dct["neuron_model"] |
| 212 | | for label in dct["synapse_models"].keys(): |
| 213 | | port_map = dct["port_map"][label] |
| 214 | | port_map = _add_prefix(dct["synapse_models"][label], label, port_map) |
| 215 | | dct["weight_variables"][label] = label + "_" + dct["weight_variables"][label] |
| 216 | | combined_model = join(combined_model, |
| 217 | | dct["synapse_models"][label], |
| 218 | | port_map, |
| 219 | | name=name) |
| 220 | | dct["combined_model"] = combined_model |
| 221 | | # set class attributes required for a PyNN cell type class |
| 222 | | dct["default_parameters"] = dict((name, 1.0) |
| 223 | | for name in combined_model.parameters) |
| 224 | | dct["default_initial_values"] = dict((name, 0.0) |
| 225 | | for name in combined_model.state_variables) |
| 226 | | dct["synapse_types"] = sorted(dct["synapse_models"]) # using alphabetical order may make things easier code generators |
| 227 | | dct["injectable"] = True # need to determine this. How?? |
| 228 | | dct["recordable"] = [port.name for port in combined_model.analog_ports] + ['spikes', 'regime'] |
| 229 | | dct["standard_receptor_type"] = (dct["synapse_types"] == ('excitatory', 'inhibitory')) |
| 230 | | dct["conductance_based"] = True # how to determine this?? |
| 231 | | dct["model_name"] = name |
| 232 | | logger.debug("Creating class '%s' with bases %s and dictionary %s" % (name, bases, dct)) |
| 233 | | # generate and compile code, then load the mechanism into the simulator |
| 234 | | dct["builder"](combined_model, dct["weight_variables"]) # weight variables should really be stored within combined_model |
| 235 | | return type.__new__(cls, name, bases, dct) |
| 236 | | |
| 237 | | |
| 238 | | |
| 239 | | def join(c1, c2, port_map=[], name=None): |
| 240 | | """Create a NineML component by joining the two given components.""" |
| 241 | | logger.debug("Joining components %s and %s with port map %s" % (c1, c2, port_map)) |
| 242 | | logger.debug("New component will have name '%s'" % name) |
| 243 | | # combine bindings from c1 and c2 |
| 244 | | bindings = {} |
| 245 | | for b in chain(c1.bindings, c2.bindings): |
| 246 | | bindings[b.name] = b |
| 247 | | # combine ports (some will later be removed) |
| 248 | | all_ports = c1.ports_map.copy() |
| 249 | | all_ports.update(c2.ports_map) |
| 250 | | # event ports do not be passed to the constructor, as they are attached to transitions |
| 251 | | for port_name, port in all_ports.items(): |
| 252 | | if isinstance(port, nineml.EventPort): |
| 253 | | all_ports.pop(port_name) |
| 254 | | # connect ports. |
| 255 | | # currently, when ports are connected they disappear. It might be better to |
| 256 | | # explicitly keep the ports in the new component but mark them as connected |
| 257 | | for name1, name2 in port_map: |
| 258 | | assert name1 in c1.ports_map, "%s is not in %s" % (name1, c1.ports_map.keys()) |
| 259 | | assert name2 in c2.ports_map, "%s is not in %s" % (name2, c2.ports_map.keys()) |
| 260 | | |
| 261 | | port1 = c1.ports_map[name1] |
| 262 | | port2 = c2.ports_map[name2] |
| 263 | | assert port1.mode != port2.mode |
| 264 | | if port1.mode == 'send': |
| 265 | | send_port = port1 |
| 266 | | recv_port = port2 |
| 267 | | send_port_name = name1 |
| 268 | | recv_port_name = name2 |
| 269 | | else: |
| 270 | | send_port = port2 |
| 271 | | recv_port = port1 |
| 272 | | send_port_name = name2 |
| 273 | | recv_port_name = name1 |
| 274 | | # when connecting ports in which the send port has an expression, need |
| 275 | | # to create a binding for this expression in the new component |
| 276 | | if send_port.expr: |
| 277 | | func_args = c1.non_parameter_symbols.union(c2.non_parameter_symbols).intersection(send_port.expr.names) |
| 278 | | lhs = "%s(%s)" % (send_port_name, ",".join(func_args)) |
| 279 | | send_binding = nineml.Binding(lhs, send_port.expr.rhs) |
| 280 | | bindings[send_binding.name] = send_binding |
| 281 | | for eq in chain(c1.equations, c2.equations): |
| 282 | | if send_port_name in eq.names: |
| 283 | | eq.rhs = eq.rhs_name_transform({send_port_name: lhs}) |
| 284 | | if recv_port.mode == 'reduce': |
| 285 | | # need to retain reduce ports as they can be connected to in a future join |
| 286 | | if recv_port_name in bindings: |
| 287 | | # this reduce port has already been connected to, so combine using its reduce_op |
| 288 | | reduce_binding = bindings[recv_port_name] |
| 289 | | func_args = func_args.union(reduce_binding.args) |
| 290 | | lhs = "%s(%s)" % (recv_port_name, ",".join(func_args)) |
| 291 | | rhs = recv_port.reduce_op.join([reduce_binding.rhs, send_binding.lhs]) |
| 292 | | else: |
| 293 | | # this is the first time this reduce port has been connected to |
| 294 | | lhs = "%s(%s)" % (recv_port_name, ",".join(func_args)) |
| 295 | | rhs = send_binding.lhs |
| 296 | | bindings[recv_port_name] = nineml.Binding(lhs, rhs) |
| 297 | | recv_port.connected = True |
| 298 | | else: |
| 299 | | all_ports.pop(name1) |
| 300 | | else: |
| 301 | | if recv_port.mode == 'reduce': |
| 302 | | raise NotImplementedError |
| 303 | | else: |
| 304 | | all_ports.pop(name1) |
| 305 | | |
| 306 | | if name1 != name2: |
| 307 | | #c2.substitute(name2, name1) # need to implement this. Currently this all only works if name1 == name2 |
| 308 | | # probably needs to happen sooner in the function |
| 309 | | all_ports.pop(name2) |
| 310 | | |
| 311 | | # where parameters have become bindings due to connecting ports, replace |
| 312 | | # bare names with function calls in the equations |
| 313 | | for bname, binding in bindings.items(): |
| 314 | | for eq in chain(c1.equations, c2.equations): |
| 315 | | if bname in eq.names: |
| 316 | | print "#### replacing %s by %s" % (bname, binding.lhs) |
| 317 | | pattern = re.compile(r'%s(\([\w\, ]*\))?' % bname) |
| 318 | | m = pattern.search(eq.rhs) |
| 319 | | if m: |
| 320 | | eq.rhs = pattern.sub(binding.lhs, eq.rhs) |
| 321 | | else: |
| 322 | | eq.rhs = eq.rhs_name_transform({bname: binding.lhs}) |
| 323 | | |
| 324 | | # create new regimes from all possible combinations of the regimes from the |
| 325 | | # two components |
| 326 | | regime_map = {} |
| 327 | | for r1 in c1.regimes: |
| 328 | | regime_map[r1.name] = {} |
| 329 | | for r2 in c2.regimes: |
| 330 | | if r1.name == r2.name: |
| 331 | | new_name = r1.name |
| 332 | | else: |
| 333 | | new_name = "%s_AND_%s" % (r1.name, r2.name) |
| 334 | | kwargs = {'name': new_name} |
| 335 | | new_regime = nineml.Regime(*r1.nodes.union(r2.nodes), **kwargs) |
| 336 | | regime_map[r1.name][r2.name] = new_regime |
| 337 | | # create transitions between all the new regimes |
| 338 | | transitions = [] |
| 339 | | for r1 in c1.regimes: |
| 340 | | for r2 in c2.regimes: |
| 341 | | for t in r1.transitions: |
| 342 | | new_transition = nineml.Transition(*t.nodes, |
| 343 | | from_=regime_map[r1.name][r2.name], |
| 344 | | to=regime_map[t.to.name][r2.name], |
| 345 | | condition=t.condition) |
| 346 | | transitions.append(new_transition) |
| 347 | | for t in r2.transitions: |
| 348 | | new_transition = nineml.Transition(*t.nodes, |
| 349 | | from_=regime_map[r1.name][r2.name], |
| 350 | | to=regime_map[r1.name][t.to.name], |
| 351 | | condition=t.condition) |
| 352 | | transitions.append(new_transition) |
| 353 | | |
| 354 | | regimes = [] |
| 355 | | for d in regime_map.values(): |
| 356 | | regimes.extend(d.values()) |
| 357 | | name = name or "%s__%s" % (c1.name, c2.name) |
| 358 | | return nineml.Component(name, |
| 359 | | regimes=regimes, |
| 360 | | transitions=transitions, |
| 361 | | ports=all_ports.values(), |
| 362 | | bindings=bindings.values()) |
| 363 | | |
| | 206 | pass |
| | 207 | |
| | 208 | #class _build_nineml_celltype(type): |
| | 209 | # """ |
| | 210 | # Metaclass for building NineMLCellType subclasses |
| | 211 | # """ |
| | 212 | # def __new__(cls, name, bases, dct): |
| | 213 | # assert False |
| | 214 | # |
| | 215 | # # join the neuron and synapse components into a single component |
| | 216 | # combined_model = dct["neuron_model"] |
| | 217 | # for label in dct["synapse_models"].keys(): |
| | 218 | # port_map = dct["port_map"][label] |
| | 219 | # port_map = _add_prefix(dct["synapse_models"][label], label, port_map) |
| | 220 | # dct["weight_variables"][label] = label + "_" + dct["weight_variables"][label] |
| | 221 | # combined_model = join(combined_model, |
| | 222 | # dct["synapse_models"][label], |
| | 223 | # port_map, |
| | 224 | # name=name) |
| | 225 | # dct["combined_model"] = combined_model |
| | 226 | # # set class attributes required for a PyNN cell type class |
| | 227 | # dct["default_parameters"] = dict((name, 1.0) |
| | 228 | # for name in combined_model.parameters) |
| | 229 | # dct["default_initial_values"] = dict((name, 0.0) |
| | 230 | # for name in combined_model.state_variables) |
| | 231 | # dct["synapse_types"] = sorted(dct["synapse_models"]) # using alphabetical order may make things easier code generators |
| | 232 | # dct["injectable"] = True # need to determine this. How?? |
| | 233 | # dct["recordable"] = [port.name for port in combined_model.analog_ports] + ['spikes', 'regime'] |
| | 234 | # dct["standard_receptor_type"] = (dct["synapse_types"] == ('excitatory', 'inhibitory')) |
| | 235 | # dct["conductance_based"] = True # how to determine this?? |
| | 236 | # dct["model_name"] = name |
| | 237 | # logger.debug("Creating class '%s' with bases %s and dictionary %s" % (name, bases, dct)) |
| | 238 | # # generate and compile code, then load the mechanism into the simulator |
| | 239 | # dct["builder"](combined_model, dct["weight_variables"]) # weight variables should really be stored within combined_model |
| | 240 | # return type.__new__(cls, name, bases, dct) |
| | 241 | # |
| | 242 | # |
| | 243 | # |
| | 244 | #def join(c1, c2, port_map=[], name=None): |
| | 245 | # """Create a NineML component by joining the two given components.""" |
| | 246 | # logger.debug("Joining components %s and %s with port map %s" % (c1, c2, port_map)) |
| | 247 | # logger.debug("New component will have name '%s'" % name) |
| | 248 | # # combine bindings from c1 and c2 |
| | 249 | # bindings = {} |
| | 250 | # for b in chain(c1.bindings, c2.bindings): |
| | 251 | # bindings[b.name] = b |
| | 252 | # # combine ports (some will later be removed) |
| | 253 | # all_ports = c1.ports_map.copy() |
| | 254 | # all_ports.update(c2.ports_map) |
| | 255 | # # event ports do not be passed to the constructor, as they are attached to transitions |
| | 256 | # for port_name, port in all_ports.items(): |
| | 257 | # if isinstance(port, nineml.EventPort): |
| | 258 | # all_ports.pop(port_name) |
| | 259 | # # connect ports. |
| | 260 | # # currently, when ports are connected they disappear. It might be better to |
| | 261 | # # explicitly keep the ports in the new component but mark them as connected |
| | 262 | # for name1, name2 in port_map: |
| | 263 | # assert name1 in c1.ports_map, "%s is not in %s" % (name1, c1.ports_map.keys()) |
| | 264 | # assert name2 in c2.ports_map, "%s is not in %s" % (name2, c2.ports_map.keys()) |
| | 265 | # |
| | 266 | # port1 = c1.ports_map[name1] |
| | 267 | # port2 = c2.ports_map[name2] |
| | 268 | # assert port1.mode != port2.mode |
| | 269 | # if port1.mode == 'send': |
| | 270 | # send_port = port1 |
| | 271 | # recv_port = port2 |
| | 272 | # send_port_name = name1 |
| | 273 | # recv_port_name = name2 |
| | 274 | # else: |
| | 275 | # send_port = port2 |
| | 276 | # recv_port = port1 |
| | 277 | # send_port_name = name2 |
| | 278 | # recv_port_name = name1 |
| | 279 | # # when connecting ports in which the send port has an expression, need |
| | 280 | # # to create a binding for this expression in the new component |
| | 281 | # if send_port.expr: |
| | 282 | # func_args = c1.non_parameter_symbols.union(c2.non_parameter_symbols).intersection(send_port.expr.names) |
| | 283 | # lhs = "%s(%s)" % (send_port_name, ",".join(func_args)) |
| | 284 | # send_binding = nineml.Binding(lhs, send_port.expr.rhs) |
| | 285 | # bindings[send_binding.name] = send_binding |
| | 286 | # for eq in chain(c1.equations, c2.equations): |
| | 287 | # if send_port_name in eq.names: |
| | 288 | # eq.rhs = eq.rhs_name_transform({send_port_name: lhs}) |
| | 289 | # if recv_port.mode == 'reduce': |
| | 290 | # # need to retain reduce ports as they can be connected to in a future join |
| | 291 | # if recv_port_name in bindings: |
| | 292 | # # this reduce port has already been connected to, so combine using its reduce_op |
| | 293 | # reduce_binding = bindings[recv_port_name] |
| | 294 | # func_args = func_args.union(reduce_binding.args) |
| | 295 | # lhs = "%s(%s)" % (recv_port_name, ",".join(func_args)) |
| | 296 | # rhs = recv_port.reduce_op.join([reduce_binding.rhs, send_binding.lhs]) |
| | 297 | # else: |
| | 298 | # # this is the first time this reduce port has been connected to |
| | 299 | # lhs = "%s(%s)" % (recv_port_name, ",".join(func_args)) |
| | 300 | # rhs = send_binding.lhs |
| | 301 | # bindings[recv_port_name] = nineml.Binding(lhs, rhs) |
| | 302 | # recv_port.connected = True |
| | 303 | # else: |
| | 304 | # all_ports.pop(name1) |
| | 305 | # else: |
| | 306 | # if recv_port.mode == 'reduce': |
| | 307 | # raise NotImplementedError |
| | 308 | # else: |
| | 309 | # all_ports.pop(name1) |
| | 310 | # |
| | 311 | # if name1 != name2: |
| | 312 | # #c2.substitute(name2, name1) # need to implement this. Currently this all only works if name1 == name2 |
| | 313 | # # probably needs to happen sooner in the function |
| | 314 | # all_ports.pop(name2) |
| | 315 | # |
| | 316 | # # where parameters have become bindings due to connecting ports, replace |
| | 317 | # # bare names with function calls in the equations |
| | 318 | # for bname, binding in bindings.items(): |
| | 319 | # for eq in chain(c1.equations, c2.equations): |
| | 320 | # if bname in eq.names: |
| | 321 | # print "#### replacing %s by %s" % (bname, binding.lhs) |
| | 322 | # pattern = re.compile(r'%s(\([\w\, ]*\))?' % bname) |
| | 323 | # m = pattern.search(eq.rhs) |
| | 324 | # if m: |
| | 325 | # eq.rhs = pattern.sub(binding.lhs, eq.rhs) |
| | 326 | # else: |
| | 327 | # eq.rhs = eq.rhs_name_transform({bname: binding.lhs}) |
| | 328 | # |
| | 329 | # # create new regimes from all possible combinations of the regimes from the |
| | 330 | # # two components |
| | 331 | # regime_map = {} |
| | 332 | # for r1 in c1.regimes: |
| | 333 | # regime_map[r1.name] = {} |
| | 334 | # for r2 in c2.regimes: |
| | 335 | # if r1.name == r2.name: |
| | 336 | # new_name = r1.name |
| | 337 | # else: |
| | 338 | # new_name = "%s_AND_%s" % (r1.name, r2.name) |
| | 339 | # kwargs = {'name': new_name} |
| | 340 | # new_regime = nineml.Regime(*r1.nodes.union(r2.nodes), **kwargs) |
| | 341 | # regime_map[r1.name][r2.name] = new_regime |
| | 342 | # # create transitions between all the new regimes |
| | 343 | # transitions = [] |
| | 344 | # for r1 in c1.regimes: |
| | 345 | # for r2 in c2.regimes: |
| | 346 | # for t in r1.transitions: |
| | 347 | # new_transition = nineml.Transition(*t.nodes, |
| | 348 | # from_=regime_map[r1.name][r2.name], |
| | 349 | # to=regime_map[t.to.name][r2.name], |
| | 350 | # condition=t.condition) |
| | 351 | # transitions.append(new_transition) |
| | 352 | # for t in r2.transitions: |
| | 353 | # new_transition = nineml.Transition(*t.nodes, |
| | 354 | # from_=regime_map[r1.name][r2.name], |
| | 355 | # to=regime_map[r1.name][t.to.name], |
| | 356 | # condition=t.condition) |
| | 357 | # transitions.append(new_transition) |
| | 358 | # |
| | 359 | # regimes = [] |
| | 360 | # for d in regime_map.values(): |
| | 361 | # regimes.extend(d.values()) |
| | 362 | # name = name or "%s__%s" % (c1.name, c2.name) |
| | 363 | # return nineml.Component(name, |
| | 364 | # regimes=regimes, |
| | 365 | # transitions=transitions, |
| | 366 | # ports=all_ports.values(), |
| | 367 | # bindings=bindings.values()) |
| | 368 | # |