Python interface

Contents

Python interface#

This section describes the Python user interface of Mulder. The user interface is organised in sub-topics, Geometry, Materials, Modules, Physics, States, Simulation and Pictures, as described below. Moreover, Mulder exhibits some package-level data, as outlined in the Configuration section.

Geometry interface#

To represent an Earth-like geometry, typically described by a Digital Elevation Model (DEM), Mulder provides a dedicated EarthGeometry object. Alternatively, a Calzone (Geant4) geometry can be imported as a LocalGeometry object. Other use cases might be implemented as an external Module (using Mulder’s C interface). Mulder geometry objects rely on a common model, inherited from Pumas [Nie22], which is discussed in the Geometry section.


class mulder.EarthGeometry#

A stratified Earth geometry.

This class represents a stratified section of the Earth. The strates (or Layers) form distinct propagation media that are assumed to be uniform in composition and density. They are delimited vertically, typically by a Grid of elevation values, forming a Digital Elevation Model (DEM).

Note

Mulder uses Turtle’s algorithm [NBCM20] to efficiently navigate through EarthGeometry objects.

__new__(*layers)#

Creates a new Earth geometry.

The layers are provided in index order, i.e. the first layer has index 0 and is thus the bottom strate. Each individual layer argument may be either an explicit Layer object, or data-like objects coercing to the latter. For instance, the following two syntaxes lead to the same geometry.

>>> geometry = mulder.EarthGeometry(
...     mulder.Layer("dem.asc", 0.0),
...     mulder.Layer(-100.0)
... )
>>> geometry = mulder.EarthGeometry(
...     ("dem.asc", 0.0),
...     -100.0
... )

Geometry methods

Note

The geometry methods below use the Coordinates interface to specify the coordinates of interest.

locate(position=None, /, *, notify=None, **kwargs)#

Locates point(s) within the Earth geometry.

The method returns the layer index(es) that correspond to the input position(s). For instance,

>>> layer = geometry.locate(latitude=45, altitude=-5.0)
scan(coordinates=None, /, *, notify=None, output=None, **kwargs)#

Performs a scan of the Earth geometry.

The output argument determines the returned data. The possible values are "grammage" (i.e., \(\int{\rho(s) ds}\), a.k.a. opacity in the context of muography), "intersections" and "thickness" (which is the default setting). For instance, the following returns an array containing the thicknesses of the layers along the line(s) of sight specified by the input coordinates, as

>>> thickness = geometry.scan(latitude=45, elevation=10)
>>> thickness[0]  
3.0

If output is set to "intersections", then, for each input coordinate, this method returns an array containing the successive tracing intersections, as obtained per the trace() method.

trace(coordinates=None, /, *, ignore=None, notify=None, **kwargs)#

Performs a tracing step of the Earth geometry.

The method returns a structured array describing the first intersection(s) along the line(s) of sight specified by the input coordinates. For instance,

>>> intersection = geometry.trace(latitude=45, elevation=10)
>>> intersection["distance"]  
3.0

During the tracing process, specific layers can be excluded by providing their indices as the optional ignore argument.

Attributes

Note

EarthGeometry objects are immutable, i.e. their structure cannot be modified. However, the density and material of layers is mutable.

layers#

The Earth geometry layers.

The first layer (of index 0) is the bottom strate, while the last layer is the top-most strate. The latter can be accessed as

>>> top = geometry.layers[-1]
zlim#

The Earth geometry limits along the z-coordinate.


class mulder.Grid#

This class represents a parametric surface, \(z = f(x, y)\), described by a regularly spaced Grid of elevation values, \(z_{ij} = f(x_j, y_i)\), forming a Digital Elevation Model (DEM).

The elevation values, \(z_{ij}\), of a Grid object may be offset by a constant value using the + and - operators. For example, the following will create a new grid offset by 100.0 metres w.r.t. the initial one.

>>> new_grid = initial_grid + 100.0

Tip

The offsetting of a grid creates a reference to the data of the initial grid, i.e. data is not duplicated.

__new__(data, /, *, xlim=None, ylim=None, crs=None)#

Creates a new grid.

The data argument may refer to:

  • A file containing a DEM (see Table 1 for supported file formats).

  • A folder containing the tiles of a Global Digital Elevation Model (GDEM), such as SRTMGL1.003.

  • A 2D array containing the \(z_{ij}\) values in row-major order.

In the latter case, the xlim and ylim arguments must specify the DEM limits along the \(x\) and \(y\)-axes.

Depending on the data argument, a Coordinate Reference System (CRS) may be specified (by providing its EPSG code). See Table 2 for a list of supported crs values. By default, the WGS84 / GPS system is assumed.

For instance, the following loads elevation data stored in ASCII Grid format using UTM 31N coordinates, i.e. EPSG:32631.

>>> grid = mulder.Grid("dem.asc", crs=32631)
Table 1 Supported file formats.#

Description

Extension

ASCII Grid

.asc

GeoTIFF

.tif

EGM96 Grid

.grd

HGT

.hgt

Table 2 Supported Coordinate Reference Systems.#

Description

EPSG code(s)

NTF / Lambert I-IV

27571-27574

RGF93 / Lambert 93

2154

WGS84 / GPS

4326

WGS84 / UTM 1-60 N

32601-32660

WGS84 / UTM 1-60 S

32701-32760

Methods

gradient(x_or_xy, y=None, /, *, notify=None)#

Computes the elevation gradient(s) at grid point(s).

This method returns the gradient w.r.t. the \(x\) and \(y\) coordinates. The interface is the same as the Grid.z() method. Please refer to the latter for a description of the arguments.

z(x_or_xy, y=None, /, *, notify=None)#

Computes the elevation value(s) at grid point(s).

This method is vectorised. It accepts either a sequence of \((x_k, y_k)\) values as the first argument, or two sequences of \(x_j\) and \(y_i\) values as the first and second arguments. In the latter case, the method returns the \(z_{ij}\) values corresponding to the outer product \((x_j, y_i)\). For instance, the following returns a 2D array of elevation values, z, with shape (41, 21).

>>> x, y = np.linspace(-1, 1, 21), np.linspace(-2, 2, 41)
>>> z = grid.z(x, y)

Attributes

Note

Grid instances are immutable.

crs#

Coordinate Reference System.

The grid Coordinate Reference System (CRS) is encoded according to the EPSG standard. For example,

>>> grid.crs
32631
xlim#

Grid limits along the x-coordinate.

ylim#

Grid limits along the y-coordinate.

zlim#

Grid limits along the z-coordinate.


class mulder.Layer#

A layer (or strate) of an Earth geometry.

This class represents a layer (or strate) of an EarthGeometry, considered to be uniform in composition and density. A layer is delimited by a top surface, typically described by one or more Grids of elevation values. The bottom of a layer is determined by the top of its underlying layer within the EarthGeometry.

__new__(*data, density=None, description=None, material=None)#

Creates a new layer.

The data argument determines the top of the layer. It must be akin to a Grid object. Alternatively, a float value can be provided to specify a flat topography. Multiple data can be provided to specify successive fallback models. For example, the following creates a new layer whose top surface is defined by two data sets, as

>>> layer = mulder.Layer("dem.asc", 0.0)

