Skip to content

model

Module for classes used to define DM models

Model(name, coeff_prefactor, coeff_func, F_med_prop=None, ref_cross_sect=None, S_chi=0.5, shortname=None)

A class representing a model for dark matter scattering. See the benchmark models for examples on how to define a model.

Attributes:

Name Type Description
name str

The name of the model.

coeff_prefactor dict

A dictionary of coefficient prefactors (constants).

coeff_func dict

A dictionary of coefficient functions of the form (grid, m_chi, S_chi) -> np.array.

F_med_prop Callable[[SphericalGrid], array]

The mediator propagator

power_V int

The power V

S_chi float

The value of S_chi.

operators dict

A dictionary of operators.

particles dict

A dictionary of particles.

Parameters:

Name Type Description Default
name str

The name of the model.

required
coeff_prefactor dict

A dictionary of coefficient prefactors.

required
coeff_func dict

A dictionary of coefficient functions.

required
F_med_prop Callable[[SphericalGrid], array] | None

A function that calculates the medium flux property. Defaults to None.

None
ref_cross_sect Callable[[array], array] | None

A function that calculates the reference cross section. Defaults to None.

None
S_chi float

DM spin, 1/2 by default.

0.5
shortname str

The short name of the model, used in the output filenames. Defaults to lowercase initials of the model name.

None
Source code in darkmagic/model.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def __init__(
    self,
    name: str,
    coeff_prefactor: dict,
    coeff_func: dict,
    F_med_prop: Callable[[SphericalGrid], np.array] | None = None,
    ref_cross_sect: Callable[[np.array], np.array] | None = None,
    S_chi: float = 0.5,
    shortname: str = None,
):
    """
    Constructor for the Model class.

    Args:
        name (str): The name of the model.
        coeff_prefactor (dict): A dictionary of coefficient prefactors.
        coeff_func (dict): A dictionary of coefficient functions.
        F_med_prop (Callable[[SphericalGrid], np.array] | None, optional): A function that calculates the medium flux property. Defaults to None.
        ref_cross_sect (Callable[[np.array], np.array] | None, optional): A function that calculates the reference cross section. Defaults to None.
        S_chi (float, optional): DM spin, 1/2 by default.
        shortname (str, optional): The short name of the model, used in the output filenames. Defaults to lowercase initials of the model name.
    """
    self.name = name
    if shortname is None and self.name is not None:
        shortname = "".join([word[0].lower() for word in name.split()])
    self.shortname = shortname

    self.S_chi = S_chi
    self.coeff_prefactor = coeff_prefactor
    self.coeff_func = coeff_func
    self.operators, self.particles = self._get_operators_and_particles()

    if F_med_prop is None:

        def ones(grid):
            return np.ones_like(grid.q_norm)

        F_med_prop = ones
    self.F_med_prop = F_med_prop

    # Conversion factor to $\bar{\sigma}$ in NATURAL UNITS
    if ref_cross_sect is None:

        def ref_cross_sect(m_chi):
            return np.ones_like(m_chi)

        ref_cross_sect = ref_cross_sect

    self.ref_cross_sect = ref_cross_sect

    self._validate_coefficients()

    # Unused, for backwards compatibility
    self.Fmed_power = 0
    self.power_V = 0

from_dict(d) classmethod

Create a Model object from a dictionary.

Parameters:

Name Type Description Default
d dict

The dictionary containing the model information.

required

Returns:

Name Type Description
Model

The Model object.

Source code in darkmagic/model.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@classmethod
def from_dict(cls, d: dict):
    """
    Create a Model object from a dictionary.

    Args:
        d (dict): The dictionary containing the model information.

    Returns:
        Model: The Model object.
    """
    return cls(
        d["name"],
        d["coeff_prefactor"],
        d["coeff_func"],
        None,  # d["F_med_prop"],
        S_chi=d["S_chi"],
        shortname=d["shortname"],
    )

to_dict(serializable=False)

Convert the Model object to a dictionary.

Parameters:

Name Type Description Default
serializable bool

