Beispiel #1
0
 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)
Beispiel #2
0
 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)
Beispiel #3
0
 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)
Beispiel #4
0
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)
Beispiel #5
0
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})
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
    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)
Beispiel #9
0
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)
Beispiel #10
0
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)
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
 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)