def test_ASTIM_hybrid(self, is_profiled=False): logger.info('Test: running ASTIM hybrid simulation') pp = PulsedProtocol(0.6e-3, 0.1e-3) pneuron = getPointNeuron('RS') nbls = NeuronalBilayerSonophore(self.a, pneuron) self.execute(lambda: nbls.simulate(self.USdrive, pp, method='hybrid'), is_profiled)
def test_MECH(self, is_profiled=False): logger.info('Test: running MECH simulation') Qm0 = -80e-5 # membrane resting charge density (C/m2) Cm0 = 1e-2 # membrane resting capacitance (F/m2) Qm = 50e-5 # C/m2 bls = BilayerSonophore(self.a, Cm0, Qm0) self.execute(lambda: bls.simulate(self.USdrive, Qm), is_profiled)
def test_ASTIM_full(self, is_profiled=False): logger.info('Test: running ASTIM detailed simulation') pp = PulsedProtocol(1e-6, 1e-6) pneuron = getPointNeuron('RS') nbls = NeuronalBilayerSonophore(self.a, pneuron) self.execute(lambda: nbls.simulate(self.USdrive, pp, method='full'), is_profiled)
def main(): # Parse command line arguments parser = AStimParser() args = parser.parse() logger.setLevel(args['loglevel']) sim_inputs = parser.parseSimInputs(args) simQueue_func = {9: 'simQueue', 10: 'simQueueBurst'}[len(sim_inputs)] # Run A-STIM batch logger.info("Starting A-STIM simulation batch") queue = getattr(NeuronalBilayerSonophore, simQueue_func)(*sim_inputs, outputdir=args['outputdir'], overwrite=args['overwrite']) output = [] for a in args['radius']: for pneuron in args['neuron']: nbls = NeuronalBilayerSonophore(a, pneuron) batch = Batch(nbls.simAndSave if args['save'] else nbls.simulate, queue) output += batch(mpi=args['mpi'], loglevel=args['loglevel']) # Plot resulting profiles if args['plot'] is not None: parser.parsePlot(args, output)
def computeCmLookup(bls, fref, Aref, mpi=False, loglevel=logging.INFO): descs = {'f': 'US frequencies', 'A': 'US amplitudes'} # Populate reference vectors dictionary refs = { 'f': fref, # Hz 'A': Aref # Pa } # Check validity of all reference vectors for key, values in refs.items(): if not isIterable(values): raise TypeError( f'Invalid {descs[key]} (must be provided as list or numpy array)' ) if not all(isinstance(x, float) for x in values): raise TypeError(f'Invalid {descs[key]} (must all be float typed)') if len(values) == 0: raise ValueError(f'Empty {key} array') if key == 'f' and min(values) <= 0: raise ValueError( f'Invalid {descs[key]} (must all be strictly positive)') if key == 'A' and min(values) < 0: raise ValueError( f'Invalid {descs[key]} (must all be positive or null)') # Get references dimensions dims = np.array([x.size for x in refs.values()]) # Create simulation queue drives = AcousticDrive.createQueue(fref, Aref) queue = [[drive, 0.] for drive in drives] # Run simulations and populate outputs logger.info(f'Starting Cm simulation batch for {bls}') batch = Batch(bls.getRelCmCycle, queue) rel_Cm_cycles = batch(mpi=mpi, loglevel=loglevel) # Make sure outputs size matches inputs dimensions product nout, nsamples = len(rel_Cm_cycles), rel_Cm_cycles[0].size assert nout == dims.prod( ), 'Number of outputs does not match number of combinations' dims = np.hstack([dims, nsamples]) refs['t'] = np.linspace(0., 1., nsamples) # Reshape effvars into nD arrays and add them to lookups dictionary logger.info('Reshaping output into lookup table') rel_Cm_cycles = np.array(rel_Cm_cycles).reshape(dims) # Construct and return lookup object return Lookup(refs, {'Cm_rel': rel_Cm_cycles})
def main(): parser = MechSimParser(outputdir='.') parser.addTest() parser.defaults['radius'] = 32.0 # nm parser.defaults['freq'] = np.array([20., 100., 500., 1e3, 2e3, 3e3, 4e3]) # kHz parser.defaults['amp'] = np.insert( np.logspace(np.log10(0.1), np.log10(600), num=50), 0, 0.0) # kPa args = parser.parse() logger.setLevel(args['loglevel']) # Model a = args['radius'][0] # m Cm0 = 1e-2 # F/m2 Qm0 = 0.0 # C/m2 bls = BilayerSonophore(a, Cm0, Qm0) lookup_fpath = bls.Cm_lkp_filepath # Batch inputs inputs = [args[x] for x in ['freq', 'amp']] # Adapt inputs and output filename if test case if args['test']: for i, x in enumerate(inputs): if x is not None and x.size > 1: inputs[i] = np.array([x.min(), x.max()]) fcode, fext = os.path.splitext(lookup_fpath) lookup_fpath = f'{fcode}_test{fext}' # Check if lookup file already exists if os.path.isfile(lookup_fpath): logger.warning( f'"{lookup_fpath}" file already exists and will be overwritten. Continue? (y/n)' ) user_str = input() if user_str not in ['y', 'Y']: logger.error('Cm-lookup creation canceled') return # Compute lookup lkp = computeCmLookup(bls, *inputs, mpi=args['mpi'], loglevel=args['loglevel']) logger.info(f'Generated Cm-lookup: {lkp}') # Save lookup in PKL file logger.info(f'Saving {bls} Cm-lookup in file: "{lookup_fpath}"') lkp.toPickle(lookup_fpath)
def main(): # Parse command line arguments parser = VClampParser() args = parser.parse() logger.setLevel(args['loglevel']) # Run E-STIM batch logger.info("Starting V-clamp simulation batch") queue = VoltageClamp.simQueue(*parser.parseSimInputs(args), outputdir=args['outputdir'], overwrite=args['overwrite']) output = [] for pneuron in args['neuron']: vlcamp = VoltageClamp(pneuron) batch = Batch(vlcamp.simAndSave if args['save'] else vlcamp.simulate, queue) output += batch(mpi=args['mpi'], loglevel=args['loglevel']) # Plot resulting profiles if args['plot'] is not None: parser.parsePlot(args, output)
def test_ASTIM_sonic(self, is_profiled=False): logger.info('Test: ASTIM sonic simulation') pp = PulsedProtocol(50e-3, 10e-3) pneuron = getPointNeuron('RS') nbls = NeuronalBilayerSonophore(self.a, pneuron) # test error 1: sonophore radius outside of lookup range try: nbls = NeuronalBilayerSonophore(100e-9, pneuron) nbls.simulate(self.USdrive, pp, method='sonic') except ValueError: logger.debug('Out of range radius: OK') # test error 2: frequency outside of lookups range try: nbls = NeuronalBilayerSonophore(self.a, pneuron) nbls.simulate(AcousticDrive(10e3, self.USdrive.A), pp, method='sonic') except ValueError: logger.debug('Out of range frequency: OK') # test error 3: amplitude outside of lookups range try: nbls = NeuronalBilayerSonophore(self.a, pneuron) nbls.simulate(AcousticDrive(self.USdrive.f, 1e6), pp, method='sonic') except ValueError: logger.debug('Out of range amplitude: OK') # Run simulation on all neurons for name, neuron_class in getNeuronsDict().items(): if name not in ('template', 'LeechP', 'LeechT', 'LeechR', 'SWnode'): pneuron = neuron_class() nbls = NeuronalBilayerSonophore(self.a, pneuron) self.execute( lambda: nbls.simulate(self.USdrive, pp, method='sonic'), is_profiled)
def main(): # Parse command line arguments parser = EStimParser() args = parser.parse() logger.setLevel(args['loglevel']) sim_inputs = parser.parseSimInputs(args) simQueue_func = {5: 'simQueue', 6: 'simQueueBurst'}[len(sim_inputs)] # Run E-STIM batch logger.info("Starting E-STIM simulation batch") queue = getattr(PointNeuron, simQueue_func)(*sim_inputs, outputdir=args['outputdir'], overwrite=args['overwrite']) output = [] for pneuron in args['neuron']: batch = Batch(pneuron.simAndSave if args['save'] else pneuron.simulate, queue) output += batch(mpi=args['mpi'], loglevel=args['loglevel']) # Plot resulting profiles if args['plot'] is not None: parser.parsePlot(args, output)
def main(): # Parse command line arguments parser = MechSimParser() args = parser.parse() logger.setLevel(args['loglevel']) # Run MECH batch logger.info("Starting mechanical simulation batch") queue = BilayerSonophore.simQueue( *parser.parseSimInputs(args), outputdir=args['outputdir'], overwrite=args['overwrite']) output = [] for a in args['radius']: for d in args['embedding']: for Cm0 in args['Cm0']: for Qm0 in args['Qm0']: bls = BilayerSonophore(a, Cm0, Qm0, embedding_depth=d) batch = Batch(bls.simAndSave if args['save'] else bls.simulate, queue) output += batch(mpi=args['mpi'], loglevel=args['loglevel']) # Plot resulting profiles if args['plot'] is not None: parser.parsePlot(args, output)
def computeAStimLookup(pneuron, aref, fref, Aref, fsref, Qref, novertones=0, test=False, mpi=False, loglevel=logging.INFO): ''' Run simulations of the mechanical system for a multiple combinations of imposed sonophore radius, US frequencies, acoustic amplitudes charge densities and (spatially-averaged) sonophore membrane coverage fractions, compute effective coefficients and store them in a dictionary of n-dimensional arrays. :param pneuron: point-neuron model :param aref: array of sonophore radii (m) :param fref: array of acoustic drive frequencies (Hz) :param Aref: array of acoustic drive amplitudes (Pa) :param Qref: array of membrane charge densities (C/m2) :param fsref: acoustic drive phase (rad) :param mpi: boolean statting wether or not to use multiprocessing :param loglevel: logging level :return: lookups dictionary ''' descs = { 'a': 'sonophore radii', 'f': 'US frequencies', 'A': 'US amplitudes', 'fs': 'sonophore membrane coverage fractions', 'overtones': 'charge Fourier overtones' } # Populate reference vectors dictionary refs = { 'a': aref, # nm 'f': fref, # Hz 'A': Aref, # Pa 'Q': Qref # C/m2 } err_span = 'cannot span {} for more than 1 {}' # If multiple sonophore coverage values, ensure that only 1 value of # sonophore radius and US frequency are provided if fsref.size > 1 or fsref[0] != 1.: for x in ['a', 'f']: assert refs[x].size == 1, err_span.format(descs['fs'], descs[x]) # Add sonophore coverage vector to references refs['fs'] = fsref # If charge overtones are required, ensure that only 1 value of # sonophore radius, US frequency and coverage fraction are provided if novertones > 0: for x in ['a', 'f', 'fs']: assert refs[x].size == 1, err_span.format(descs['overtones'], descs[x]) # If charge overtones are required, downsample charge and US amplitude input vectors if novertones > 0: nQmax = 50 if len(refs['Q']) > nQmax: refs['Q'] = np.linspace(refs['Q'][0], refs['Q'][-1], nQmax) nAmax = 15 if len(refs['A']) > nAmax: refs['A'] = np.insert( np.logspace(np.log10(refs['A'][1]), np.log10(refs['A'][-1]), num=nAmax - 1), 0, 0.0) # If test case, reduce all vector dimensions to their instrinsic bounds if test: refs = { k: np.array([v.min(), v.max()]) if v.size > 1 else v for k, v in refs.items() } # Check validity of all reference vectors for key, values in refs.items(): if not isIterable(values): raise TypeError( f'Invalid {descs[key]} (must be provided as list or numpy array)' ) if not all(isinstance(x, float) for x in values): raise TypeError(f'Invalid {descs[key]} (must all be float typed)') if len(values) == 0: raise ValueError(f'Empty {key} array') if key in ('a', 'f') and min(values) <= 0: raise ValueError( f'Invalid {descs[key]} (must all be strictly positive)') if key in ('A', 'fs') and min(values) < 0: raise ValueError( f'Invalid {descs[key]} (must all be positive or null)') # Create simulation queue per sonophore radius drives = AcousticDrive.createQueue(refs['f'], refs['A']) queue = [] for drive in drives: for Qm in refs['Q']: queue.append([drive, refs['fs'], Qm]) # Add charge overtones to queue if required if novertones > 0: # Default references nAQ, nphiQ = 5, 5 AQ_ref = np.linspace(0, 100e-5, nAQ) # C/m2 phiQ_ref = np.linspace(0, 2 * np.pi, nphiQ, endpoint=False) # rad # Downsample if test mode is on if test: AQ_ref = np.array([AQ_ref.min(), AQ_ref.max()]) phiQ_ref = np.array([phiQ_ref.min(), phiQ_ref.max()]) # Construct refs dict specific to Qm overtones Qovertones_refs = {} for i in range(novertones): Qovertones_refs[f'AQ{i + 1}'] = AQ_ref Qovertones_refs[f'phiQ{i + 1}'] = phiQ_ref # Create associated batch queue Qovertones = Batch.createQueue(*Qovertones_refs.values()) Qovertones = [list(zip(x, x[1:]))[::2] for x in Qovertones] # Merge with main queue (moving Qm overtones into kwargs) queue = list(itertools.product(queue, Qovertones)) queue = [(x[0], {'Qm_overtones': x[1]}) for x in queue] # Update main refs dict, and reset 'fs' as last dictionary key refs.update(Qovertones_refs) refs['fs'] = refs.pop('fs') # Get references dimensions dims = np.array([x.size for x in refs.values()]) # Print queue (or reduced view of it) logger.info('batch queue:') Batch.printQueue(queue) # Run simulations and populate outputs logger.info('Starting simulation batch for %s neuron', pneuron.name) outputs = [] for a in refs['a']: nbls = NeuronalBilayerSonophore(a, pneuron) batch = Batch(nbls.computeEffVars, queue) outputs += batch(mpi=mpi, loglevel=loglevel) # Split comp times and effvars from outputs effvars, tcomps = [list(x) for x in zip(*outputs)] effvars = list(itertools.chain.from_iterable(effvars)) # Make sure outputs size matches inputs dimensions product nout = len(effvars) ncombs = dims.prod() if nout != ncombs: raise ValueError( f'Number of outputs ({nout}) does not match number of input combinations ({ncombs})' ) # Reshape effvars into nD arrays and add them to lookups dictionary logger.info( f'Reshaping {nout}-entries output into {tuple(dims)} lookup tables') varkeys = list(effvars[0].keys()) tables = {} for key in varkeys: effvar = [effvars[i][key] for i in range(nout)] tables[key] = np.array(effvar).reshape(dims) # Reshape computation times, tile over extra fs dimension, and add it as a lookup table tcomps = np.array(tcomps).reshape(dims[:-1]) tcomps = np.moveaxis(np.array([tcomps for i in range(dims[-1])]), 0, -1) tables['tcomp'] = tcomps # Construct and return lookup object return Lookup(refs, tables)
def main(): parser = MechSimParser(outputdir='.') parser.addNeuron() parser.addTest() parser.defaults['neuron'] = 'RS' parser.defaults['radius'] = np.array([16.0, 32.0, 64.0]) # nm parser.defaults['freq'] = np.array([20., 100., 500., 1e3, 2e3, 3e3, 4e3]) # kHz parser.defaults['amp'] = np.insert( np.logspace(np.log10(0.1), np.log10(600), num=50), 0, 0.0) # kPa parser.defaults['charge'] = np.nan parser.add_argument('--novertones', type=int, default=0, help='Number of Fourier overtones') args = parser.parse() logger.setLevel(args['loglevel']) for pneuron in args['neuron']: # Determine charge vector charges = args['charge'] if charges.size == 1 and np.isnan(charges[0]): Qmin, Qmax = pneuron.Qbounds charges = np.arange(Qmin, Qmax + DQ_LOOKUP, DQ_LOOKUP) # C/m2 # Number of Fourier overtones novertones = args['novertones'] # Determine output filename input_args = { 'a': args['radius'], 'f': args['freq'], 'A': args['amp'], 'fs': args['fs'] } fname_args = { k: v[0] if v.size == 1 else None for k, v in input_args.items() } fname_args['novertones'] = novertones lookup_fpath = NeuronalBilayerSonophore( 32e-9, pneuron).getLookupFilePath(**fname_args) # Combine inputs into single list inputs = [args[x] for x in ['radius', 'freq', 'amp', 'fs']] + [charges] # Adapt inputs and output filename if test case if args['test']: fcode, fext = os.path.splitext(lookup_fpath) lookup_fpath = f'{fcode}_test{fext}' # Check if lookup file already exists if os.path.isfile(lookup_fpath): logger.warning( f'"{lookup_fpath}" file already exists and will be overwritten. Continue? (y/n)' ) user_str = input() if user_str not in ['y', 'Y']: logger.error('%s Lookup creation canceled', pneuron.name) return # Compute lookup lkp = computeAStimLookup(pneuron, *inputs, novertones=novertones, test=args['test'], mpi=args['mpi'], loglevel=args['loglevel']) logger.info(f'Generated lookup: {lkp}') # Save lookup in PKL file logger.info('Saving %s neuron lookup in file: "%s"', pneuron.name, lookup_fpath) lkp.toPickle(lookup_fpath)
def test_ESTIM(self, is_profiled=False): logger.info('Test: running ESTIM simulation') ELdrive = ElectricDrive(10.0) # mA/m2 pp = PulsedProtocol(100e-3, 50e-3) pneuron = getPointNeuron('RS') self.execute(lambda: pneuron.simulate(ELdrive, pp), is_profiled)