Whether to return a serializable dictionary. Defaults to False. This essentially replaces function handles with their names.

False

Returns:

Name Type Description
dict dict

The dictionary representation of the Model object.

Source code in darkmagic/model.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def to_dict(self, serializable=False) -> dict:
    """
    Convert the Model object to a dictionary.

    Args:
        serializable (bool, optional): Whether to return a serializable dictionary. Defaults to False. This essentially replaces function handles with their names.

    Returns:
        dict: The dictionary representation of the Model object.
    """
    cf = self.coeff_func
    if serializable:
        cf = {
            alpha: {psi: f.__name__ for psi, f in c.items()}
            for alpha, c in cf.items()
        }
    return {
        "name": self.name,
        "coeff_prefactor": self.coeff_prefactor,
        "coeff_func": cf,
        # "F_med_prop": self.F_med_prop,  # TODO: this isn't written
        "S_chi": self.S_chi,
        "shortname": self.shortname,
    }

get_unscreened_coeff(alpha, psi, grid, m_chi, S_chi)

Get the unscreened coefficient for a given (alpha, psi) pair.

Parameters:

Name Type Description Default
alpha str

The operator ID.

required
psi str

The particle.

required
grid SphericalGrid

The spherical grid.

required
m_chi float

The dark matter mass.

required
S_chi float

The dark matter spin.

required

Returns:

Type Description
float

The unscreened coefficient \(c_{lpha}^{(\psi)}\).

Source code in darkmagic/model.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def get_unscreened_coeff(
    self, alpha: str, psi: str, grid: SphericalGrid, m_chi: float, S_chi: float
) -> float:
    """
    Get the unscreened coefficient for a given (alpha, psi) pair.

    Args:
        alpha: The operator ID.
        psi: The particle.
        grid: The spherical grid.
        m_chi: The dark matter mass.
        S_chi: The dark matter spin.

    Returns:
        The unscreened coefficient $c_{\alpha}^{(\psi)}$.
    """
    return self.coeff_prefactor[alpha][psi] * self.coeff_func[alpha][psi](
        grid, m_chi, S_chi
    )

compute_screened_coeff(grid, epsilon, m_chi, S_chi)

Compute the screened coefficients for every (operator, particle) pair.

The screening of electron coefficients is done according to $$ c^{(e)}{\alpha} \rightarrow \frac{c^{(e)}{\alpha}}{\hat{q} \cdot (\epsilon \hat{q})} $$ and the proton coefficients $$ c^{(p)}{\alpha} \rightarrow c^{(p)}{\alpha} + c^{(e)}_{\alpha} \left( 1 - \frac{1}{\hat{q} \cdot (\epsilon \hat{q})} \right) $$

Parameters:

Name Type Description Default
grid SphericalGrid

The SphericalGrid object containing all the momentum transfer vectors.

required
epsilon array

The dielectric tensor.

required
m_chi float

The dark matter mass.

required
S_chi float

The dark matter spin.

required

Returns:

Type Description
dict

A dictionary of the screened coefficients, indexable as [alpha][psi], with each element being a np.array of shape (nq,).

Source code in darkmagic/model.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def compute_screened_coeff(
    self, grid: SphericalGrid, epsilon: np.array, m_chi: float, S_chi: float
) -> dict:
    r"""
    Compute the screened coefficients for every (operator, particle) pair.

    The screening of electron coefficients is done according to
    $$
    c^{(e)}_{\alpha} \rightarrow \frac{c^{(e)}_{\alpha}}{\hat{q} \cdot (\epsilon \hat{q})}
    $$
    and the proton coefficients
    $$
    c^{(p)}_{\alpha} \rightarrow c^{(p)}_{\alpha} + c^{(e)}_{\alpha} \left( 1 - \frac{1}{\hat{q} \cdot (\epsilon \hat{q})} \right)
    $$

    Args:
        grid: The SphericalGrid object containing all the momentum transfer vectors.
        epsilon: The dielectric tensor.
        m_chi: The dark matter mass.
        S_chi: The dark matter spin.

    Returns:
        A dictionary of the screened coefficients, indexable as [alpha][psi], with each element being a np.array of shape (nq,).
    """
    q_eps_q = np.sum(grid.qhat_qhat * epsilon[None, :], axis=(-1, -2))
    screened_coeff = {
        alpha: {psi: self.coeff_prefactor[alpha][psi] for psi in self.particles}
        for alpha in self.operators
    }
    for alpha, c_alpha in screened_coeff.items():
        for psi in c_alpha.keys():
            if psi == "e":
                c_alpha[psi] *= 1 / q_eps_q
            elif psi == "p":
                # TODO: is this correct?
                c_alpha[psi] += (1 - 1 / q_eps_q) * c_alpha.get("e", 0)
            elif psi == "n":
                c_alpha[psi] *= np.ones_like(q_eps_q)
            # neutrons are unscreened
            c_alpha[psi] *= self.coeff_func[alpha][psi](grid, m_chi, S_chi)

    return screened_coeff

