Source code for netpyne.sim.validator

import numpy as np
from schema import Schema, Optional, And, Or, Use, SchemaError
from collections import ChainMap

try:
    basestring
except NameError:
    basestring = str

[docs] class ValidationContext(object): def __init__(self, netParams): self.cellParams = netParams.cellParams self.popParams = netParams.popParams self.synMechParams = netParams.synMechParams self.stimSourceParams = netParams.stimSourceParams self.validateModels = True # cfg.validateNetParamsMechs
numberOrStringFunc = Or(int, float, str, error='Expected a number (int, float) or a function as string.')
[docs] def general_specs(): specs = { '_labelid': int, 'scale': Or(int, float), 'sizeX': Or(int, float), 'sizeY': Or(int, float), 'sizeZ': Or(int, float), 'shape': And(str, Use(str.lower), lambda s: s in ['cuboid', 'cylinder', 'ellipsoid']), 'rotateCellsRandomly': Or( And(bool, lambda s: s == False), Or(And(bool, lambda s: s == True), And([Or(int, float)], lambda s: len(s) == 2)), ), 'defineCellShapes': bool, 'correctBorder': Or( And(bool, lambda s: s == False), { 'threshold': And([Or(int, float)], lambda s: len(s) == 3), Optional('xborders'): And([Or(int, float)], lambda s: len(s) == 2), Optional('yborders'): And([Or(int, float)], lambda s: len(s) == 2), Optional('zborders'): And([Or(int, float)], lambda s: len(s) == 2), }, ), 'cellsVisualizationSpacingMultiplier': And([Or(int, float)], lambda s: len(s) == 3), 'scaleConnWeight': Or(int, float), 'scaleConnWeightNetStims': Or(int, float), 'scaleConnWeightModels': Or( And(bool, lambda s: s == False), {str: Or(int, float)} ), # not any str -- To properly work, each cell (updating its weight) should have a tag 'cellModel' and the str should call it. Otherwise, it uses "scaleConnWeight" -- NOT CONSIDERED FOR VALIDATION 'defaultWeight': Or(int, float), 'defaultDelay': Or(int, float), 'defaultThreshold': Or(int, float), 'propVelocity': Or(int, float), 'mapping': {Optional(str): Or(str, [str])}, 'popTagsCopiedToCells': [str], Optional(str): object # maybe other definitions, mostly to be used in string-based functions # Restrictions for 'popTagsCopiedToCells': # 1) Not any str -- it should be the defaults ('cellModel', 'cellType'), which may be or not in the tags of the populations, plus the real tags present in the populations (popParams entries + 'pop' corresponding to the label of the population) # 2) Also, if the cellParams do not have "conds", the cells are defined by the label of the cell rule and "cellType" should be inherited from the pop. ##### The effective list of "popTagsCopiedToCells" with be validated afterwards, once the cellParams and popParams were validated } return specs
[docs] def pop_specs(context): specs = { str: { # TODO: either cellType or cellModel has to be present?? Optional('cellType'): And( str, lambda s: __isKeyIn(s, context.cellParams) or __isAmongConds(s, 'cellType', context.cellParams) ), Optional('cellModel'): And( str, # Either NEURON model or conds cellModel lambda s: __isArtificialCellModel(s, context) or __isAmongConds(s, 'cellModel', context.cellParams) ), Optional('originalFormat'): lambda s: s in ['NeuroML2','NeuroML2_SpikeSource'], # Not from specs (I think they are from imported models) Optional('cellsList'): [ { Optional('x'): Or(int, float), Optional('y'): Or(int, float), Optional('z'): Or(int, float), Optional('xnorm'): Or(int, float), Optional('ynorm'): Or(int, float), Optional('znorm'): Or(int, float), Optional('spkTimes'): Or(Or(list, tuple), lambda s: isinstance(s, np.ndarray)), Optional('params'): { str: object # specific params - useful in cases of a pointCell or when using pointps in compartCell }, Optional( str ): object, # may be other tags, used-defined (foe example, cellLabel in an example from NetPyNE) } ], Optional('numCells'): Or(int, float), Optional('density'): numberOrStringFunc, # string-based function is allowed Optional('gridSpacing'): Or(And([Or(int, float)], lambda s: len(s) == 3), Or(int, float)), Optional('xRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('yRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('zRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('xnormRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('ynormRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('znormRange'): And([Or(int, float)], lambda s: len(s) == 2), Optional('spkTimes'): Or( [[Or(int, float)]], [Or(int, float)] ), # 2D array (list of times for each cell) or 1D (same list for all cells) Optional('diversity'): bool, # this option is optional, but conditional to numCells (or an empty definition regarding the extension of the net -by default set to numCells=1-) # Also, it is valid only for NetStim Optional('dynamicRates'): { Optional('rates'): [Or(Or(int, float), [Or(int, float)])], # the embedded list should match numCells Optional('times'): [Or(int, float)], # both lists should have the same lenght }, # Following, all definitions associated to the specification of a population of pointCells directly from popParams # It is the same as the "Optional('params')" in cellParams Optional('seed'): Or(int, float), Optional('rate'): Or( Or(int, float), And([Or(int, float)], lambda s: len(s) == 2) # a value ), # inferior and superior bounds - random value in this range # Option for implementing time-dependent rates (for NetStims only) Optional('rates'): Or( Or( int, float ), # this option works, but because of the default values for the "interval" definition - it does not implement a time-dependent rate And([[Or(int, float)]], lambda s: len(s) == 2), ), Optional('interval'): Or(int, float), # When 'cellModel' == 'NetStim', beyond rate/rates/interval, there are a number of other parameters available Optional('number'): Or(int, float), Optional('start'): Or(int, float), Optional('noise'): And( Or(int, float), lambda s: 0 <= s <= 1 ), # it works if noise is beyond this range, but formally it's wrong # When 'cellModel' == 'VecStim', beyond rate/interval/start/noise, there are a number of other parameters available Optional('spikePattern'): { # Neither 'rate' nor 'interval' should be defined for pattern -> condition (to be completed) 'type': lambda s: s in ['rhythmic', 'evoked', 'poisson', 'gauss'], Optional('sync'): bool, # options related to each specific pattern - conditional will be afterwards (to be completed) # parameters required if 'spikePattern' == 'rhythmic' Optional('start'): Or( lambda s: s == -1, Or(int, float) ), # -1 is a special selection, and of course it is included in "int"; however "(int,float)" is supposed to be positive -> not invalidated because it will still work Optional('repeats'): int, Optional('stop'): Or(int, float), # optional Optional('startMin'): Or( int, float ), # used when 'start' == -1, but not mandatory (it has default values) Optional('startMax'): Or( int, float ), # used when 'start' == -1, but not mandatory (it has default values) Optional('startStd'): And( Or(int, float), lambda s: s >= 0 ), # possibility when 'start' != -1, not mandatory Optional('freq'): Or(int, float), Optional('freqStd'): And(Or(int, float), lambda s: s >= 0), Optional('eventsPerCycle'): int, # any integer, but afterwards selected (1 or 2) Optional('distribution'): lambda s: s in ['normal', 'uniform'], # parameters required if 'spikePattern' == 'evoked' # Optional('start'): Or(int,float), # already set in 'rhythmic' # Optional('startStd'): And( Or(int,float) , lambda s: s >= 0 ), # already set in 'rhythmic' Optional('numspikes'): Or(int, float), # parameters required if 'spikePattern' == 'poisson' # Optional('start'): Or(int,float), # already set in 'rhythmic' # Optional('stop'): Or(int,float), # already set in 'rhythmic' Optional('frequency'): Or(int, float), # parameters required if 'spikePattern' == 'gauss' Optional('mu'): Or(int, float), Optional('sigma'): And(Or(int, float), lambda s: s >= 0), }, Optional('spkTimes'): Or(Or(list, tuple), lambda s: isinstance(s, np.ndarray)), ## IN ADDITION to some of the previous VecStims Optional('pulses'): [ { 'rate': Or(Or(int, float), And([Or(int, float)], lambda s: len(s) == 2)), Optional('interval'): Or(int, float), Optional('noise'): And( Or(int, float), lambda s: 0 <= s <= 1 ), # it works if noise is beyond this range, but formally it's wrong 'start': Or(int, float), 'end': Or(int, float), } ], # Other options are possible, for example those from IntFire1, etcetera. Optional(str): object, } } return specs
[docs] def cell_specs(context): specs = { Optional(str): { Optional('conds'): { str: Or( str, [str], And([Or(int, float)], lambda s: len(s) == 2)) }, Optional('secLists'): { Optional(str): Or( [str] ) # the strings/labels in the list should be "secs" already defined, empty dictionary when loading json struc }, Optional('globals'): {Optional(str): Or(int, float)}, Optional('diversityFraction'): Or(int, float), ## Entries associated to compartCell class Optional('secs'): { ## It is optional because it may NOT be a compartCell, but for compartCells this entry is mandatory str: { Optional('geom'): { Optional('diam'): numberOrStringFunc, Optional('L'): numberOrStringFunc, Optional('Ra'): numberOrStringFunc, Optional('cm'): numberOrStringFunc, Optional('nseg'): numberOrStringFunc, Optional('pt3d'): [ And( lambda s: len(s) == 4, # list of (list or tuples), each with 4 components Or( [Or(int, float), Or(int, float), Or(int, float), Or(int, float)], (Or(int, float), Or(int, float), Or(int, float), Or(int, float)), ), ) ], }, Optional('topol'): Or( {}, { # or empty or populated with specific information 'parentSec': str, # later, conditional to the existence of this sec 'parentX': Or(int, float), 'childX': Or(int, float), }, ), Optional('mechs'): { Optional('hh'): { # one possible built-in mechanism, very used Optional('gnabar'): numberOrStringFunc, Optional('gkbar'): numberOrStringFunc, Optional('gl'): numberOrStringFunc, Optional('el'): numberOrStringFunc, }, Optional('pas'): { # another one Optional('g'): numberOrStringFunc, Optional('e'): numberOrStringFunc, }, Optional(str): { # other possibilities (nonlinear mechanisms: .mod) Optional( str ): object # maybe empty dictionary (default values in .mod), but also different kind of parameters to be given to the mod (numbers, lists, etc) }, Optional(str): { Optional(str): object # maybe empty dictionary (default values in .mod), but also different kind of parameters to be given to the mod (numbers, lists, etc) }, }, Optional('ions'): {str: {'e': Or(int, float), 'o': Or(int, float), 'i': Or(int, float)}}, # not used from programmatic definitions - only for loading (and creating structure) # Optional('synMechs'): [{'label': str, 'loc': Or(int,float)}] Optional('pointps'): { str: { 'mod': And( str, And(lambda s: __isPointpModel(s, context), error='no pointp'), error='Mod is bad'), # 'mod': And( str, And(lambda s: __isPointpModel(s, context), error='Bebe'), error='Meme'), Optional('loc'): Or(int, float), Optional('vref'): str, # voltage calculated in the .mod Optional('synList'): [ str ], # for connections in .mod with the voltage calculated internally (synapses traced back in the mechanism itself): e.g. Izhi2007a Optional(str): Or(int, float, str, bool), # parameters to be given to the mod } }, Optional('spikeGenLoc'): Or(int, float), Optional('vinit'): Or(int, float), Optional('weightNorm'): [Or(int, float)], # number of elements should be equal to nseg Optional('threshold'): Or(int, float), } }, # ## Entries associated to pointCell class Optional('cellType'): str, # valid entry in pointCell class (), but not used for anything # the important thing is that 'cellModel' in the correspoding pop should # be a valid option, and parameters here filled correspondingly Optional('cellModel'): And( str, lambda s: __isArtificialCellModel(s, context) ), Optional('params'):{ # Mandatory when 'cellModel' is a pointCell and the parameters are # filled at the level of cellParams (in contrast to be filled at popParams) # --> conditional validation later Optional('seed'): Or(int,float), Optional('rate'): Or( Or(int,float), # a value And( [Or(int,float)] , lambda s: len(s)==2) ), # inferior and superior bounds - random value in this range # Option for implementing time-dependent rates (for NetStims only) Optional('rates'): Or( Or( int, float ), # this option works, but because of the default values for the "interval" definition - it does not implement a time-dependent rate And([[Or(int, float)]], lambda s: len(s) == 2), ), Optional('interval'): Or(int, float), # When 'cellModel' == 'NetStim', beyond rate/rates/interval, there are a number of other parameters available Optional('number'): Or(int, float), Optional('start'): Or(int, float), Optional('noise'): And( Or(int, float), lambda s: 0 <= s <= 1 ), # it works if noise is beyond this range, but formally it's wrong # When 'cellModel' == 'VecStim', beyond rate/interval/start/noise, there are a number of other parameters available Optional('spikePattern'): { # Neither 'rate' or 'interval' should be defined for pattern to be implemented -> condition (to be completed) 'type': lambda s: s in ['rhythmic', 'evoked', 'poisson', 'gauss'], Optional('sync'): bool, # options related to each specific pattern - conditional will be afterwards (to be completed) # parameters required if 'spikePattern' == 'rhythmic' Optional('start'): Or( lambda s: s == -1, Or(int, float) ), # -1 is a special selection, and of course it is included in "int"; however "(int,float)" is supposed to be positive -> not invalidated because it will still work Optional('repeats'): int, Optional('stop'): Or(int, float), # optional Optional('startMin'): Or( int, float ), # used when 'start' == -1, but not mandatory (it has default values) Optional('startMax'): Or( int, float ), # used when 'start' == -1, but not mandatory (it has default values) Optional('startStd'): And( Or(int, float), lambda s: s >= 0 ), # possibility when 'start' != -1, not mandatory Optional('freq'): Or(int, float), Optional('freqStd'): And(Or(int, float), lambda s: s >= 0), Optional('eventsPerCycle'): int, # any integer, but afterwards selected (1 or 2) Optional('distribution'): lambda s: s in ['normal', 'uniform'], # parameters required if 'spikePattern' == 'evoked' # Optional('start'): Or(int,float), # already set in 'rhythmic' # Optional('startStd'): And( Or(int,float) , lambda s: s >= 0 ), # already set in 'rhythmic' Optional('numspikes'): Or(int, float), # parameters required if 'spikePattern' == 'poisson' # Optional('start'): Or(int,float), # already set in 'rhythmic' # Optional('stop'): Or(int,float), # already set in 'rhythmic' Optional('frequency'): Or(int, float), # parameters required if 'spikePattern' == 'gauss' Optional('mu'): Or(int, float), Optional('sigma'): And(Or(int, float), lambda s: s >= 0), }, Optional('spkTimes'): Or(Or(list, tuple), lambda s: isinstance(s, np.ndarray)), ## IN ADDITION to some of the previous VecStims Optional('pulses'): [ { 'rate': Or(Or(int, float), And([Or(int, float)], lambda s: len(s) == 2)), Optional('interval'): Or(int, float), Optional('noise'): And( Or(int, float), lambda s: 0 <= s <= 1 ), # it works if noise is beyond this range, but formally it's wrong 'start': Or(int, float), 'end': Or(int, float), } ], # Other options are possible, for example those from IntFire1, etcetera. Optional(str): object, }, Optional('vars'): {Optional(str): numberOrStringFunc}, Optional(str): object, } } return specs
[docs] def synmech_specs(context): specs = { Optional(str): { 'mod': And(str, lambda s: __isPointpModel(s, context) ), # built-in models from NEURON are ExpSyn and Exp2Syn Optional('loc'): Or(int,float), Optional('selfNetCon'): { Optional('sec'): str, # should be existing section, default 'soma' Optional('loc'): Or(int, float), Optional('weight'): Or(int, float), Optional('delay'): Or(int, float), Optional('threshold'): Or(int, float), }, # lambda s: return True, # Options for ExpSyn Optional('tau'): numberOrStringFunc, Optional('e'): numberOrStringFunc, # Options for Exp2Syn Optional('tau1'): numberOrStringFunc, Optional('tau2'): numberOrStringFunc, # Optional('e'): Or(int,float), # already set in ExpSyn Optional('pointerParams'): { 'target_var': str, Optional('source_var'): str, Optional('bidirectional'): bool, }, Optional(str): Or(int, float, bool, str), # parameters for other custom-made mods } } return specs
[docs] def conn_specs(context): popConds = And( Or( str, [str]), lambda s: __isKeyIn(s, context.popParams) ) cellTypeConds = And( Or( str, [str]), lambda s: __isKeyIn(s, context.cellParams) or __isAmongConds(s, 'cellType', context.cellParams) ) cellModelConds = And( Or( str, [str]), lambda s: __isArtificialCellModel(s, context) or __isAmongConds(s, 'cellModel', context.cellParams) ) twoElementsList = And( Or([int, float], (int, float), np.ndarray), lambda s: len(s) == 2 ) specs = { Optional(Or(int, str)): { 'preConds': { Optional('pop'): popConds, # it should be an existing population Optional('cellType'): cellTypeConds, # it should be an existing cellType and "cellType" should be in the list sim.net.params.popTagsCopiedToCells Optional('cellModel'): cellModelConds, # it should be a valid cellModel and "cellModel" should be in the list sim.net.params.popTagsCopiedToCells Optional('x'): twoElementsList, Optional('y'): twoElementsList, Optional('z'): twoElementsList, Optional('xnorm'): twoElementsList, Optional('ynorm'): twoElementsList, Optional('znorm'): twoElementsList, # Match an unspecified key (str) to a value or list of values (for example, something similar to 'pop': ['S','M'] -considered above-) # This pop-key should be included in sim.net.params.popTagsCopiedToCells Optional(str): Or(Or(str, int, float), [Or(str, int, float)]), }, 'postConds': { Optional('pop'): popConds, # it should be an existing population Optional('cellType'): cellTypeConds, # it should be an existing cellType and "cellType" should be in the list sim.net.params.popTagsCopiedToCells Optional('cellModel'): cellModelConds, # it should be a valid cellModel and "cellModel" should be in the list sim.net.params.popTagsCopiedToCells Optional('x'): twoElementsList, Optional('y'): twoElementsList, Optional('z'): twoElementsList, Optional('xnorm'): twoElementsList, Optional('ynorm'): twoElementsList, Optional('znorm'): twoElementsList, # Match an unspecified key (str) to a value or list of values (for example, something similar to 'pop': ['S','M'] -considered above-) # This pop-key should be included in sim.net.params.popTagsCopiedToCells Optional(str): Or(Or(str, int, float), [Or(str, int, float)]), }, Optional('connFunc'): lambda s: s in ['fullConn', 'probConn', 'convConn', 'divConn', 'fromListConn'], Optional('probability'): numberOrStringFunc, # it can also be a string-based function Optional('convergence'): numberOrStringFunc, # it can also be a string-based function Optional('divergence'): numberOrStringFunc, # it can also be a string-based function Optional('connList'): Or( [And (Or(tuple, list), lambda s: len(s) == 2 and all(isinstance(n, int) for n in s))], # list of 2-element lists/tuples of two ints (pre, post) lambda s: isinstance(s, np.ndarray) and s.shape[1] == 2 and s.dtype == 'int' # np.array of shape (x, 2) of ints ), Optional('synMech'): And( # existing mechanism in synMechParams - if not defined, it takes the first one in synMechParams Or( [str] , str), lambda s: __isKeyIn(s, context.synMechParams) ), Optional('weight'): Or( str, int, float, [ Or(int, float) ]), # number or string-based function. Listing weights is allowed in 3 situations, only with numbers: 1) when 'synMech' is a list (should have the same lenght), 2) With 'synsPerConn' other than 1, 3) When the connections are specified on a one-by-one basis, with 'connList'. Optional, otherwise default Optional('synMechWeightFactor'): [ Or(int,float) ], # scaling factor ('weight' should not be a list), same lenght as 'synMech' Optional('delay'): Or( str, int, float, [ Or(int, float) ]), # number or string-based function. Listing delays is allowed in 3 situations, only with numbers: 1) when 'synMech' is a list (should have the same lenght), 2) With 'synsPerConn' other than 1, 3) When the connections are specified on a one-by-one basis, with 'connList'. Optional, otherwise default Optional('synMechDelayFactor'): [ Or(int,float) ], # scaling factor ('delay' should not be a list), same lenght as 'synMech' Optional('loc'): Or( str, int, float, [ Or(int, float) ]), # number or string-based function. Listing locs is allowed in 2 situations, only with numbers: 1) when 'synMech' is a list (should have the same lenght), 2) When the connections are specified on a one-by-one basis, with 'connList'. Optional, otherwise default (0.5) Optional('synMechLocFactor'): [ Or(int,float) ], # scaling factor ('loc' should not be a list), same lenght as 'synMech' Optional('synsPerConn'): Or(str, int, float), # number or string-based function. Optional, otherwise default (1) Optional('sec'): Or( [str] , str ), # existing section/s (or secLists) in postCell Optional('disynapticBias'): Or(int, float, None), # apparently, deprecated Optional('shape'): { Optional('pulseType'): lambda s: s in ['square', 'gaussian'], Optional('pulseWidth'): Or(int, float), Optional('pulsePeriod'): Or(int, float), Optional('switchOnOff'): [Or(int, float)], }, Optional('plast'): { 'mech': And(str, lambda s: __isPointpModel(s, context)), 'params': {Optional(str): Or(int, float, str, bool)}, # unspecified parameters }, Optional('weightIndex'): int, Optional('gapJunction'): bool, # deprecated, use 'pointerParams' in 'synMechParams' Optional('preSec'): Or( [str], str ), # existing section/s (or secLists) in pre-synaptic cell. Optional (assuming 'gapJunction' == True), otherwise default ('soma') Optional('preLoc'): Or(int, float, [Or(int, float)]), # string-based function is not allowed here Optional('threshold'): Or(int, float), # deprecated, but some models still have one (for example, tut1) } } return specs
[docs] def subconn_specs(context): popConds = And( Or( str, [str]), lambda s: __isKeyIn(s, context.popParams) ) cellTypeConds = And( Or( str, [str]), lambda s: __isKeyIn(s, context.cellParams) or __isAmongConds(s, 'cellType', context.cellParams) ) cellModelConds = And( Or( str, [str]), lambda s: __isArtificialCellModel(s, context) or __isAmongConds(s, 'cellModel', context.cellParams) ) twoElementsList = And( Or([int, float], (int, float), np.ndarray), lambda s: len(s) == 2 ) specs = { Optional(str): { 'preConds': { Optional('pop'): popConds, # it should be an existing population Optional('cellType'): cellTypeConds, # it should be an existing cellType and "cellType" should be in the list sim.net.params.popTagsCopiedToCells Optional('cellModel'): cellModelConds, # it should be a valid cellModel and "cellModel" should be in the list sim.net.params.popTagsCopiedToCells Optional('x'): twoElementsList, Optional('y'): twoElementsList, Optional('z'): twoElementsList, Optional('xnorm'): twoElementsList, Optional('ynorm'): twoElementsList, Optional('znorm'): twoElementsList, # Match an unspecified key (str) to a value or list of values (for example, something similar to 'pop': ['S','M'] -considered above-) # This pop-key should be included in sim.net.params.popTagsCopiedToCells Optional(str): Or(Or(str, int, float), [Or(str, int, float)]), }, 'postConds': { Optional('pop'): popConds, # it should be an existing population Optional('cellType'): cellTypeConds, # it should be an existing cellType and "cellType" should be in the list sim.net.params.popTagsCopiedToCells Optional('cellModel'): cellModelConds, # it should be a valid cellModel and "cellModel" should be in the list sim.net.params.popTagsCopiedToCells Optional('x'): twoElementsList, Optional('y'): twoElementsList, Optional('z'): twoElementsList, Optional('xnorm'): twoElementsList, Optional('ynorm'): twoElementsList, Optional('znorm'): twoElementsList, # Match an unspecified key (str) to a value or list of values (for example, something similar to 'pop': ['S','M'] -considered above-) # This pop-key should be included in sim.net.params.popTagsCopiedToCells Optional(str): Or(Or(str, int, float), [Or(str, int, float)]), }, Optional('groupSynMechs'): And( # The mechanisms should exist in synMechParams [str], lambda s: __isKeyIn(s, context.synMechParams) ), Optional('sec'): Or([str] , str), # existing section/s (or secLists) in postCell 'density': Or( # either it redistributes uniformely lambda s: s == 'uniform', # or with specific prescriptions, given as a dictionary { 'type': Or( lambda s: s in ['1Dmap', '2Dmap'], lambda s: s == 'distance' ), # we can put all options in the same list, but we write in this way to stress different possibilities (require different entries in this dictionary) # Options conditional to ['1Dmap','2Dmap'] Optional('gridX'): Or( [Or(int, float)], (Or(int, float)), None ), # mandatory when 'type' == '2Dmap', but it doesn't appear then 'type' in ['1Dmap','distance'] -> here, we put as Optional Optional('gridY'): Or( [Or(int, float)], (Or(int, float)), None ), # mandatory when 'type' in ['1Dmap','2Dmap'], but it doesn't appear then 'type' == 'distance' -> here, we put as Optional Optional('fixedSomaY'): Or( int, float ), # optional when 'type' in ['1Dmap','2Dmap'], not needed in 'distance' Optional( 'gridValues' ): Or( # mandatory when 'type' in ['1Dmap','2Dmap'], but it doesn't appear then 'type' == 'distance' -> here, we put as Optional Or( [Or(int, float)], (Or(int, float)) ), # 1D list, conditional: len should be the same as gridY Or( [[Or(int, float)]], [(Or(int, float))], ([Or(int, float)]) ), # 2D list[x][y], conditional: len(s) == len(gridX), len(s[0]) == len(gridY) ), ## NOTE: For 1Dmap/2Dmap, to calculate relative distances, the post-cell SHOULD have a 'soma' section --> conditional validation # Options conditional to 'type' == 'distance' Optional( 'ref_sec' ): str, # not mandatory (see NOTE below). If defined, check that the name coincides to an existing region Optional('ref_seg'): Or(int, float), # not mandatory Optional('target_distance'): Or(int, float), # not mandatory Optional('coord'): lambda s: s in [ 'cartesian' ] # not mandatory (if not declared, distances calculated along the dendrite). Other options may be included ## NOTE: Here, it is not necessary to have a section named 'soma'. It will capture any section with something with 'soma' or it will go to the first section }, ), } } return specs
[docs] def stimsource_specs(context): specs = { Optional(str): { 'type': lambda s: __isPointpModel(s, context) or __isArtificialCellModel(s, context), Optional('originalFormat'): lambda s: s in [ 'NeuroML2', 'NeuroML2_SpikeSource', 'NeuroML2_stochastic_input', ], # Not sure if specified from specs or imported # if 'type' = 'NetStim' Optional('rate'): Or( str, int, float, And(str, lambda s: s == 'variable') ), # a value or particular string (see addNetStim in cell.py). String-based function is allowed Optional('interval'): Or(str, int, float), # number or string-based function Optional('start'): Or(str, int, float), # number or string-based function Optional('number'): Or(str, int, float), # number or string-based function Optional('noise'): Or( str, And(Or(int, float), lambda s: 0 <= s <= 1) ), # it works if noise is beyond this range, but formally it's wrong. String-based function is allowed Optional('seed'): Or(int, float), Optional('shape'): { Optional('pulseType'): lambda s: s in ['square', 'gaussian'], Optional('pulseWidth'): Or(int, float), Optional('pulsePeriod'): Or(int, float), Optional('switchOnOff'): [Or(int, float)], }, Optional('plast'): { 'mech': And(str, lambda s: __isPointpModel(s, context)), 'params': {Optional(str): Or(int, float, str, bool)}, # unspecified parameters }, # if 'type' in ['IClamp', 'VClamp', 'SEClamp', 'AlphaSynapse'], there are a number of other parameters available Optional('del'): Or(str, int, float), # number or string-based function. Optional('dur'): Or( Or(str, int, float), And([Or(int, float)], lambda s: len(s) == 3) ), # number or string-based function. Parameters for the Vclamp/SEClamp (list) only with numbers (otherwise, the string should include the list in the quotation marks, '[x1,x2,x3]') Optional('amp'): Or( Or(str, int, float), And([Or(int, float)], lambda s: len(s) == 3) ), # number or string-based function. Parameters for the Vclamp/SEClamp (list) only with numbers (otherwise, the string should include the list in the quotation marks, '[x1,x2,x3]') Optional('gain'): Or(str, int, float), # number or string-based function. Optional('rstim'): Or(str, int, float), # number or string-based function. Optional('tau1'): Or(str, int, float), # number or string-based function. Optional('tau2'): Or(str, int, float), # number or string-based function. Optional('onset'): Or(str, int, float), # number or string-based function. Optional('tau'): Or(str, int, float), # number or string-based function. Optional('gmax'): Or(str, int, float), # number or string-based function. Optional('e'): Or(str, int, float), # number or string-based function. Optional('dur1'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('dur2'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('dur3'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('amp1'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('amp2'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('amp3'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional('rs'): Or(int, float), # number (not included in sim.net.stimStringFuncParams) Optional(str): object, # unspecified parameters for 'originalFormat' } } return specs
[docs] def stimtarget_specs(context): specs = { Optional(str): { 'source': str, # Conditional: one label from stimSourceParams 'conds': { # Similar to conds in connections (except that here, a list of ids is also possible) Optional('pop'): And( Or( str, [str]), # it should be an existing population lambda s: __isKeyIn(s, context.popParams) ), Optional('cellType'): And( Or( str, [str]), # it should be an existing cellType and "cellType" should be in the list sim.net.params.popTagsCopiedToCells lambda s: __isKeyIn(s, context.cellParams) or __isAmongConds(s, 'cellType', context.cellParams) ), Optional('cellModel'): And( Or( str, [str]), # it should be a valid cellModel and "cellModel" should be in the list sim.net.params.popTagsCopiedToCells lambda s: __isArtificialCellModel(s, context) ), Optional('x'): And([Or(int, float)], lambda s: len(s) == 2), Optional('y'): And([Or(int, float)], lambda s: len(s) == 2), Optional('z'): And([Or(int, float)], lambda s: len(s) == 2), Optional('xnorm'): And([Or(int, float)], lambda s: len(s) == 2), Optional('ynorm'): And([Or(int, float)], lambda s: len(s) == 2), Optional('znorm'): And([Or(int, float)], lambda s: len(s) == 2), Optional('cellList'): [int], # Match an unspecified key (str) to a value or list of values (for example, something similar to 'pop': ['S','M'] -considered above-) # This pop-key should be included in sim.net.params.popTagsCopiedToCells Optional(str): Or(Or(str, int, float), [Or(str, int, float)]), }, Optional('sec'): Or( str, [str] ), # Conditional: existing section, but also it could be a string-based function (weird, but available -at least formally, see that "secList" exists after conversion of str to func-). In the case of a list, it is conditional to the incoming source to be a NetStim Optional('loc'): Or( str, int, float, [Or(int, float)] ), # number or string-based function. Listing weights is allowed in 2 situations, only with numbers: 1) when the incoming source is a NetStim and 'synMech' is a list (should have the same lenght), 2) With 'synsPerConn' other than 1. Optional, otherwise default # Conditional, next entries only for NetStims Optional('weight'): Or( str, int, float, [ Or(int, float) ]), # number or string-based function. Listing weights is allowed in 2 situations, only with numbers: 1) when 'synMech' is a list (should have the same lenght), 2) With 'synsPerConn' other than 1. Optional, otherwise default Optional('delay'): Or( str, int, float, [ Or(int, float) ]), # number or string-based function. Listing weights is allowed in 2 situations, only with numbers: 1) when 'synMech' is a list (should have the same lenght), 2) With 'synsPerConn' other than 1. Optional, otherwise default Optional('synsPerConn'): Or(str, int, float), # number or string-based function. Optional, otherwise default Optional('synMech'): And( # existing mechanism in synMechParams - if not defined, it takes the first one in synMechParams Or( [str] , str), lambda s: __isKeyIn(s, context.synMechParams) ), Optional('synMechWeightFactor'): [ Or(int,float) ], # scaling factor ('weight' should not be a list), same lenght as 'synMech' Optional('synMechDelayFactor'): [ Or(int,float) ], # scaling factor ('delay' should not be a list), same lenght as 'synMech' Optional('synMechLocFactor'): [ Or(int,float) ] # scaling factor ('loc' should not be a list), same lenght as 'synMech' } } return specs
[docs] def rxd_specs(): specs = { 'regions': { str: Or( # dictionary for an extracellular region { 'extracellular': And(bool, lambda s: s == True), 'xlo': Or(int, float), 'ylo': Or(int, float), 'zlo': Or(int, float), 'xhi': Or(int, float), 'yhi': Or(int, float), 'zhi': Or(int, float), 'dx': Or(int, float, tuple, None), Optional('volume_fraction'): Or(int, float), Optional('tortuosity'): Or(int, float), }, # dictionary for a regular region { Optional('extracellular'): And(bool, lambda s: s == False), Optional('cells'): Or('all',[ Or( 'all', int, str, And( Or(tuple, list), lambda s: isinstance(s[0], str), lambda s: Or(isinstance(s[1], list), isinstance(s[1], int)), ), ) ]), Optional('secs'): Or(str, list), Optional('nrn_region'): Or(lambda s: s in ['i', 'o'], None), Optional('geometry'): Or( And(str, lambda s: s in ['inside', 'membrane']), And( lambda s: s['class'] in [ 'DistributedBoundary', 'FractionalVolume', 'FixedCrossSection', 'FixedPerimeter', 'ScalableBorder', 'Shell', ], lambda s: isinstance(s['args'], dict), ), None, ), Optional('dimension'): Or(lambda s: s in [1, 3], None), Optional('dx'): Or(int, float, None), }, ) }, Optional('extracellular'): { 'xlo': Or(int, float), 'ylo': Or(int, float), 'zlo': Or(int, float), 'xhi': Or(int, float), 'yhi': Or(int, float), 'zhi': Or(int, float), 'dx': Or(int, float, tuple, None), Optional('volume_fraction'): Or(int, float), Optional('tortuosity'): Or(int, float), }, 'species': { str: { 'regions': Or(str, [str]), # one or more regions defined in the previous entry Optional('d'): Or(int, float), Optional('charge'): int, Optional('initial'): Or(int, float, str, None), # string-based function, based on "node" attributes Optional('ecs_boundary_conditions'): Or(None, int, float), Optional('atolscale'): Or(int, float), Optional('name'): str, } }, Optional('states'): { str: { 'regions': Or(str, [str]), Optional('initial'): Or(int, float, str, None), # string-based function, based on "node" attributes Optional('name'): str, } }, Optional('reactions'): { str: { 'reactant': str, # validity of the expression will not be checked 'product': str, # validity of the expression will not be checked 'rate_f': numberOrStringFunc, Optional('rate_b'): Or(int, float, str, None), Optional('regions'): Or(str, [str], [None]), Optional('custom_dynamics'): Or(bool, None) # Optional('membrane'): Or(str,None), # Either none or one of the regions, with appropriate geometry. This is an argument not required in Reaction class (single-compartment reactions) # Optional('membrane_flux'): bool # This is an argument not required in Reaction class (single-compartment reactions) } }, Optional('parameters'): { str: { 'regions': Or(str, [str]), Optional('name'): Or(str, None), Optional('charge'): int, Optional('value'): Or(int, float, str, None), } }, Optional('multicompartmentReactions'): { str: { 'reactant': str, # validity of the expression will not be checked 'product': str, # validity of the expression will not be checked 'rate_f': numberOrStringFunc, Optional('rate_b'): Or(int, float, str, None), Optional('regions'): Or(str, [str], [None]), Optional('custom_dynamics'): Or(bool, None), Optional('membrane'): Or(str, None), Optional('membrane_flux'): bool, Optional('scale_by_area'): bool, } }, Optional('rates'): { str: { 'species': Or(str, [str]), # string-based specification (see rxd_net example) 'rate': numberOrStringFunc, Optional('regions'): Or(str, [str], [None]), Optional('membrane_flux'): bool, } }, Optional('constants'): {str: Or(int, float, [int, float], np.ndarray)}, } return specs
[docs] def validateNetParams(net_params, printWarnings=True): validatedSchemas = {} failedSchemas = [] global __mechVarList __mechVarList = None def validate(data, specs, component): schema = Schema(specs) try: valid = schema.validate(data) print(f" Successfully validated {component}") validatedSchemas[component] = valid except SchemaError as origError: error = ValidationError(component, origError) failedSchemas.append(error) if printWarnings: print(f"\n Error validating {component}:") print(error.formattedMessage(baseIndent=' ') + "\n") context = ValidationContext(net_params) ## GENERAL SPECIFICATIONS # Get only general specifications and set up as a dictionary specs_classes = [ 'cellParams', 'popParams', 'synMechParams', 'connParams', 'subConnParams', 'stimSourceParams', 'stimTargetParams', 'rxdParams', ] net_params_general = { elem: net_params.__dict__[elem] for elem in net_params.__dict__.keys() if elem not in specs_classes } validate( net_params_general, general_specs(), 'generalParams' ) validate( net_params.cellParams, cell_specs(context), 'cellParams' ) validate( net_params.popParams, pop_specs(context), 'popParams' ) validate( net_params.synMechParams, synmech_specs(context), 'synMechParams' ) validate( net_params.connParams, conn_specs(context), 'connParams' ) validate( net_params.subConnParams, subconn_specs(context), 'subConnParams' ) validate( net_params.stimSourceParams, stimsource_specs(context), 'stimSourceParams' ) validate( net_params.stimTargetParams, stimtarget_specs(context), 'stimTargetParams' ) if net_params.rxdParams: validate( net_params.rxdParams, rxd_specs(), 'rxdParams' ) return validatedSchemas, failedSchemas
# utils def __isKeyIn(key, otherStruct): if type(key) in (list, tuple): return all(k in otherStruct for k in key) return key in otherStruct def __isAmongConds(val, cond, cellParams): # cond is either `cellType` or `cellModel` def isAmongConds(val, cond, cellParams): for _, cellRule in cellParams.items(): condVals = cellRule.get('conds', {}).get(cond, None) if type(condVals) not in (list, tuple): condVals = [condVals] for condVal in condVals: if val == condVal: return True return False if type(val) in (list, tuple): return all(isAmongConds(v, cond, cellParams) for v in val) return isAmongConds(val, cond, cellParams) def __isPointpModel(name, context): return __isModel(name, 'pointps', context) def __isArtificialCellModel(name, context): return __isModel(name, 'artifcells', context) def __isMechModel(name, context): return __isModel(name, 'mechs', context) def __isModel(name, modelType, context): if not context.validateModels: return True global __mechVarList if __mechVarList is None: from netpyne.conversion import mechVarList __mechVarList = mechVarList(distinguishArtifCells=True) def isModel(name, modelType): if name not in __mechVarList[modelType]: return False return True if type(name) in (list, tuple): return all(isModel(n, modelType) for n in name) return isModel(name, modelType)
[docs] class ValidationError(object): def __init__(self, component, error): self.component = component self.originalError = error self.keyPath, self.summary = self.__parseErrorMessage()
[docs] def formattedMessage(self, baseIndent=''): message = "" if len(self.keyPath) > 0: keySeq = ' -> '.join(self.keyPath) message += f"{baseIndent}Error in {keySeq}:" newLine = '\n' + baseIndent + ' ' if len(self.summary): message += newLine + newLine.join(self.summary) else: message += newLine + '<no additional info>' return message
def __parseErrorMessage(self, indent=''): import re pattern = re.compile("key ('.*') error:", re.IGNORECASE) # TODO: need to freeze `schema` version to make sure this pattern is preserved keySeq = [] other = [] err = self.originalError # convert dict keys sequence to single string for line in err.autos: if line is None: line = '' matches = pattern.match(line) if matches: matches = matches.groups() if len(matches) > 0: keySeq.append(matches[0]) # presume only one match elif line != '': other.append(line) errors = [e for e in err.errors if e is not None] if errors: # custom errors (provided by netpyne validator) summary = [errors[-1]] # take the last error (and put into list for consistency) else: # auto-generated by `schema` summary = other return keySeq, summary
# This is a utility method that loops over models in ./examples folder and prints any validation errors # Consider running it as github "checks"
[docs] def checkValidation(): import os, glob from netpyne import sim from netpyne.sim import validator def checkModelValid(index): print(f'PROCESSSING {index}') _, netParams = sim.loadModel(index, loadMechs=True) valid, failed = validator.validateNetParams(net_params=netParams) if failed: print(f'FOUND {len(failed)} ERRORS IN {index}') for compName in [f.component for f in failed]: print(compName) else: print(f'VALIDATION SUCCEEDED FOR {index}') return valid, failed os.chdir('examples') try: valid, failed = [], [] # TODO: for some reason, the models below fail to load when run in order of for-loop below. # Works okay when run individually though... exceptionFromLoop = ['batchCellMapping/index.npjson', 'batchCell/index.npjson'] for index in [index for index in glob.glob('*/*.npjson') if index not in exceptionFromLoop ]: v, f = checkModelValid(index) valid.extend(v) failed.extend(f) except Exception as e: print(f"FAILED VALIDATING EXAMPLES: {e}") print(f'================\nValidation summary: {len(failed)} failed\n') os.chdir('..') return failed