The corresponding top surface matches the Digital Elevation Model (DEM) from the file dem.asc within its domain of definition, but falls back to a constant elevation value of 0 outside this domain.

See the layer attributes below for the meaning of the optional density, description and material arguments.

Methods

altitude(latitude_or_latlon, longitude=None, /, *, frame=None, notify=None)#

Computes the layer top altitude(s) at coordinate(s).

This method is vectorised. It accepts either a sequence of \((\phi_k, \lambda_k)\) values as the first argument, where \(\phi\) denotes the latitude and \(\lambda\) the longitude, or two sequences of \(\phi_i\) and \(\lambda_j\) values as the first and second arguments. In the latter case, the method returns the \(z_{ij}\) values corresponding to the outer product \((\lambda_j, \phi_i)\). For instance, the following returns a 2D array of altitude values with shape (181, 361).

>>> lat, lon = np.linspace(-90, 90, 181), np.linspace(-180, 180, 361)
>>> altitudes = layer.altitude(lat, lon)
normal(latitude_or_latlon, longitude=None, /, *, frame=None, notify=None)#

Computes the layer top normal(s) at coordinate(s).

This method returns the normal to the top surface at the latitude (\(\phi\)) and longitude (\(\lambda\)) coordinates. The interface is the same as the Layer.altitude() method. Please refer to the latter for a description of the arguments.

The optional frame argument specifies the coordinates system (as a LocalFrame) in which the normal should be expressed. If this argument is omitted, geocentric (ECEF) coordinates will be used.

Attributes

data#

The layer top elevation data.

Note

This attribute is immutable.

density#

The layer bulk density.

The bulk density is expressed in \(\mathrm{kg}/\mathrm{m}^3\). If None, then the material default density is assumed.

description#

An optional description.

material#

The layer constitutive material.

This attribute is the name of the material. For instance, the following changes the layer material to water.

>>> layer.material = "Water"

class mulder.LocalFrame#

An Earth-local reference-frame.

This class specifies a Local-Tangent-Plane (LTP) reference frame on the Earth. Optionnaly, the frame can be inclined (w.r.t. the vertical) or declined (w.r.t. the geographic north).

Note

LocalFrame instances are immutable.

__new__(coordinates=None, /, **kwargs)#

Creates a new Earth-local reference-frame.

The coordinates argument specifies the origin position and the y-axis direction, using the Coordinates interface. For example, the following defines a local frame close to Clermont-Ferrand, France.

>>> frame = mulder.LocalFrame(latitude=45.8, longitude=3.1)

If no direction is specified, then local frames are East-North-Upward (ENU) oriented by default.

camera(resolution=None, /, *, focal=None, fov=None, ratio=None)#

Spawns a new camera.

See the Camera object documentation for further details.

Note

The focal and fov arguments cannot be specified simultaneously since these two quantities are directly related.

looking_at(position=None, /, *, skew=None, **kwargs)#

Returns a rotated local frame.

The position argument specifies the target point using the Position interface. For example, the following returns a local frame oriented along the x-axis of the initial frame.

>>> frame1 = frame0.looking_at(position=(1, 0, 0))
transform(q, /, *, destination, mode)#

Transforms point(s) or vector(s) to another local frame.

The quantity, q, is transformed from the self LocalFrame to the destination one. The mode parameter specifies the nature of q (i.e., "point" or "vector"). For example, the following computes the coordinates, in frame1, of the \(\vec{e}_x\) basis vector of frame0.

>>> ex = frame0.transform((1, 0, 0), destination=frame1, mode="vector")
translated(v, /)#

Returns a translated local frame.

The translation vector v is expressed in the local coordinates of the initial frame. For instance, the following returns a frame translated by 1 meter along the x-axis of the initial frame.

>>> frame1 = frame0.translated((1, 0, 0))

Position attributes

Note

The latitude, longitude and altitude attributes refer to the origin position.

altitude#

The altitude coordinate of the frame origin, in m.

latitude#

The latitude coordinate of the frame origin, in deg.

longitude#

The longitude coordinate of the frame origin, in deg.

Direction attributes

Note

The azimuth and elevation attributes refer to the y-axis direction. The skew angle (a.k.a. roll angle) is a rotation around the transformed y-axis.

azimuth#

The frame azimuth angle (w.r.t. the geographic north), in deg.

elevation#

The frame elevation angle (w.r.t. the local vertical), in deg.

skew#

The frame skew angle (a.k.a. roll angle), in deg.


class mulder.LocalGeometry#

A local geometry.

This class represents a local geometry on the Earth, w.r.t. a LocalFrame. Local geometries can be created by importing a Calzone geometry. Alternatively, they might be implemented using an external software, typically in C/C++, wrapped within a mulder.Module.

__new__(data, /, *, frame=None)#

The data argument may be a path-string pointing to a Module file or to a Calzone geometry file. Alternatively, one might provide a calzone.Geometry object as data argument. For instance, the following loads a local geometry from a Calzone geometry file.

>>> geometry = mulder.LocalGeometry("geometry.toml")

The optional frame argument specifies the origin and orientation of the local geometry as a LocalFrame object.

Geometry methods

locate(position=None, /, *, notify=None, **kwargs)#

Locates point(s) within the local geometry.

The method returns the media index(es) that correspond to the input position(s). For instance,

>>> media = geometry.locate(position=[0, 0, 1])
scan(coordinates=None, /, *, notify=None, output=None, **kwargs)#

Performs a scan of the local geometry.

The output argument determines the returned data. The possible values are "grammage" (i.e., \(\int{\rho(s) ds}\), a.k.a. opacity in the context of muography), "intersections" and "thickness" (which is the default setting). For instance, the following returns an array containing the thicknesses of the media along the line(s) of sight specified by the input coordinates, as

>>> thickness = geometry.scan(position=[0, 0, 1], direction=[0, 0, -1])
>>> thickness[0]  
1.0

If output is set to "intersections", then, for each input coordinate, this method returns an array containing the successive tracing intersections, as obtained per the trace() method.

trace(coordinates=None, /, *, ignore=None, notify=None, **kwargs)#

Performs a tracing step of the local geometry.

The method returns a structured array describing the first intersection(s) along the line(s) of sight specified by the input coordinates. For instance,

>>> intersection = geometry.trace(position=[0, 0, 1], direction=[0, 0, -1])
>>> intersection["distance"]  
1.0

During the tracing process, specific media can be excluded by providing their indices as the optional ignore argument.

Attributes

frame#

The geometry reference frame.

The geometry reference frame is mutable. For instance,

>>> geometry.frame = mulder.LocalFrame(altitude=10.0)
media#

The geometry media.

The geometry media form an immutable sequence. A medium is identified by its index within this sequence. For instance, the following returns the first medium

>>> first = geometry.media[0]

class mulder.Medium#

A medium of a local geometry.

This class represents a medium of a LocalGeometry, considered to be uniform in composition and density.

normal(position=None, /, *, notify=None, **kwargs)#

Computes the surface normal(s).

Attributes

density#

The medium bulk density.

The bulk density is expressed in \(\mathrm{kg}/\mathrm{m}^3\). If None, then the material default density is assumed.

description#

An optional description.

material#

The medium constitutive material.

This attribute is the name of the material. For instance, the following changes the medium material to water.

>>> medium.material = "Water"  

Materials interface#