Potential(model)

Class for evaluating the potential for a given model.

TODO: needs a good bit of cleanup and rethinking. Maybe this should be a subclass of model? TODO: all terms except V1_00 are written correctly but don't work with an array of q's as they should. (Painful) work in progress.

Attributes:

Name Type Description
operators set

The set of operators.

particles set

The set of particles.

c Callable[[SphericalGrid, array, float, float], array]

The function to compute the screened coefficients.

needs_g1 bool

Whether the G1 velocity integrals are needed.

needs_g2 bool

Whether the G2 velocity integrals are needed.

Parameters:

Name Type Description Default
model Model

The model for which to evaluate the potential.

required
Source code in darkmagic/model.py
308
309
310
311
312
313
314
315
316
317
def __init__(self, model: Model):
    """
    Constructor for the Potential class.

    Args:
        model (Model): The model for which to evaluate the potential.
    """
    self.operators = model.operators
    self.particles = model.particles
    self.c = model.compute_screened_coeff

eval_V(grid, material, m_chi, S_chi)

Evaluate the full potential V_j(q) for the given grid and material

Parameters:

Name Type Description Default
grid SphericalGrid

The grid of momentum transfer vectors.

required
material Material

The material object

required
m_chi float

The dark matter mass.

required
S_chi float

The dark matter spin.

required

Returns:

Name Type Description
dict dict

A dictionary of the potential terms, indexed by the term type.

Source code in darkmagic/model.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
def eval_V(
    self, grid: SphericalGrid, material: Material, m_chi: float, S_chi: float
) -> dict:
    """
    Evaluate the full potential V_j(q) for the given grid and material

    Args:
        grid (SphericalGrid): The grid of momentum transfer vectors.
        material (Material): The material object
        m_chi (float): The dark matter mass.
        S_chi (float): The dark matter spin.

    Returns:
        dict: A dictionary of the potential terms, indexed by the term type.
    """

    # Get all the V functions
    full_V = self._get_full_V()

    # Prepare the output dictionary
    terms = {key for alpha in self.operators for key in full_V[alpha].keys()}
    V = {t: self._get_zeros(t, material.n_atoms, grid) for t in terms}

    # Determine which velocity integrals are needed
    self.needs_g1 = bool("12" or "11" in terms)
    self.needs_g2 = "20" in terms

    def get_slice(t):
        return (slice(None),) + (None,) * (V[t].ndim - 1)

    coeff = self.c(grid, material.epsilon, m_chi, S_chi)
    # TODO: write this nicer
    for psi in self.particles:
        for alpha in self.operators:
            C = coeff[alpha][psi]
            for t, V_func in full_V[alpha].items():
                V[t] += C[get_slice(t)] * V_func(grid, psi, material, m_chi, S_chi)
    return V

MissingCoefficientFunctionException

Bases: Exception

Raised when an operator has a non-zero coefficient prefactor but no coefficient function.

ExtraCoefficientFunctionWarning

Bases: Warning

Warning that an operator has a coefficient function but no non-zero coefficient prefactor, so the operator will be ignored.

UnsupportedOperatorException

Bases: Exception

Raised when an operator is not supported.