In the fitting of multi-compartment models, it is often useful or necessary to impose certain constraints on the optimization of certain model parameters. For example, if you have a-priori knowledge on the values of some of the model parameter, then does not make sense to still fit it from the data. Or, if you know something about the structure of the data, then you want to constrain the relationships between your model parameters in a way that respects this structure.
Dmipy allows a user to set any parameter constraint or relation for any model combination. Here, we make a distinction between setting linked parameters and optimization parameters.
Setting a linked parameter means making one parameter's value dependent on the values of others (or on a fixed value). This means that after imposing a linked parameter, the model will not optimize on the linked parameter anymore (as it will be inferred from the others internally).
Setting an optimization parameter means replacing a parameter with a new, custom one. In the simplest case this can be some rescaling of the replaced parameter that will help the optimization find a solution the user prefers. However, like linked parameters, optimization parameters can also be make a function of other model parameters.
Below, we provide examples of typically already-included functions to set linked and optimization parameters, as well as explain how to say your own custom functions.
A linked parameter can be seen as somehow simplifying the problem by injecting already-known information into the optimization process. In Microstructure Imaging, often-used parameter links are imposing axon tortuosity, parameter equality and fixed parameters.
Axon Tortuosity: The idea behind axon tortuosity is that the perpendicular, extra-axonal diffusivity between a set of axons (modeled as parallel cylinders) is dependent on the overall water diffusivity of the medium, and the density of the cylinders. Mathematically, axon tortuosity is imposed as
$$\lambda_\perp=(1-vf)\lambda_\parallel$$where $\lambda_\perp$ and $\lambda_\parallel$ are the perpendicular extra-axonal and parallel intra-axonal diffusivity, and $vf$ is the intra-axonal volume fraction. If $vf=0$, then both diffusivities are equal. If $vf=1$, then $\lambda_\perp=0$.
Parameter Equality: As an example, it is often assumed that the parallel diffusivity of the intra- and extra-axonal models are the same.
Fixing Parameters: To avoid fitting them from the data, the diffusivities of different models are often fixed to some reasonable values.
To impose axon tortuosity we first define a multi-compartment model that models the intra- and extra-axonal space. We can define axon tortuosity both in the dispersed model and multi-compartment model representation:
# setting tortuosity in the dispersed model representation
from dmipy.signal_models import gaussian_models, cylinder_models
from dmipy.distributions import distribute_models
stick = cylinder_models.C1Stick()
zeppelin = gaussian_models.G2Zeppelin()
watson_bundle = distribute_models.SD1WatsonDistributed(models=[stick, zeppelin])
watson_bundle.parameter_names
['G2Zeppelin_1_lambda_perp', 'SD1Watson_1_odi', 'G2Zeppelin_1_lambda_par', 'SD1Watson_1_mu', 'C1Stick_1_lambda_par', 'partial_volume_0']
We can now call set_tortuous_parameter($\lambda_\perp$, $\lambda_\parallel$, vf) with the appropriate parameter names to set the tortuos parameter link
watson_bundle.set_tortuous_parameter('G2Zeppelin_1_lambda_perp', 'G2Zeppelin_1_lambda_par', 'partial_volume_0')
watson_bundle.parameter_names
['SD1Watson_1_odi', 'G2Zeppelin_1_lambda_par', 'SD1Watson_1_mu', 'C1Stick_1_lambda_par', 'partial_volume_0']
It can be seen that $\lambda_\perp$ is now removed from the list of parameters, as it has been made dependent on $\lambda_\parallel$ and $vf$.
Next, we impose that the parallel diffusivity is the same between the Stick (intra-axonal) and Zeppelin (extra-axonal) model. For this, we use set_equal_parameter function($\lambda_{\parallel, Stick}$, $\lambda_{\parallel, Zeppelin}$)
watson_bundle.set_equal_parameter('C1Stick_1_lambda_par', 'G2Zeppelin_1_lambda_par')
watson_bundle.parameter_names
['SD1Watson_1_odi', 'SD1Watson_1_mu', 'C1Stick_1_lambda_par', 'partial_volume_0']
Notice how the Zeppelin's $\lambda_\parallel$ was now removed.
Finally, models like NODDI (Zhang et al. 2012) also fix the model's diffuvity to avoid fitting them from the data. A value that is typically used for intra-axonal diffusvity is $\lambda=1.7e-9m^2/s$. As an example, we will fix the Stick's $\lambda_\parallel$ to this value using set_fixed_parameter($\lambda_\parallel$, fixed-value)
watson_bundle.set_fixed_parameter('C1Stick_1_lambda_par', 1.7e-9)
Which leaves us with 3 parameters left.
watson_bundle.parameter_names
['SD1Watson_1_odi', 'SD1Watson_1_mu', 'partial_volume_0']
The watson_bundle can finally be used in a MultiCompartment model like any other, after which it can be used for simulating and fitting data.
from dmipy.core.modeling_framework import MultiCompartmentModel
mcmodel = MultiCompartmentModel(models=[watson_bundle])
mcmodel.parameter_names
['SD1WatsonDistributed_1_SD1Watson_1_mu', 'SD1WatsonDistributed_1_partial_volume_0', 'SD1WatsonDistributed_1_SD1Watson_1_odi']
Once the multi-compartment model is made, simulating and fitting data is exactly as in the previous tutorials.
There are situations in which one wants to ensure that one parameter is always smaller or equal than another. For example, when only fitting a Zeppelin model to the data - like in the Spherical Mean Technique, see example - the perpendicular diffusivity is imposed to be smaller than the parallel diffusivity, i.e. $\lambda_\perp\leq\lambda_\parallel$.
from dmipy.core.modeling_framework import MultiCompartmentSphericalMeanModel
smt_mod = MultiCompartmentSphericalMeanModel([zeppelin])
smt_mod.parameter_names
['G2Zeppelin_1_lambda_perp', 'G2Zeppelin_1_lambda_par']
To impose this "smaller or equal than" constraint we use the set_fractional_parameter($\lambda_\perp$, $\lambda_\parallel$) function, where the former parameter is now constrained to smaller than the latter.
smt_mod.set_fractional_parameter('G2Zeppelin_1_lambda_perp', 'G2Zeppelin_1_lambda_par')
smt_mod.parameter_names
['G2Zeppelin_1_lambda_par', 'G2Zeppelin_1_lambda_perp_fraction']
Now the original "lambda_perp" parameter has been replaced by a "lambda_perp_fraction" parameter. This new parameter ranges between [0-1] and is internally interpreted as
\begin{equation} \lambda_\perp = \lambda_{\perp, fraction}\times\lambda_\parallel \end{equation}such that the smaller or equal than contraint is effectively enforced in the model.