Mulder makes a distinction between base Materials and Composite materials. A base Material is a microscopic mixture of atomic Elements. A Composite material, by contrast, is a macroscopic mixture of base Materials, typically a rock composed of various minerals.

Note

The stopping-power of a Composite material slightly differs from that of a base Material with the same composition, due to the density effect in ionisation loss.

A material (atomic element) is uniquely identified by its name (atomic symbol), which maps to a concrete definition, e.g. a Material (Element). The mapping may be lazy, i.e. delayed until usage. Once a material (element) definition has been established, it cannot be modified or removed.

Note

The resolution of unmapped materials (elements) is done in the following order.

  1. Firstly, the material name (element symbol) is searched for in Modules, in order of loading.

  2. Secondly, Mulder’s companion Python package(s) are inspected, e.g. Calzone.

  3. Finally, Mulder default definitions are checked.

class mulder.materials.Composite#

This class represents a macroscopic mixture of Materials. Composite objects use the mapping protocol to expose their components’ mass fractions, which are mutable. For example

>>> composite["Water"] = 0.1  

It is not possible to add or remove a constitutive material once the composite has been mapped. However, a specific material may be disabled by setting its mass fraction to zero.

__new__(name, /)#

Gets a composite definition.

Returns the definition of the composite matching name. For instance,

>>> composite = materials.Composite("HumidRock")  
all()#

Returns all currently mapped composites.

The composites are returned as a dict object mapping names to definitions.

define(name, /, *, composition)#

Defines a composite material.

Note

This method explictly maps the material name to the provided composite definition.

The composite is defined by specifying its composition, for instance as,

>>> humid_rock = materials.Composite.define(
...     "HumidRock",
...     composition=("Rock", "Water"),
... )

The mass fractions may also be specified when defining the composite, for instance as

>>> humid_rock = materials.Composite.define(
...     "HumidRock",
...     composition={"Rock": 0.95, "Water": 0.05},
... )

Attributes

Note

Composite instances are immutable appart from their components’ mass fractions.

composition#

The composite content.

The composite content is returned as a tuple. For example

>>> humid_rock.composition
('Rock', 'Water')

The corresponding mass fractions may be accessed using the mapping protocol, as

>>> humid_rock["Water"]
0.05
density#

The composite density, in kg/m3.

Note

The composite density depends on the mass fractions of its constitutive materials.


class mulder.materials.Element#

This class represents an atomic element, which may be a specific isotope or a mixture of isotopes.

Tip

Mulder provides default definitions for atomic elements from H, D (\(Z=1\)) to Og (\(Z=118\)) according to the PDG. Furthermore, Mulder provides a fictitious Rk (\(Z=11, A=22\)) element to represent standard rock.

__new__(symbol, /)#

Gets an atomic element definition.

Returns the definition of the atomic element matching symbol. For instance,

>>> H = materials.Element("H")
all()#

Returns all currently mapped elements.

The elements are returned as a dict object mapping the atomic elements symbols to their definitions.

define(symbol, /, *, Z, A, I=None)#

Defines a new atomic element.

Note

This method explictly maps the element symbol to the provided element definition.

If the Mean Excitation Energy (I) is omitted, a default value is used depending on Z. For example,

>>> U_238 = materials.Element.define("U-238", Z=92, A=238.0508)

Attributes

Note

Element instances are immutable.

A#

The element mass number, in g/mol.

I#

The element Mean Excitation Energy, in GeV.

Z#

The element atomic number.


class mulder.materials.Material#

This class represents a homogeneous material, at the microscopic scale. A Material object may be composed of a single atomic Element (e.g., C), a molecule (e.g., H2O) or be a mixture (e.g., air). In addition to the atomic composition, the material structure is essentially summarised by its density and its mean excitation energy (I).

Tip

Mulder provides default definitions for the Air, Rock and Water materials.

__new__(name, /)#

Gets a material definition.

Returns the definition of the material matching name. For instance,

>>> rock = materials.Material("Rock")
all()#

Returns all currently mapped materials.

The materials are returned as a dict object mapping names to definitions.

define(name, /, *, composition, density, I=None)#

Note

This method explictly maps the material name to the provided material definition.

The composition argument may be a str, specifying the material chemical composition, as

>>> ice = materials.Material.define("Ice", composition="H2O", density=0.92E+03)

Alternatively, the composition argument may be akin to a dict mapping atomic elements or other materials to mass fractions. For example, as

>>> moist_air = materials.Material.define(
...     "MoistAir",
...     composition={"Air": 0.99, "Water": 0.01},
...     density=1.2
... )

See the material attributes below for a description of the density and I arguments.

Attributes

Note

Material instances are immutable.

composition#

The material mass composition.

The atomic mass composition is returned as a tuple. For example

>>> moist_air.composition
(('Ar', 0.0126987...), ..., ('O', 0.2383443...))
density#

The material density, in kg/m3.

I#

The material Mean Excitation Energy, in GeV.

If None then the mean excitation energy is computed from the material’s atomic content assuming Bragg additivity [BrKl05].


mulder.materials.dump(path, *materials)#

Dump material definitions.

If the materials arguments are ommited, then all currently mapped material definitions are dumped to a TOML file, for instance as

>>> materials.dump("materials.toml")

Alternatively, one may explicit the material definitions to dump, for example as

>>> materials.dump("materials.toml", "Ice", "MoistAir", "HumidRock")

mulder.materials.load(path, /)#

Load material definitions.

The definition file must be in TOML format. For example, the following materials.toml file defines two Materials (Ice and MoistAir) and one Composite (HumidRock).

[Ice]
composition = "H2O"
density = 0.92E+03  # kg/m3

[MoistAir]
composition = { Air = 0.99, Water = 0.01 }  # mass fractions
density = 1.2  # kg/m3

[HumidRock]
composition = [ "Rock", "Water" ]

The corresponding material definitions are loaded as

>>> materials.load("materials.toml")

Module interface#

External software may be interfaced with Mulder as external modules. For further information, please refer to the External modules section.

class mulder.Module#

This class provides an interface to an external module, typically implemented in C/C++. External modules may extend Mulder functionalities with new materials and geometries.

__new__(path, /)#

Loads a Module.

The path argument must refer to a shared library containing the module implementation. For instance, on a Linux system,

>>> module = mulder.Module("module.so")  

Methods

element(symbol, /)#

Fetches a module atomic element.

This method explictly maps the element symbol to its module definition. For example,

>>> H = module.element("G4_H")  
geometry(*, frame=None)#

Creates an external geometry.

The external geometry is returned as a LocalGeometry object, for example as,

>>> geometry = module.geometry()  

Optionally, a LocalFrame may be specified using the frame named argument.

material(name, /)#

Fetches a module material.

This method explictly maps the material name to its module definition. For example,

>>> air = module.material("G4_AIR")  

Attributes

Note

Module attributes are immutable.

path#

The module location.

The absolute path to the shared library containing the module implementation. This path also serves as a unique identifier for the module during runtime.

ptr#

Pointer to the C interface.

A ctypes.c_void_p pointing to the initialised module, i.e. to the struct mulder_module object obtained upon calling the mulder_initialise() entry point function.

Physics interface#

