Пример #1
0
def test_mixed_vector_length():
    #================================================================
    "Check the definition of scalar nonlinear parameters and vector linear parameters"
    model = Model(gauss2_scaled)
    model.addlinear('gaussian', vec=100)

    assert model.Nparam == 101
Пример #2
0
def test_mixed_vector_names():
    #================================================================
    "Check the definition of scalar nonlinear parameters and vector linear parameters"
    model = Model(gauss2_scaled)
    model.addlinear('gaussian', vec=100)

    assert 'gaussian' in model.__dict__ and 'scale' in model.__dict__
Пример #3
0
def test_freeze_vec_outofbounds():
    #================================================================
    "Check that a parameter cannot be frozen outside of the bounds"
    model = Model(gauss2_identity)
    model.addlinear('gaussian', vec=100, lb=0)
    with pytest.raises(ValueError):
        model.gaussian.freeze(np.full(100, -10))
Пример #4
0
def test_addlinear_set():
    #================================================================
    "Check that attributes of the linear parameters are editable"
    model = Model(gauss2_design)
    model.addlinear('amp1')
    model.amp1.set(lb=0, ub=10)

    assert getattr(model.amp1, 'lb') == 0 and getattr(model.amp1, 'ub') == 10
Пример #5
0
def test_addlinear_names():
    #================================================================
    "Check that linear parameters can be properly added"
    model = Model(gauss2_design)
    model.addlinear('amp1', lb=0)
    model.addlinear('amp2', lb=0)

    assert 'amp1' in model.__dict__ and 'amp2' in model.__dict__
Пример #6
0
def test_addlinear_length():
    #================================================================
    "Check that the model is contructed correctly with the appropiate number of parameters"
    model = Model(gauss2_design)
    model.addlinear('amp1', lb=0)
    model.addlinear('amp2', lb=0)

    assert model.Nparam == 6
Пример #7
0
def test_addlinear_vector_set():
    #================================================================
    "Check that attributes of the vector linear parameters are editable"
    model = Model(gauss2_identity)
    model.addlinear('gaussian', vec=100)
    model.gaussian.set(lb=np.zeros(100))

    assert np.allclose(getattr(model.gaussian, 'lb'), np.zeros(100))
Пример #8
0
def test_mixed_vector_call_positional():
    #================================================================
    "Check that calling the model with scalar and vector parameters returns the correct response"
    model = Model(gauss2_scaled)
    model.addlinear('gaussian', vec=len(x))
    reference = 5 * gauss2(3, 4, 0.2, 0.3, 0.5, 0.4)
    response = model(5, gauss2(3, 4, 0.2, 0.3, 0.5, 0.4))

    assert np.allclose(response, reference)
Пример #9
0
def test_addlinear_call_mixed():
    #================================================================
    "Check that calling the model with parameters returns the correct response"
    model = Model(gauss2_design)
    model.addlinear('amp1')
    model.addlinear('amp2')
    response = model(3, 4, 0.2, width2=0.3, amp1=0.5, amp2=0.4)
    reference = gauss2(3, 4, 0.2, width2=0.3, amp1=0.5, amp2=0.4)

    assert np.allclose(response, reference)
Пример #10
0
def test_addlinear_vector_call_keywords():
    #================================================================
    "Check that calling the model with scalar and vector parameters returns the correct response"
    model = Model(gauss2_identity)
    model.addlinear('gaussian', vec=len(x))
    reference = gauss2(mean1=3,
                       mean2=4,
                       width1=0.2,
                       width2=0.3,
                       amp1=0.5,
                       amp2=0.4)
    response = model(gaussian=reference)

    assert np.allclose(response, reference)