Mulder builds over the Pumas transport engine, the physics implementation of which is described in detail in [Nie22]. In order to improve performance, some key physical properties are pre-computed and later interpolated at runtime, e.g. cross-sections, stopping-powers, etc. This process can be triggered manually using the compile() method of a Physics instance, which translates a set of Material and Composite definitions into CompiledMaterials, providing access to the tabulated physical properties.

Note

Since their computation can be time consuming, material tables related to a specific set of materials and Physics settings are cached. See the DEFAULT_CACHE entry for information on controlling the cache location.

Tip

Fluxmeters seamlessly manage the generation of material tables, negating the need for any explicit compilation.

class mulder.CompiledMaterial#

This class acts as a proxy for the material tables relating to a specific material. The physical properties can be accessed via vectorised class methods. For example as,

>>> energy = np.geomspace(1E-02, 1E+03, 101)
>>> stopping_power = compiled_material.stopping_power(energy)

Note

The CompiledMaterial class cannot be instantiated directly; it must be generated using the Physics.compile() method instead.

Methods

cross_section(energy, /, *, notify=None)#

Returns the macroscopic cross-section.

The macroscopic cross-section, expressed in \(\mathrm{m}^{-1}\), is restricted to hard collisions with a fractionnal energy loss larger than the physics cutoff. Collisions with a smaller energy loss are included in the continuous energy loss given by the stopping_power() method.

Note

The hard elastic collisions are not included in the macroscopic cross-section but in the elastic mean free path given by the elastic_scattering() method.

elastic_scattering(energy, /, *, notify=None)#

Returns the muon elastic scattering properties.

This method returns the mean free path, in metres, restricted to hard elastic collisions and the corresponding cutoff angle, in deg. The cutoff angle is expressed in the center of mass frame of the collision. It is set according to the physics elastic_ratio following Fernandez-Varea et al. [FMBS93].

Note

Soft elastic collisions are taken into account in the multiple scattering (see the transport_path() method).

inverse_range(range, /, *, mode=None, notify=None)#

Returns the inverse of the muon CSDA range.

This method returns the inverse of the CSDA range, i.e. the required muon kinetic energy, in GeV, for a given range, expressed in metres. See the range() method for further details.

magnetic_gyration(energy, /, *, mode=None, notify=None)#

Returns the magnetic gyration coefficient.

This method returns the magnetic gyration coefficient, \(g\), over the total muon range, assuming continuous energy loss. The gyration coefficient is defined as

\[g = \frac{\theta}{B_\perp} ,\]

where \(B_\perp\) is the magnetic field transverse to the muon direction, and \(\theta\) the resulting rotation angle. The returned gyration coefficient is expressed in deg / T.

proper_time(energy, /, *, mode=None, notify=None)#

Returns the muon proper time.

This method returns the ellapsed proper time of a particle over its total range, in seconds. Continuous energy loss is assumed.

range(energy, /, *, mode=None, notify=None)#

Returns the muon CSDA range.

The CSDA range is expressed in metres. See the stopping_power() method for the corresponding continuous energy loss.

Note

In mixed or discrete modes, the range does not include hard collisions.

stopping_power(energy, /, *, mode=None, notify=None)#

Returns the material stopping-power.

The material stopping-power is expressed in \(\mathrm{GeV}/\mathrm{m}\). See the range() method for the corresponding CSDA range.

Note

In mixed or discrete modes, the stopping power does not include hard collisions.

transport_path(energy, /, *, mode=None, notify=None)#

Returns the transport mean free path.

The transport mean free path, expressed in metres, is restricted to soft collisions, including both elastic and inelastic processes.

Note

The transport m.f.p., \(\lambda\), is related to the standard deviation of the multiple scattering angle as \(\sigma_\theta^2 = s / (2 \lambda)\), where \(s\) is the travelled distance.

Attributes

definition#

The material definition.

This attribute is an instance of Composite or Material, depending on the type of material.

name#

The material name.


class mulder.Physics#

This class provides access to configurable Pumas settings relevant to muon transport physics, as mutable attributes. For further details, please refer to [Nie22]. In addition, the Physics class provides an interface for generating material tables from material definitions, using the compile() method.

__new__(**kwargs)#

Creates a Physics context.

Configuration settings can be provided as keyword arguments (kwargs). See the class attributes below for a list of possible parameters. For example,

>>> physics = mulder.Physics(cutoff=5E-02)

Methods

compile(*materials, notify=None)#

Compiles material definitions to physics tables.

If the materials arguments are ommited, then all currently defined materials are compiled. The returned CompiledMaterials can be extracted to a dict, for instance as

>>> compiled = { m.name: m for m in physics.compile() } 

Alternatively, one may explicit the materials to compile, for example as

>>> ice, rock = physics.compile("Ice", "Rock") 

Attributes

bremsstrahlung#

The Bremsstrahlung model for muon energy losses.

The possible values for bremsstralung models are summarised in Table 3 below, the default setting is "SSR19".

Table 3 Available bremsstrahlung models.#

Model

Reference

"ABB94"

Andreev, Bezrukov and Bugaev, Physics of Atomic Nuclei 57 (1994) 2066.

"KKP95"

Kelner, Kokoulin and Petrukhin, Moscow Engineering Physics Inst., Moscow, 1995.

"SSR19"

PROPOSAL‘s implementation of [SSR19].

cutoff#

The cutoff between hard and soft energy losses.

Relative cutoff between soft and hard energy losses. Setting a null or negative value results in the default cutoff value to be used i.e. 5% which is a good compromise between speed and accuracy for transporting a continuous muon spectrumm, see e.g. Sokalski et al. [SBK01].

Warning

Cutoff values lower than 1% are not supported.

elastic_ratio#

The hard to soft ratio for elastic collisions.

Ratio of the mean free path for hard elastic events to the smallest of the transport mean free path or CSDA range. The lower the ratio the more detailed the simulation of elastic scattering, see e.g. Fernandez-Varea et al. [FMBS93]. Setting a null or negative value results in the default ratio to be used i.e. 5%.

pair_production#

The e+e- pair-production model for muon energy losses.

The possible values for pair-production models are summarised in Table 4 below, the default setting is "SSR19".

Table 4 Available pair-production models.#

Model

Reference

"KKP68"

Kelner, Kokoulin and Petrukhin, Soviet Journal of Nuclear Physics 7 (1968) 237.

"SSR19"

PROPOSAL‘s implementation of [SSR19].

photonuclear#

The photonuclear model for muon energy losses.

The possible values for photonuclear interaction models are summarised in Table 5 below, the default setting is "DRSS01".

Table 5 Available photonuclear interaction models.#

Model

Reference

"BBKS03"

Bezrukov, Bugaev, Sov. J. Nucl. Phys. 33 (1981), 635, with improved photon-nucleon cross-section according to Kokoulin and hard component from Bugaev and Shlepin.

"BM02"

Butkevich and Mikheyev, Soviet Journal of Experimental and Theoretical Physics 95 (2002) 11.

"DRSS01"

Dutta, Reno, Sarcevic and Seckel, Phys.Rev. D63 (2001) 094020.

States interface#

A Mulder state is a set of variables used to characterise a flux of muons. Typically, a state specifies a view point (position and direction of observation) together with the kinetic energy of the observed muons.

Tip

State variables may be vectorised, e.g. to represent a collection of muons, or a spectrum. Mulder follows a straightforward broadcasting rule that requires variables to be either scalar or to share the same size, regardless of their array shape.

Mulder considers two different representations of a set of states, GeographicStates and LocalStates, differing by their coordinate system. GeographicStates represent the position and direction of observation using geographic-like variables (e.g. latitude, longitude), while LocalStates use Cartesian coordinates w.r.t. a LocalFrame. The correspondence between the two representations is outlined in Table 6 below. Conversion methods (e.g. to_geographic(), to_local()) can be used to transform between the two representations.

Table 6 State variables.#

Geographic representation

Local representation

latitude, longitude, altitude

position

azimuth, elevation

direction

energy, pid, weight

energy, pid, weight

Note

The direction of observation variable(s) specifies the opposite of the muon propagation direction, in both the Geographic and Local representations.

Note

The pid variable is optional. It categorises a state as a muon (pid = 13) or as an anti-muon (pid = -13). If ommitted, each state is regarded as a superposition of muons and anti-muons.

Tip

State variables are stored internally as NumPy structured arrays accessible via the array attribute. For the sake of convenience, shape related attributes (ndim, shape, size) are also forwarded.

States objects are used as input to Mulder functions, for instance as follows

>>> states = mulder.GeographicStates(
...     latitude = 45.0,
...     energy = np.geomspace(1E-02, 1E+04, 61)
... )
>>> result = some_state_function(states)

Alternatively, state variables can be provided directly as named arguments. For instance, the following syntax produces the same result as the previous example.

>>> result = some_state_function(
...     latitude = 45.0,
...     energy = np.geomspace(1E-02, 1E+04, 61)
... )

Some Mulder functions use only a subset of state variables, thus defining sub-interfaces. Functions that require only position (position and direction) variables are said to follow the Position interface (Coordinates interface). These functions will also accept states objects as positional arguments, but only position (position and direction) variables as named arguments.

class mulder.GeographicStates#

A geographic representation of Mulder states.

__new__(states=None, /, **kwargs)#

Creates state(s) using geographic coordinates.

This class method uses the States interface. For instance,

>>> states = mulder.GeographicStates(
...     latitude = 45,
...     energy = np.geomspace(1E-02, 1E+04, 61)
... )

Coordinates methods

from_local(states, /)#

Creates geographic states from local ones.

to_local(frame=None, /)#

Converts the geographic states to local ones.

Array methods

Note

Depending on the tagged argument, the array methods described below return tagged muon or anti-muon states, or untagged ones (i.e. a superposition of muons and anti-muons).

dtype(*, tagged=False)#

Returns the corresponding array dtype.

empty(shape=None, /, *, tagged=False)#

Returns uninitialised geographic states.

full(shape=None, /, fill_value=None, **kwargs)#

Returns a collection of identical geographic states.

from_array(array, /, *, copy=True)#

Creates geographic states from a Numpy array.

The input NumPy array must be of GeographicStates.dtype. If copy is False, the returned GeographicStates object refers to the input array.

zeros(shape=None, /, *, tagged=False)#

Returns zeroed geographic states.

Coordinates attributes

Note

The direction of observation is the opposite of the muon propagation direction.

Note

The azimuth and elevation angles refer to LocalFrames, the origins of which are defined by the latitude, longitude and altitude attributes.

altitude#

The altitude coordinate, in m.

azimuth#

The azimuth angle of observation, in deg.

elevation#

The elevation angle of observation, in deg.

latitude#

The latitude coordinate, in deg.

longitude#

The longitude coordinate, in deg.

Common state attributes

energy#

The kinetic energy, in GeV.

pid#

The PDG particle identifier.

For untagged states this attribute is immutably None.

weight#

The Monte Carlo weight.

Array attributes

array#

The underlying NumPy array.

ndim#

The geographic states’ array dimension.

shape#

The geographic states’ array shape.

size#

The total number of geographic states.


class mulder.LocalStates#

A local representation of Mulder states.

__new__(states=None, /, **kwargs)#

Creates state(s) using local coordinates.

This class method uses the States interface. For instance,

>>> states = mulder.LocalStates(
...     direction = (0, 0, 1),
...     energy = np.geomspace(1E-02, 1E+04, 61)
... )

Coordinates methods

from_geographic(*, frame=None)#

Creates local states from geographic ones.

to_geographic()#

Converts the local states to geographic ones.

transform(destination)#

Transforms to another local frame.

Array methods

Note

Depending on the tagged argument, the array methods described below return tagged muon or anti-muon states, or untagged ones (i.e. a superposition of muons and anti-muons).

dtype(*, tagged=False)#

Returns the corresponding array dtype.

empty(shape=None, /, *, tagged=False)#

Returns uninitialised local states.

full(shape=None, /, fill_value=None, **kwargs)#

Returns a collection of identical local states.

from_array(array, /, *, copy=True, frame=None)#

Creates local states from a Numpy array.

The input NumPy array must be of LocalStates.dtype. If copy is False, the returned LocalStates object refers to the input array.

zeros(shape=None, /, *, tagged=False)#

Returns zeroed local states.

Coordinates attributes

frame#

The coordinates local frame.

position#

The local position, in m.

direction#

The local direction of observation.

Note

The direction of observation is the opposite of the muon propagation direction.

Common state attributes

energy#

The kinetic energy, in GeV.

pid#

The PDG particle identifier.

For untagged states this attribute is immutably None.

weight#

The Monte Carlo weight.

Array attributes

array#

The underlying NumPy array.

ndim#

The local states’ array dimension.

shape#

The local states’ array shape.

size#

The total number of local states.

Simulation interface#

A Mulder simulation is managed using a Fluxmeter object. For basic use cases, one might simply invoke the flux() methods which returns the muon flux for input observation states, depending on the Fluxmeter configuration (atmosphere, geometry, reference, etc.). For more advanced usage, please refer to the flux computation section.

class mulder.Atmosphere#

An atmospheric medium.

This class manages the properties of the atmosphere medium. The atmosphere is assumed to be homogeneous in composition, but with a density that varies vertically.

__new__(model=None, /, *, material=None)#

Creates a new atmospheric medium.

The model argument specifies the vertical density profile, which is provided as an \(N \times 2\) array mapped as \([(z_0, \rho_0), \ldots, (z_{N-1}, \rho_{N-1})]\) with altitudes (\(z\)) in meters and densities (\(\rho\)) in \(\mathrm{kg} / \mathrm{m}^3\). For instance,

>>> atmosphere = mulder.Atmosphere((
...     (     0, 1.225E+00),
...     ( 1_000, 4.135E-01),
...     (30_000, 1.841E-02),
...     (70_000, 8.283E-05),
... ))

Note

The provided altitude values (\(z\)) should be strictly increasing, and the density values (\(\rho\)) must be strictly positive.

Alternatively, a predefined model can be specified, e.g. as

>>> atmosphere = mulder.Atmosphere("midlatitude-summer")

See the models class attribute for a list of predefined density models.

By default, the atmosphere is composed of "Air". This can be overridden using the optional material argument, for examples as follows

>>> atmosphere = mulder.Atmosphere(material="SaturatedAir")

See the Materials interface for information on defining custom materials.

density(altitude, /)#

Computes the density value(s) at the specified altitude(s).

This method is vectorised. It can accomodate a scalar altitude input or an array of altitude values. For instance,