Пример #11
0
def _getmodel_axis(type, vec=50):
    if type == 'parametric':
        model = Model(gauss2_axis, constants='axis')
        model.mean1.set(lb=0, ub=10, par0=2)
        model.mean2.set(lb=0, ub=10, par0=4)
        model.width1.set(lb=0.01, ub=5, par0=0.2)
        model.width2.set(lb=0.01, ub=5, par0=0.2)
        model.amp1.set(lb=0, ub=5, par0=1)
        model.amp2.set(lb=0, ub=5, par0=1)
    elif type == 'semiparametric':
        model = Model(gauss2_design_axis, constants='axis')
        model.mean1.set(lb=0, ub=10, par0=2)
        model.mean2.set(lb=0, ub=10, par0=4)
        model.width1.set(lb=0.01, ub=5, par0=0.2)
        model.width2.set(lb=0.01, ub=5, par0=0.2)
        model.addlinear('amp1', lb=0, ub=5)
        model.addlinear('amp2', lb=0, ub=5)
    elif type == 'semiparametric_vec':
        model = Model(gauss2_design_axis, constants='axis')
        model.mean1.set(lb=0, ub=10, par0=2)
        model.mean2.set(lb=0, ub=10, par0=4)
        model.width1.set(lb=0.01, ub=5, par0=0.2)
        model.width2.set(lb=0.01, ub=5, par0=0.2)
        model.addlinear('amps', lb=0, ub=5, vec=2)
    elif type == 'nonparametric':
        model = Model(lambda x: gauss2_design_axis(x, 3, 4, 0.5, 0.2),
                      constants='x')
        model.addlinear('amp1', lb=0)
        model.addlinear('amp2', lb=0)
    elif type == 'nonparametric_vec':
        model = Model(lambda x: np.eye(len(x)), constants='x')
        model.addlinear('dist', lb=0, vec=vec)
    return model
Пример #12
0
def freedist(r):
    def _nonparametric():
        return np.eye(len(r))

    # Create model
    dd_nonparametric = Model(_nonparametric, constants='r')
    dd_nonparametric.description = 'Non-parametric distribution model'
    # Parameters
    dd_nonparametric.addlinear(
        'P',
        vec=len(r),
        lb=0,
        par0=0,
        description='Non-parametric distance distribution')
    return dd_nonparametric
Пример #13
0
def _getmodel(type):
    if type == 'parametric':
        model = Model(gauss2)
        model.mean1.set(lb=0, ub=10, par0=2)
        model.mean2.set(lb=0, ub=10, par0=4)
        model.width1.set(lb=0.1, ub=5, par0=0.2)
        model.width2.set(lb=0.1, ub=5, par0=0.2)
        model.amp1.set(lb=0, ub=5, par0=1)
        model.amp2.set(lb=0, ub=5, par0=1)
    elif type == 'semiparametric':
        model = Model(gauss2_design)
        model.mean1.set(lb=0, ub=10, par0=2)
        model.mean2.set(lb=0, ub=10, par0=4)
        model.width1.set(lb=0.1, ub=5, par0=0.2)
        model.width2.set(lb=0.1, ub=5, par0=0.2)
        model.addlinear('amp1', lb=0, ub=5)
        model.addlinear('amp2', lb=0, ub=5)
    elif type == 'nonparametric':
        model = Model(gauss2_design(3, 4, 0.5, 0.2))
        model.addlinear('amp1', lb=0)
        model.addlinear('amp2', lb=0)
    return model
Пример #14
0
                    ub=20,
                    par0=4.5,
                    units='nm')
dd_gauss2.width1.set(description='1st Gaussian standard deviation',
                     lb=0.05,
                     ub=2.5,
                     par0=0.2,
                     units='nm')
dd_gauss2.width2.set(description='2nd Gaussian standard deviation',
                     lb=0.05,
                     ub=2.5,
                     par0=0.2,
                     units='nm')
dd_gauss2.addlinear('amp1',
                    description='1st Gaussian amplitude',
                    lb=0,
                    par0=1,
                    units='')
dd_gauss2.addlinear('amp2',
                    description='2nd Gaussian amplitude',
                    lb=0,
                    par0=1,
                    units='')
# Add documentation
dd_gauss2.__doc__ = _dd_docstring(dd_gauss2,
                                  notes) + docstr_example('dd_gauss2')