>>> densities = atmosphere.density(np.linspace(0E+00, 1E+05, 10001))

Attributes

material#

The constitutive material.

This is a mutable attribute. For instance, the following changes the atmosphere material

>>> atmosphere.material = "SaturatedAir"

See the Materials interface for information on defining custom materials.

model#

The density model.

This is an immutable attribute containing a copy of the density model used when the atmospheric medium was defined.

Class attributes

models = ['midlatitude-summer', 'midlatitude-winter', 'subartic-summer', 'subartic-winter', 'tropical', 'us-standard']#

Predefined density models according to the MODTRAN 2/3 report [AbAn96].


class mulder.EarthMagnet#

A snapshot of the geomagnetic field.

This class provides an interface to a geomagnetic model, parametrised by spherical harmonics. The default model used by Mulder is IGRF14.

__new__(model=None, /, *, date=None)#

Creates a new snapshot of the geomagnetic field.

If provided, the model argument should point to a *.COF file containing the geomagnetic model coefficients.

The optional date argument allows the user to specify the date of the snapshot, as a datetime.date object, or as an ISO 8601-formatted string. For instance,

>>> from datetime import date
>>> magnet = mulder.EarthMagnet(date=date.today())

or

>>> magnet = mulder.EarthMagnet(date="1978-08-16")
field(position=None, /, *, notify=None, **kwargs)#

Computes the geomagnetic field value(s) at the specified position(s).

This method uses the Position interface for specifying the position(s) of interest. For instance, using geographic coordinates

>>> field = magnet.field(latitude=45, longitude=3)

The returned field is expressed in Tesla (T) units, with the coordinates frame depending on the input position. For geographic positions, ENU coordinates are returned. For local positions, the field is returned in the local frame of the input positions.

Attributes

Note

EarthMagnet instances are immutable.

date#

The snapshot date.

model#

The model name.

zlim#

The model altitude limits, in m.


class mulder.Fluxmeter#

A muon fluxmeter.

This class provides a high-level interface for computing alterations in the flux of atmospheric muons, due to geometrical features, w.r.t. to an open-sky reference model. For basic use cases one might simply use the flux() method with default settings. For more advanced usage, please refer to the flux computation section.

__new__(*layers, **kwargs)#

Creates a new fluxmeter.

The layers arguments may specify an EarthGeometry. For instance, the following creates a meter with a 2-layers geometry.

>>> meter = mulder.Fluxmeter(
...     ("dem.asc", 0.0),
...     -100.0
... )

Alternatively, the geometry may be explicitly specified as a named argument. For instance, the following creates a meter with a LocalGeometry loaded from a file.

>>> meter = mulder.Fluxmeter(geometry="geometry.toml")

Other attributes (see below) may be specified as named arguments, as well. For instance,

>>> meter = mulder.Fluxmeter(
...     atmosphere = "midlatitude-winter",
...     date = "2025-12-25",                # For geomagnetic field.
...     mode = "mixed",
...     bremsstrahlung = "KKP95",           # For physics model.
...     seed = 123456,                      # For random engine.
...     reference = "Gaisser90",
... )

Note that specifying a date enables the geomagnetic field, which is disabled by default. Alternatively, the geomagnetic field might also be enabled as,

>>> meter = mulder.Fluxmeter(geomagnet=True)
flux(states=None, /, *, events=None, notify=None, **kwargs)#

Compute flux estimate(s).

This method uses the States interface for specifying the observation states(s) of interest. For instance, the following computes the flux at an altitude of 100 m and along an elevation angle of 30 deg.

>>> flux = meter.flux(altitude=100, elevation=30)

In mixed or detailed mode, the events parameter specifies the number of reference states that are generated for each observation state in order to estimate the flux. In these cases, the method returns the flux and error estimates as an ndarray. For instance,

>>> flux, sigma = meter.flux(altitude=100, elevation=30, events=1000)
transport(states=None, /, *, events=None, notify=None, **kwargs)#

Transport state(s) to the reference model.

This method uses the States interface for specifying the observation states(s) of interest. For instance, the following determines the reference state corresponding to an observation altitude of 100 m, along an elevation angle of 30 deg.

>>> state0 = meter.transport(altitude=100, elevation=30)

In mixed or detailed mode, the events parameter specifies the number of reference states that are generated for each observation state. For example, the following returns an ndarray containing a thousand reference states.

>>> states0 = meter.transport(altitude=100, elevation=30, events=1000)

Attributes

atmosphere#

The atmosphere model.

This attribute is an instance of a mulder.Atmosphere, which controls the atmosphere properties. For convenience, the atmosphere model can be provided directly when setting this attribute. For example,

>>> meter.atmosphere = "us-standard"
geomagnet#

The geomagnetic field.

This attribute is an instance of a mulder.EarthMagnet, which controls the geomagnetic field. For convenience, the geomagnetic model can be provided directly when setting this attribute. For example,

>>> meter.geomagnet = "IGRF14.COF"

By default, the geomagnetic field is disabled.

geometry#

The surrounding geometry.

This attribute is an EarthGeometry or a LocalGeometry. For convenience, local geometry data can be provided directly when setting this attribute. For example,

>>> meter.geometry = "geometry.toml"
mode#

The transport mode.

Possible values are, "continous", "discrete" or "mixed". By default, the fluxmeter operates in continuous mode. For instance, the following switches the fluxmeter to discrete mode.

>>> meter.mode = "discrete"
physics#

The muon physics.

This attribute is an instance of a mulder.Physics, which controls the physics of the muon transport.

random#

The pseudo-random stream.

This attribute is an instance of a mulder.Random, which controls the pseudo-randomness of simulated events.

reference#

The reference muon flux.

This attribute is an instance of a mulder.Reference, which controls the reference model for flux computations.


class mulder.Random#

A Pseudo-Random Numbers Generator (PRNG).

This class manages a cyclic sequence of pseudo-random numbers over the interval \((0, 1)\). These numbers are exposed as a stream of floats. The sequence is fully determined by the seed attribute, while the index attribute indicates the stream state.

Note

A Permuted Congruential Generator (PCG) is used (namely Mcg128Xsl64), which has excellent performances for Monte Carlo applications.

__new__(seed=None, *, index=None)#

Creates a new pseudo-random stream.

If seed is None, then a random value is picked using the system entropy. Otherwise, the specified seed value is used. For instance,

>>> prng = mulder.Random(123456789)
uniform01(shape=None, /)#

Generate pseudo-random number(s) uniformly distributed over (0,1).

If shape is None, then a single number is returned. Otherwise, a numpy.ndarray is returned, with the given shape. For instance, the following returns the next 100 pseudo-random numbers from the stream.

>>> rns = prng.uniform01(100)

Attributes

index#

The PRNG stream index.

This property can be modified, resulting in consuming or rewinding the pseudo-random stream. For instance, the following resets the stream.

>>> prng.index = 0
seed#

The PRNG initial seed.

The property fully determines (and identifies) the pseudo-random sequence. Note that modifying the seed also resets the stream to index 0.


class mulder.Reference#

This class represents a reference model of the muon flux. Typically, this is the opensky flux, i.e the atmospheric muon flux in the absence of any topographic features.

Mulder expresses the reference flux, \(\phi(K, \epsilon, z)\), as function of the muon kinetic energy, \(K\), and using geographic coordinates, where \(\epsilon\) is the elevation angle of observation and \(z\) the altitude. In addition, the contributions of muons (\(\phi_-\)) and anti-muons (\(\phi_+\)) are split, as \(\phi = \phi_+ + \phi_-\).

Alternatively, the reference flux might be set as flat, typically \(\phi = 1\) over a domain in \((K, \epsilon, z)\). This is especially relevant in conjuction with the Fluxmeter.transport() method, e.g. to generate a sample of reference muons.

__new__(model=None, /, **kwargs)#

Creates a reference model.

The model argument might be,

  • an array containing a tabulation of the reference flux,

  • a str indicating a parametric model (see Table 7),

  • a Path to a file containing a tabulated flux model,

  • a float indicating a flat reference.

By default, i.e. if model is None, the parametric model of [GCC+15] is used. For instance, as

>>> reference = mulder.Reference()
Table 7 Parametric reference models.#

Name

Reference

"GCCLY15"

[GCC+15]

"Gaisser90"

[Gai90]

flux(states=None, /, **kwargs)#

Computes the reference flux.

This method uses the States interface for specifying the observation state of interest. For instance, using geographic coordinates,

>>> flux = reference.flux(elevation=30)

Attributes

Note

Reference objects are immutable, i.e. the underlying model or its support cannot be modified.

altitude#

Altitude (range) of the reference flux.

Depending on the reference model, the altitude might be a float constant or an interval. For instance,

>>> reference.altitude
0.0
elevation#

Elevation range of the reference flux.

energy#

Energy range of the reference flux.

model#

The reference model.

Depending on how the reference was created, this attribute may be a float, a ndarray or a str. For instance,

>>> reference.model
'GCCLY15'

Picture interface#

Mulder ray-tracing algorithms may be used to visualise a geometry (e.g., as a cross-check). For this purpose, Mulder provides a Camera object from which a Projection of the geometry may be done (onto the camera screen). The geometry is then rendered by applying a lighting model to the projected data.

Important

Mulder ray-tracing algorithms are designed for particle transport. These algorithms are suboptimal for graphic applications.

Light sources

Mulder provides three types of light sources: AmbientLight, DirectionalLight, or SunLight. It is also possible to define a superposition of several light sources as a sequence, for example as

>>> lights = (
...     picture.AmbientLight(intensity=0.1),
...     picture.DirectionalLight(colour="gold"),
... )

The light intensity defines the source power on a linear scale, with a default intensity of 1. The light colour indicates the source spectral content. By default, light sources are white.

Colours

Mulder uses the sRGB colour space to specify colours, as a triplet of values within \([0, 1]\). For instance, the following defines purple as a balanced mixture of red and blue.

>>> purple = (1, 0, 1)

Alternatively, string-encoded matplotlib colours may be employed. For instance,

>>> colour1 = "skyblue"
>>> colour2 = "#87CEEB"

class mulder.picture.AmbientLight#

An ambient light source.

Ambient light sources provide uniform illumination, which, although convenient, may be counterintuitive in certain situations, for example when rendering outdoor scenes. In the latter case, a SunLight would be a more natural choice.

__new__(*, colour=None, intensity=None)#

Creates an ambient light source.

For instance, the following creates a dim golden ambient light source,

>>> light = picture.AmbientLight(colour="gold", intensity=0.1)

See the Light sources and Colours sections for further details.

Attributes

colour#

The light colour.

intensity#

The light intensity.


class mulder.picture.Atmosphere#

Graphic properties of the atmosphere.

The atmosphere rendering is done following the Physically Based Rendering (PBR) model of [Hil20], adapted to Mulder. The atmosphere properties are set according to the Earth, and are immutable. For more information, please refer to [Hil20].

Note

This class is singleton. Please note that all methods below are class methods.

aerial_view(projection, /, *, lights)#

Computes the aerial light.

The aerial light is computed over the input projection, which must be an instance of Projection. Please refer to the Light sources section for information on the lights argument.

ambient_light(position=None, /, *, lights, **kwargs)#

Computes the ambient light table.

This method uses the Position interface for specifying the camera position. For instance, using geographic coordinates

>>> ambient_light = picture.Atmosphere.ambient_light(
...     lights = picture.SunLight(datetime="2025-06-21 15:00:00"),
...     longitude = 3,
...     latitude = 45,
... )

Please refer to the Light sources section for information on the lights argument.

multiple_scattering()#

Returns the multiple scattering table.

sky_view(position=None, /, *, lights, **kwargs)#

Computes the sky view table.

This method uses the Position interface for specifying the camera position. Please refer to the Light sources section for information on the lights argument.

transmittance(elevation, /, *, altitude=None)#

Computes the transmittance value(s).

The elevation argument may be a scalar or an array. The corresponding transmittance is returned. The optional altitude argument specifies the origin of the ray(s).


class mulder.picture.Camera#

A camera model.

This class represents a digital camera. Camera objects are spawned using the camera() method of a LocalFrame, which defines the camera’s position and orientation. For instance, as

>>> camera = mulder                            \
...     .LocalFrame(elevation=15, altitude=1)  \
...     .camera()

The camera manages a 2D grid of lines of sight, accessible via the pixels attribute. Geometries can be projected onto the camera screen using the project() method.

project(geometry, /, *, ignore=None, notify=None)#

Projects a geometry.

This method encodes a geometry of interest as a raw Projection, using photographic projection. For instance,

>>> projection = camera.project(geometry)

Specific media can be made transparent by providing their indices as the optional ignore argument.

Attributes

Note

Camera instances are immutable.

frame#

The camera reference frame.

The reference frame defines the camera position and orientation (i.e., the pointing direction). For instance, the following camera points towards the geographic north,

>>> camera.frame.azimuth
0.0
focal#

The camera focal length.

The focal length is normalised to the screen width, and thus unit-less. The focal length determines the field of view (FOV) of the camera.

fov#

The camera horizontal field of view (FOV), in degrees.

The field of view (FOV) is in a bijective mapping with the camera focal length.

pixels#

The camera pixels.

The pixels matrix is exposed as a Pixels object. For instance, the ray coordinates along teh camera pixels can be obtained as,

>>> rays = camera.pixels.coordinates
ratio#

The camera screen ratio (width / height).

Unless otherwise specified, the screen ratio is determined by the camera resolution assuming square pixels. Standard screen ratio values are 4:3 or 16:9.

resolution#

The camera screen resolution (height, width), in pixels.


class mulder.picture.DirectionalLight#

A directional light source.

Directional lights model a distant source, providing a locally flat illumination oriented along the source direction.

__new__(azimuth=None, elevation=None, *, colour=None, intensity=None)#

Creates a directional light source.

For instance, the following creates a remote light source located along the south, at an elevation angle of 15 deg.

>>> light = picture.DirectionalLight(azimuth=180, elevation=15)

See the Light sources and Colours sections for further details.

Attributes

azimuth#

The source azimuth direction, in deg.

colour#

The light colour.

elevation#

The source elevation direction, in deg.

intensity#

The light intensity.


class mulder.picture.Material#

Graphic properties of a material.

This class stipulates the graphic properties of a material. For more detailed information, please refer to the Filament documentation.

__new__(*, colour=None, metallic=None, reflectance=None, roughness=None)#