#=======================================================================================
#                                     dd_gauss3
#=======================================================================================
ntoes = r"""
Пример #15
0
    model2 = dl.dd_rice
    model = lincombine(model1, model2)
    x = np.linspace(0, 10, 400)
    truth = model1(x, 3, 0.2) + model2(x, 4, 0.5)

    model.mean_1.par0 = 3
    model.location_2.par0 = 4
    result = fit(model, truth, x, x)

    assert np.allclose(result.model, truth)


# ======================================================================

model_vec = Model(lambda r: np.eye(len(r)), constants='r')
model_vec.addlinear('Pvec', vec=100, lb=0)


# ======================================================================
def test_vec_Nparam_nonlin():
    "Check that the combined model with a vector-form parameter has the right number of parameters"
    model1 = dl.dd_gauss
    model2 = model_vec

    model = lincombine(model1, model2)
    assert model.Nnonlin == model1.Nnonlin + model2.Nnonlin


# ======================================================================

Пример #16
0
def test_addlinear_vector_names():
    "Check that linear parameters can be defined as vectors"
    model = Model(gauss2_identity)
    model.addlinear('gaussian', vec=100)

    assert 'gaussian' in model.__dict__
Пример #17
0
    model = dl.dd_gauss2

    linkedmodel = link(model, amp=['amp1', 'amp2'])

    x = np.linspace(0, 10, 400)
    ref = model(x, 4, 0.5, 2, 0.2, 0.9, 0.9)

    response = linkedmodel(x, 4, 0.5, 2, 0.2, 0.9)

    assert np.allclose(response, ref)


# ======================================================================

double_vec = Model(lambda shift, scale: shift + scale * np.eye(80))
double_vec.addlinear('vec1', vec=40, lb=0, par0=0)
double_vec.addlinear('vec2', vec=40, lb=0, par0=0)


# ======================================================================
def test_vec_link_name():
    "Check that the parameters are linked to the proper name"
    model = double_vec

    linkedmodel = link(model, vec=['vec1', 'vec2'])

    assert hasattr(linkedmodel, 'vec') and not hasattr(
        linkedmodel, 'vec1') and not hasattr(linkedmodel, 'vec2')


# ======================================================================
Пример #18
0
def dipolarmodel(t,
                 r,
                 Pmodel=None,
                 Bmodel=bg_hom3d,
                 npathways=1,
                 harmonics=None,
                 experiment=None,
                 excbandwidth=np.inf,
                 orisel=None,
                 g=[ge, ge]):
    """
    Construct a dipolar EPR signal model. 

    Parameters
    ----------
    t : array_like 
        Vector of dipolar time increments, in microseconds.
    r : array_like 
        Vector of intraspin distances, in nanometers.
    Pmodel : :ref:`Model`, optional 
        Model for the distance distribution. If not speficied, a non-parametric
        distance distribution is assumed. 
    Bmodel : :ref:`Model`, optional 
        Model for the intermolecular (background) contribution. If not specified, 
        a background arising from a homogenous 3D distribution of spins is assumed. 
    npathways : integer scalar
        Number of dipolar pathways. If not specified, a single dipolar pathway is assumed. 
    experiment : :ref:`ExperimentInfo`, optional 
        Experimental information obtained from experiment models (``ex_``). If specified, the 
        boundaries and start values of the dipolar pathways' refocusing times and amplitudes 
        will be refined based on the specific experiment's delays.
    harmonics : list of integers 
        Harmonics of the dipolar pathways. Must be a list with `npathways` harmonics for each
        defined dipolar pathway.  
    orisel : callable  or ``None``, optional 
        Probability distribution of possible orientations of the interspin vector to account for orientation selection. Must be 
        a function taking a value of the angle θ∈[0,π/2] between the interspin vector and the external magnetic field and returning
        the corresponding probability density. If specified as ``None`` (by default), a uniform distribution is assumed. 
    excbandwidth : scalar, optional
        Excitation bandwidth of the pulses in MHz to account for limited excitation bandwidth.
    g : scalar, 2-element array, optional
        Electron g-values of the spin centers ``[g1, g2]``. If a single g is specified, ``[g, g]`` is assumed 

    Returns
    -------
    Vmodel : :ref:`Model`
        Dipolar signal model object.
    """

    # Input parsing and validation
    if not isinstance(Pmodel, Model) and Pmodel is not None:
        raise TypeError('The argument Pmodel must be a valid Model object')
    if not isinstance(Bmodel, Model) and Bmodel is not None:
        raise TypeError(
            'The argument Bmodel must be a valid Model object or None.')
    if not isinstance(npathways, int) or npathways <= 0:
        raise ValueError(
            'The number of pathway must be an integer number larger than zero.'
        )
    if isinstance(harmonics, float) or isinstance(harmonics, int):
        harmonics = [int(harmonics)]
    if not isinstance(harmonics, list) and harmonics is not None:
        raise TypeError(
            'The harmonics must be specified as a list of integer values.')

    #------------------------------------------------------------------------
    def _importparameter(parameter):
        """ Private function for importing a parameter's metadata """
        return {
            'lb': parameter.lb,
            'ub': parameter.ub,
            'par0': parameter.par0,
            'description': parameter.description,
            'units': parameter.units,
            'linear': parameter.linear
        }

    #------------------------------------------------------------------------

    # Parse the harmonics of the dipolar pathways
    if harmonics is None:
        harmonics = np.ones(npathways)
    if len(harmonics) != npathways:
        raise ValueError(
            'The number of harmonics must match the number of dipolar pathways.'
        )
    #------------------------------------------------------------------------
    def dipolarpathways(*param):
        """ Parametric constructor of the dipolar pathways definition """
        param = np.atleast_1d(param)
        if npathways == 1:
            # Single-pathway model, use modulation depth notation
            lam, reftime = param
            pathways = [[1 - lam], [lam, reftime, harmonics[0]]]
        else:
            # Otherwise, use general notation
            lams = param[np.arange(0, len(param), 2)]
            reftimes = param[np.arange(1, len(param), 2)]
            Lam0 = np.maximum(0, 1 - np.sum(lams))
            # Unmodulated pathways ccontribution
            pathways = [[Lam0]]
            # Modulated pathways
            for n in range(npathways):
                pathways.append([lams[n], reftimes[n], harmonics[n]])
        return pathways

    #------------------------------------------------------------------------

    # Construct the signature of the dipolarpathways() function
    if npathways == 1:
        variables = ['mod', 'reftime']
    else:
        variables = []
        for n in range(npathways):
            variables.append(f'lam{n+1}')
            variables.append(f'reftime{n+1}')

    # Create the dipolar pathways model object
    PathsModel = Model(dipolarpathways, signature=variables)

    Pnonparametric = Pmodel is None
    if Pnonparametric:
        Pmodel = freedist(r)
    Nconstants = len(Pmodel._constantsInfo)

    # Populate the basic information on the dipolar pathways parameters
    if npathways == 1:
        # Special case: use modulation depth notation instead of general pathway amplitude
        getattr(PathsModel, f'mod').set(lb=0,
                                        ub=1,
                                        par0=0.2,
                                        description=f'Modulation depth',
                                        units='')
        getattr(PathsModel, f'reftime').set(par0=0,
                                            description=f'Refocusing time',
                                            units='μs')
    else:
        # General case: use pathway ampltiudes and refocusing times
        for n in range(npathways):
            getattr(PathsModel, f'lam{n+1}').set(
                lb=0,
                ub=1,
                par0=0.2,
                description=f'Amplitude of pathway #{n+1}',
                units='')
            getattr(PathsModel, f'reftime{n+1}').set(
                par0=0,
                lb=-20,
                ub=20,
                description=f'Refocusing time of pathway #{n+1}',
                units='μs')

    # Construct the signature of the dipolar signal model function
    signature = []
    parameters, linearparam, vecparam = [], [], []
    for model in [PathsModel, Bmodel, Pmodel]:
        if model is not None:
            for param in model._parameter_list(order='vector'):
                if np.any(getattr(model, param).linear):
                    parameters.append(getattr(model, param))
                    linearparam.append({
                        'name':
                        param,
                        'vec':
                        len(np.atleast_1d(getattr(model, param).idx))
                    })
                elif not (model == Bmodel and param == 'lam'):
                    signature.append(param)
                    parameters.append(getattr(model, param))

    # Initialize lists of indices to access subsets of nonlinear parameters
    Psubset = np.zeros(Pmodel.Nnonlin, dtype=int)
    PathsSubset = np.zeros(PathsModel.Nnonlin, dtype=int)
    if Bmodel is None:
        Bsubset = []
    else:
        Bsubset = np.zeros(Bmodel.Nnonlin -
                           ('lam' in Bmodel._parameter_list()),
                           dtype=int)

    # Determine subset indices based on main function signature
    idx = 0
    for model, subset in zip([Pmodel, Bmodel, PathsModel],
                             [Psubset, Bsubset, PathsSubset]):
        if model is not None:
            for idx, param in enumerate(signature):
                if param in model._parameter_list(order='vector'):
                    subset[getattr(model, param).idx] = idx

    kernelmethod = 'fresnel' if orisel is None else 'grid'

    #------------------------------------------------------------------------
    def Vnonlinear_fcn(*nonlin):
        """ Non-linear part of the dipolar signal function """
        # Make input arguments as array to access subsets easily
        nonlin = np.atleast_1d(nonlin)
        # Construct the basis function of the intermolecular contribution
        if Bmodel is None:
            Bfcn = np.ones_like(t)
        elif hasattr(Bmodel, 'lam'):
            Bfcn = lambda t, lam: Bmodel.nonlinmodel(
                t, *np.concatenate([nonlin[Bsubset], [lam]]))
        else:

            Bfcn = lambda t, _: Bmodel.nonlinmodel(t, *nonlin[Bsubset])
        # Construct the definition of the dipolar pathways
        pathways = PathsModel.nonlinmodel(*nonlin[PathsSubset])
        # Construct the dipolar kernel
        Kdipolar = dipolarkernel(t,
                                 r,
                                 pathways=pathways,
                                 bg=Bfcn,
                                 excbandwidth=excbandwidth,
                                 orisel=orisel,
                                 g=g,
                                 method=kernelmethod)
        # Compute the non-linear part of the distance distribution
        Pnonlin = Pmodel.nonlinmodel(*[r] * Nconstants, *nonlin[Psubset])
        # Forward calculation of the non-linear part of the dipolar signal
        Vnonlin = Kdipolar @ Pnonlin
        return Vnonlin

    #------------------------------------------------------------------------

    # Create the dipolar model object
    DipolarSignal = Model(Vnonlinear_fcn, signature=signature)

    # Add the linear parameters from the subset models
    for lparam in linearparam:
        DipolarSignal.addlinear(lparam['name'], vec=lparam['vec'])

    if Pmodel is None:
        DipolarSignal.addlinear()

    # If there are no linear paramters, add a linear scaling parameter
    if DipolarSignal.Nlin == 0:
        DipolarSignal.addlinear('scale', lb=0, par0=1)

    # Import all parameter information from the subset models
    for name, param in zip(DipolarSignal._parameter_list(order='vector'),
                           parameters):
        getattr(DipolarSignal, name).set(**_importparameter(param))

    # Set prior knowledge on the parameters if experiment is specified
    if experiment is not None:
        if not isinstance(experiment, ExperimentInfo):
            raise TypeError(
                'The experiment must be a valid deerlab.ExperimentInfo object.'
            )

        # Check that the number of requested pathways does not exceed the theoretical limit of the experiment
        maxpathways = len(experiment.reftimes)
        if npathways > maxpathways:
            raise ValueError(
                f'The {experiment.name} experiment can only have up to {maxpathways} dipolar pathways.'
            )

        # Compile the parameter names to change in the model
        if npathways > 1:
            reftime_names = [f'reftime{n+1}' for n in range(npathways)]
            lams_names = [f'lam{n+1}' for n in range(npathways)]
        else:
            reftime_names = ['reftime']
            lams_names = ['mod']

        # Specify start values and boundaries according to experimental timings
        for n in range(npathways):
            getattr(DipolarSignal,
                    reftime_names[n]).set(par0=experiment.reftimes[n]['par0'],
                                          lb=experiment.reftimes[n]['lb'],
                                          ub=experiment.reftimes[n]['ub'])
            getattr(DipolarSignal,
                    lams_names[n]).set(par0=experiment.lams[n]['par0'],
                                       lb=experiment.lams[n]['lb'],
                                       ub=experiment.lams[n]['ub'])

    # Set other dipolar model specific attributes
    DipolarSignal.description = 'Dipolar signal model'
    DipolarSignal.Pmodel = Pmodel
    DipolarSignal.Bmodel = Pmodel
    DipolarSignal.Npathways = npathways

    return DipolarSignal
Пример #19
0
def test_addlinear_vector_length():
    "Check that linear parameters can be defined as vectors"
    model = Model(gauss2_identity)
    model.addlinear('gaussian', vec=100)

    assert model.Nparam == 100