Creates a new set of graphic properties.

See the attributes below for the meaning of arguments.

Attributes

colour#

Perceived colour (albedo), in sRGB space.

See the Colours section for instructions on defining a colour.

metallic#

Dielectric (false) or conductor (true).

Smooth metalic surfaces behave as mirrors, whereas dielectric surfaces are diffusive.

Tip

A float value in \([0, 1]\) may also be provided, for example, to represent a blend of dielectric and conductor materials.

reflectance#

Specular intensity for non-metals, in [0, 1].

Fresnel reflectance at normal incidence for dielectric materials. This property is ignored for metals.

roughness#

Surface roughness, in [0, 1].

Perceived smoothness (0.0) or roughness (1.0) of the material surface. Smooth surfaces exhibit sharp reflections.


class mulder.picture.MaterialMap#

A Material map.

This class represents a linear blending of Materials parametrised by a scalar observable, \(\alpha\) (typically, altitude values). The mapping is defined by providing nodes, \((\alpha_i, M_i)\), between which the material properties (\(M\)) are linearly interpolated as a function of \(\alpha\).

__new__(nodes, /)#

Creates an new material map.

For example, the following defines a mapping from a green to brown Material for parameter values (\(\alpha\)) ranging from 0 to 1000.

>>> mmap = picture.MaterialMap((
...     (   0, picture.Material(colour="darkgreen")),
...     (1000, picture.Material(colour="saddlebrown")),
... ))
material(alpha, /)#

Maps to a material.

This method returns the material properties corresponding to the input parameter value (\(\alpha\)). For instance,

>>> material = mmap.material(500)

Attributes

Note

MaterialMap instances are immutable.

nodes#

The map nodes.


mulder.picture.MATERIALS: dict#

A collection of materials graphic properties.

This dict variable maps the material names to their corresponding graphic properties (i.e., a Material or MaterialMap). For instance, the following modifies the colour of the Rock material.

>>> picture.MATERIALS["Rock"].colour = "saddlebrown"

Additional materials might be defined as well, for example as follows

>>> picture.MATERIALS["Ice"] = picture.Material(
...     metallic = True,
...     roughness = 0.1,
... )

class mulder.picture.Pixels#

A set of camera pixels.

Instances of this class are obtained by using the pixels attribute of a Camera object. The camera pixels define a mapping between screen coordinates \((u, v)\) and lines of sight.

Attributes

azimuth#

The pixels azimuth direction, in degrees.

coordinates#

The pixels geographic coordinates.

Tip

The pixels coordinates can be used as input to Mulder functions using the Coordinates interface, to iterate over the lines of sight defined by the camera pixels.

elevation#

The pixels elevation direction, in degrees.

u#

The pixels u coordinates.

This attribute represents the screen horizontal coordinates within the range of \([0, 1]\).

v#

The pixels v coordinates.

This attribute represents the screen vertical coordinates within the range of \([0, 1]\).


class mulder.picture.Projection#

A geometry projection.

Projection objects are generated with the project() method of a Camera object, e.g. as

>>> projection = camera.project(geometry)

A projection object is used to encode the first media observed along the lines of sight of the camera, as well as the corresponding surface normals at intersection points. This information can be rendered as a digital image by applying a lighting model.

normal(frame=None)#

Returns the surface normal at intersections.

The normal vectors are returned as a numpy ndarray of shape (height, width, 3). The coordinates are given in the LocalFrame specified as frame argument. If the latter argument is omitted, then the camera frame is assumed.

render(*, atmosphere=None, data=None, exposure=None, lights=None, notify=None)#

Renders the projection as an image.

The image rendering is done following the Physically Based Rendering (PBR) model of Filament, adapted to Mulder.

Please refer to the Light sources section for information on the lights argument. Furthermore, for straightforward use cases the lighting model can also be specified as a str. For instance, as

>>> image = projection.render(lights="sun")

The supported models are "ambient", "directional" and "sun".

If the atmosphere argument is set to True, then the sky is rendered following [Hil20].

Note

The atmosphere argument may also explicitly specify the index of the atmosphere medium.

The image exposure may be modified using the exposure argument, employing stops units (i.e. a base 2 logarithmic scale), with the value 0 corresponding to the default exposure. For instance, the following reduces the image exposure by a factor of 2,

>>> image = projection.render(exposure=-1)

The data argument specifies a mapping to be used in conjunction with a MaterialMap, as a dict object mapping material names to parameter values. For instance, the following defines a mapping between the rock material and the projected altitude values.

>>> image = projection.render(data={"Rock": projection.altitude})

Note that the provided parameter values must be consistent with the image shape.

view(frame=None)#

Returns the view directions.

The view vectors indicate the direction of the camera lines of sight. The directions are returned as a numpy ndarray of shape (height, width, 3). The coordinates are expressed in the LocalFrame which is provided as frame argument. When the latter argument is omitted, then the camera frame is used.

Attributes

altitude#

The altitude at intersections, in meters.

This attribute is a mutable ndarray of shape (height, width).

distance#

The distance to intersections, in meters.

This attribute is a mutable ndarray of shape (height, width).

frame#

The camera reference frame.

medium#

The visible media indices.

This attribute is a mutable ndarray, of shape (height, width), containing the visible media indices. See the Geometry model section for further information.

materials#

The materials mapping.

This attribute defines the mapping between media and materials, as a sequence of material names. For instance,

>>> projection.materials
('G4_AIR', 'G4_CALCIUM_CARBONATE')

This indicates that the first medium (of index 0) is composed of Geant4 air and the second (of index 1) is composed of limestone (\(\mathrm{Ca}\mathrm{CO}_3\)). This mapping could be redefined, for instance using Mulder native materials, as

>>> projection.materials = ('Air', 'Rock')

class mulder.picture.SunLight#

A sun like light source.

This class models the Sun lighting, which is a particular case of DirectionalLight whose orientation is specified by a local date and solar time.

__new__(*, colour=None, datetime=None, intensity=None)#

Creates a sun light source.

The datetime argument determines the Sun location. This argument can be provided as a datetime object, or as an ISO 8601 string. For instance, as

>>> light = picture.SunLight(datetime="2025-06-21 13:00:00")

See the Light sources and Colours sections for further details.

Attributes

colour#

The sun light colour.

datetime#

The local date and solar time.

intensity#

The sun light intensity.


Configuration data#

Configuration data can be accessed via the mulder.config singleton class. For instance, as

>>> mulder.config.VERSION
'0.3.7'

The available configuration data are listed below.

mulder.config.CACHE: Path#

The cache location.

Mulder uses a caching system to reduce the time taken for some time-consuming, yet repetitive tasks, such as the compilation of Physics tables. This data indicates the location of cached files. For instance, the following changes the cache location

>>> mulder.config.CACHE = "/tmp/mulder"  

Note

By default, cache files are stored under $HOME/.cache/mulder. This can be overriden by setting the MULDER_CACHE environment variable to the desired location.

mulder.config.NOTIFY: bool#

Default status for notifications.

For operations that are potentially time-consuming, Mulder reports its progress to the terminal using a progress bar. By setting this flag to False, such reports will be disabled, unless individual function calls explicitly set their notify argument to True.

mulder.config.PREFIX: Path#

The package installation prefix.

mulder.config.VERSION: str#

The package version.