Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3b5ac88
store abundance in MaterialState
arlauwer Feb 17, 2025
e236472
opacity working
arlauwer Feb 18, 2025
6e26d10
fix opacitySca
arlauwer Feb 20, 2025
44da359
started bound-bound emission
arlauwer Mar 18, 2025
13b2ed2
small fix
arlauwer Mar 28, 2025
75bdfbc
started work on material mix family
Apr 14, 2025
41d5741
cached all cross sections
arlauwer Apr 17, 2025
89d0010
attempt fix by passing Z, T to MaterialMixFamily::mix, however mix is…
arlauwer Apr 18, 2025
915b35f
functional precomputing in XRayIonicGasMix
arlauwer Apr 22, 2025
ea37abe
fix lorentzian
arlauwer Apr 22, 2025
967a7d6
fixed temp
arlauwer Apr 24, 2025
1f35d35
small fix
arlauwer Apr 29, 2025
0833127
fix default mix nullptr
arlauwer May 5, 2025
addbe57
quick fix in family setup
arlauwer Sep 8, 2025
5c52aee
first attempt resonant scattering
arlauwer Oct 10, 2025
3f81283
rs bug fixes
arlauwer Oct 13, 2025
c2e6d62
perform rs scattering
arlauwer Oct 14, 2025
68accf3
fix dipole
arlauwer Oct 14, 2025
64502af
fix vtherm
arlauwer Oct 15, 2025
1b21a05
add full Lyman series
arlauwer Dec 23, 2025
8f9bfc0
started rework ionicmix
arlauwer Dec 30, 2025
1f9a5b1
Merge branch 'master' into ionised
arlauwer Dec 30, 2025
1babfb0
refactor names
arlauwer Dec 30, 2025
11edc68
possible fix of incoherence
arlauwer Dec 30, 2025
d8db86a
full refactor
arlauwer Jan 6, 2026
3e0c6cd
use faddeeva algo for Voigt profile
arlauwer Jan 8, 2026
60224e7
fix hasExtraSpecificState
arlauwer Jan 27, 2026
74b7311
fix eq. for a
arlauwer Feb 2, 2026
2139912
fix shifts
arlauwer Feb 2, 2026
db11d53
branching is isotropic
arlauwer Mar 17, 2026
df4f49c
start refactor
arlauwer Apr 30, 2026
654c438
family WIP
arlauwer Apr 30, 2026
470233d
remove family
arlauwer Apr 30, 2026
585f505
remove rayleigh
arlauwer May 4, 2026
697334f
document XRayIonicGasMix
arlauwer May 8, 2026
02e0951
Merge remote-tracking branch 'upstream' into ionised
arlauwer May 8, 2026
de07eea
revert small inconsistencies
arlauwer May 8, 2026
110f3cd
fix compiler warning
arlauwer May 8, 2026
7d3d444
cite data source
arlauwer May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SKIRT/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# ------------------------------------------------------------------

# add all relevant subdirectories; each subdirectory defines a single target
add_subdirectory(faddeeva)
add_subdirectory(fitsio)
add_subdirectory(voro)
add_subdirectory(tetgen)
Expand Down
4 changes: 2 additions & 2 deletions SKIRT/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ target_link_libraries(${TARGET} schema fundamentals)
include_directories(../../SMILE/schema ../../SMILE/fundamentals)

# add SKIRT library dependencies
target_link_libraries(${TARGET} fitsio voro mpi utils tetgen)
include_directories(../fitsio ../voro ../mpi ../utils)
target_link_libraries(${TARGET} fitsio voro mpi utils tetgen faddeeva)
include_directories(../fitsio ../voro ../mpi ../utils ../faddeeva)
include_directories(SYSTEM ../tetgen) # suppress warnings in tetgen header

# adjust C++ compiler flags to our needs
Expand Down
35 changes: 11 additions & 24 deletions SKIRT/core/LyaUtils.cpp → SKIRT/core/LyUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//// © Astronomical Observatory, Ghent University ////
///////////////////////////////////////////////////////////////// */

#include "LyaUtils.hpp"
#include "LyUtils.hpp"
#include "Configuration.hpp"
#include "Constants.hpp"
#include "Random.hpp"
Expand All @@ -13,32 +13,24 @@

namespace
{
constexpr double c = Constants::c(); // speed of light in vacuum
constexpr double kB = Constants::k(); // Boltzmann constant
constexpr double mp = Constants::Mproton(); // proton mass
constexpr double la = Constants::lambdaLya(); // central Lyman-alpha wavelength
constexpr double Aa = Constants::EinsteinALya(); // Einstein A coefficient for Lyman-alpha transition
constexpr double c = Constants::c(); // speed of light in vacuum
}

////////////////////////////////////////////////////////////////////

double LyaUtils::section(double lambda, double T)
double LyUtils::section(double lambda, double center, double vth, double a, double g)
{
double vth = sqrt(2. * kB / mp * T); // thermal velocity for T
double a = Aa * la / 4. / M_PI / vth; // Voigt parameter
double x = (la - lambda) / lambda * c / vth; // dimensionless frequency
double sigma0 = 3. * la * la * M_2_SQRTPI / 4. * a; // cross section at line center
return sigma0 * VoigtProfile::value(a, x); // cross section at given x
double x = (center - lambda) / lambda * c / vth; // dimensionless frequency
double sigma0 = g * center * center * M_2_SQRTPI / 4. * a; // cross section at line center
return sigma0 * VoigtProfile::value(a, x); // cross section at given x
}

////////////////////////////////////////////////////////////////////

std::pair<Vec, bool> LyaUtils::sampleAtomVelocity(double lambda, double T, double nH, Direction kin,
Configuration* config, Random* random)
Vec LyUtils::sampleAtomVelocity(double lambda, double center, double vth, double a, double T, double nH, Direction kin,
Configuration* config, Random* random)
{
double vth = sqrt(2. * kB / mp * T); // thermal velocity for T
double a = Aa * la / 4. / M_PI / vth; // Voigt parameter
double x = (la - lambda) / lambda * c / vth; // dimensionless frequency
double x = (center - lambda) / lambda * c / vth; // dimensionless frequency

// generate two directions that are orthogonal to each other and to the incoming photon packet direction
Direction k1 = (kin.kx() || kin.ky()) ? Direction(kin.ky(), -kin.kx(), 0., true) : Direction(1., 0., 0., false);
Expand Down Expand Up @@ -79,21 +71,16 @@ std::pair<Vec, bool> LyaUtils::sampleAtomVelocity(double lambda, double T, doubl
// transform the dimensionless frequency into the rest frame of the atom
x -= Vec::dot(u, kin);

// select the isotropic or the dipole phase function:
// all wing events and 1/3 of core events are dipole, and the remaining 2/3 core events are isotropic,
// where x=0.2 (in the atom frame) defines the transition between core and wings
bool dipole = abs(x) > 0.2 || random->uniform() < 1. / 3.;

// scale the atom velocity from dimensionless to regular units
u *= vth;

// return the atom velocity and the phase function choice
return std::make_pair(u, dipole);
return u;
}

////////////////////////////////////////////////////////////////////

double LyaUtils::shiftWavelength(double lambda, const Vec& vatom, const Direction& kin, const Direction& kout)
double LyUtils::shiftWavelength(double lambda, const Vec& vatom, const Direction& kin, const Direction& kout)
{
return lambda / (1 - Vec::dot(kin, vatom) / c) * (1 - Vec::dot(kout, vatom) / c);
}
Expand Down
14 changes: 7 additions & 7 deletions SKIRT/core/LyaUtils.hpp → SKIRT/core/LyUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
//// © Astronomical Observatory, Ghent University ////
///////////////////////////////////////////////////////////////// */

#ifndef LYAUTILS_HPP
#define LYAUTILS_HPP
#ifndef LYUTILS_HPP
#define LYUTILS_HPP

#include "Direction.hpp"
#include "Range.hpp"

class Configuration;
class Random;

Expand Down Expand Up @@ -102,12 +102,12 @@ class Random;
purpose of this recipe, the scattering event is considered to occur in the core if the incoming
dimensionless photon frequency in the rest frame of the interacting atom is smaller than a
critical value, \f$|x|<0.2\f$. */
namespace LyaUtils
namespace LyUtils
{
/** This function returns the Lyman-alpha scattering cross section per hydrogen atom
\f$\sigma_\alpha(\lambda, T)\f$ at the given photon wavelength and gas temperature, using
the definition given in the class header. */
double section(double lambda, double T);
double section(double lambda, double center, double vth, double a, double g);

/** This function draws a random hydrogen atom velocity as seen by an incoming photon from the
appropriate probability distributions, reflecting the preference for photons to be
Expand Down Expand Up @@ -141,8 +141,8 @@ namespace LyaUtils
- Return the atom velocity and a flag indicating the selected phase function.

*/
std::pair<Vec, bool> sampleAtomVelocity(double lambda, double T, double nH, Direction kin, Configuration* config,
Random* random);
Vec sampleAtomVelocity(double lambda, double center, double vth, double a, double T, double nH, Direction kin,
Configuration* config, Random* random);

/** This function returns the Doppler-shifted wavelength in the gas bulk rest frame after a
Lyman-alpha scattering event, given the incoming wavelength in the gas bulk rest frame, the
Expand Down
81 changes: 58 additions & 23 deletions SKIRT/core/LyaNeutralHydrogenGasMix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@
///////////////////////////////////////////////////////////////// */

#include "LyaNeutralHydrogenGasMix.hpp"
#include "Configuration.hpp"
#include "Constants.hpp"
#include "LyaUtils.hpp"
#include "LyUtils.hpp"
#include "MaterialState.hpp"
#include "PhotonPacket.hpp"
#include "Random.hpp"

////////////////////////////////////////////////////////////////////

namespace
{
// the combined Lya1 and Lya2 for hydrogen
constexpr double lyaA = Constants::EinsteinALya();
constexpr double lya = Constants::lambdaLya();
constexpr double g = 3.;
constexpr double kB = Constants::k();
constexpr double mp = Constants::Mproton();
}

////////////////////////////////////////////////////////////////////

void LyaNeutralHydrogenGasMix::setupSelfBefore()
{
MaterialMix::setupSelfBefore();
Expand Down Expand Up @@ -87,6 +98,15 @@ double LyaNeutralHydrogenGasMix::mass() const

////////////////////////////////////////////////////////////////////

double LyaNeutralHydrogenGasMix::section(double lambda, double T) const
{
double vth = sqrt(2. * kB / mp * T);
double a = lyaA * lya / 4. / M_PI / vth;
return LyUtils::section(lambda, lya, vth, a, g);
}

////////////////////////////////////////////////////////////////////

double LyaNeutralHydrogenGasMix::sectionAbs(double /*lambda*/) const
{
return 0.;
Expand All @@ -96,14 +116,14 @@ double LyaNeutralHydrogenGasMix::sectionAbs(double /*lambda*/) const

double LyaNeutralHydrogenGasMix::sectionSca(double lambda) const
{
return LyaUtils::section(lambda, defaultTemperature());
return section(lambda, defaultTemperature());
}

////////////////////////////////////////////////////////////////////

double LyaNeutralHydrogenGasMix::sectionExt(double lambda) const
{
return LyaUtils::section(lambda, defaultTemperature());
return section(lambda, defaultTemperature());
}

////////////////////////////////////////////////////////////////////
Expand All @@ -119,31 +139,52 @@ double LyaNeutralHydrogenGasMix::opacityAbs(double /*lambda*/, const MaterialSta
double LyaNeutralHydrogenGasMix::opacitySca(double lambda, const MaterialState* state, const PhotonPacket* /*pp*/) const
{
double n = state->numberDensity();
return n > 0. ? n * LyaUtils::section(lambda, state->temperature()) : 0.;
return n > 0. ? n * section(lambda, state->temperature()) : 0.;
}

////////////////////////////////////////////////////////////////////

double LyaNeutralHydrogenGasMix::opacityExt(double lambda, const MaterialState* state, const PhotonPacket* /*pp*/) const
{
double n = state->numberDensity();
return n > 0. ? n * LyaUtils::section(lambda, state->temperature()) : 0.;
return n > 0. ? n * section(lambda, state->temperature()) : 0.;
}

////////////////////////////////////////////////////////////////////

bool LyaNeutralHydrogenGasMix::peeloffScattering(double& I, double& Q, double& U, double& V, double& lambda,
Direction bfkobs, Direction bfky, const MaterialState* state,
const PhotonPacket* pp) const
void LyaNeutralHydrogenGasMix::setScatteringInfoIfNeeded(PhotonPacket* pp, const MaterialState* state,
const double lambda) const
{
// draw a random atom velocity & phase function, unless a previous peel-off stored this already
auto scatinfo = const_cast<PhotonPacket*>(pp)->getScatteringInfo();
auto scatinfo = pp->getScatteringInfo();
if (!scatinfo->valid)
{
scatinfo->valid = true;
std::tie(scatinfo->velocity, scatinfo->dipole) = LyaUtils::sampleAtomVelocity(
lambda, state->temperature(), state->numberDensity(), pp->direction(), config(), random());

double T = state->temperature();
double nH = state->numberDensity();

double vth = sqrt(2. * kB / mp * T);
double a = lyaA * lya / 4. / M_PI / vth;
double x = (lya - lambda) / lambda * Constants::c() / vth;

// select the isotropic or the dipole phase function:
// all wing events and 1/3 of core events are dipole, and the remaining 2/3 core events are isotropic,
// where x=0.2 (in the atom frame) defines the transition between core and wings
scatinfo->dipole = abs(x) > 0.2 || random()->uniform() < 1. / 3.;

scatinfo->velocity =
LyUtils::sampleAtomVelocity(lambda, lya, vth, a, T, nH, pp->direction(), config(), random());
}
}

////////////////////////////////////////////////////////////////////

bool LyaNeutralHydrogenGasMix::peeloffScattering(double& I, double& Q, double& U, double& V, double& lambda,
Direction bfkobs, Direction bfky, const MaterialState* state,
const PhotonPacket* pp) const
{
setScatteringInfoIfNeeded(const_cast<PhotonPacket*>(pp), state, lambda);
auto scatinfo = const_cast<PhotonPacket*>(pp)->getScatteringInfo();

// add the contribution to the Stokes vector components depending on scattering type
if (scatinfo->dipole)
Expand All @@ -158,7 +199,7 @@ bool LyaNeutralHydrogenGasMix::peeloffScattering(double& I, double& Q, double& U
}

// Doppler-shift the photon packet wavelength into and out of the atom frame
lambda = LyaUtils::shiftWavelength(lambda, scatinfo->velocity, pp->direction(), bfkobs);
lambda = LyUtils::shiftWavelength(lambda, scatinfo->velocity, pp->direction(), bfkobs);

return false;
}
Expand All @@ -167,14 +208,8 @@ bool LyaNeutralHydrogenGasMix::peeloffScattering(double& I, double& Q, double& U

void LyaNeutralHydrogenGasMix::performScattering(double lambda, const MaterialState* state, PhotonPacket* pp) const
{
// draw a random atom velocity & phase function, unless a peel-off stored this already
auto scatinfo = pp->getScatteringInfo();
if (!scatinfo->valid)
{
scatinfo->valid = true;
std::tie(scatinfo->velocity, scatinfo->dipole) = LyaUtils::sampleAtomVelocity(
lambda, state->temperature(), state->numberDensity(), pp->direction(), config(), random());
}
setScatteringInfoIfNeeded(const_cast<PhotonPacket*>(pp), state, lambda);
auto scatinfo = const_cast<PhotonPacket*>(pp)->getScatteringInfo();

// draw the outgoing direction from the dipole or the isotropic phase function
// and, if required, update the polarization state of the photon packet
Expand All @@ -190,7 +225,7 @@ void LyaNeutralHydrogenGasMix::performScattering(double lambda, const MaterialSt
}

// Doppler-shift the photon packet wavelength into and out of the atom frame
lambda = LyaUtils::shiftWavelength(lambda, scatinfo->velocity, pp->direction(), bfknew);
lambda = LyUtils::shiftWavelength(lambda, scatinfo->velocity, pp->direction(), bfknew);

// execute the scattering event in the photon packet
pp->scatter(bfknew, state->bulkVelocity(), lambda);
Expand Down
24 changes: 19 additions & 5 deletions SKIRT/core/LyaNeutralHydrogenGasMix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

////////////////////////////////////////////////////////////////////

/** The LyaNeutralHydrogenGasMix class describes the material properties related to
Lyman-alpha line transfer for a population of neutral hydrogen atoms, including support for
polarization by scattering.
/** The LyaNeutralHydrogenGasMix class describes the material properties related to Lyman-alpha
line transfer for a population of neutral hydrogen atoms, including support for polarization by
scattering.

The spatial distributions for both the mass density and the temperature of the neutral hydrogen
gas must be defined by the input model and are considered to be constant during the simulation.
Expand Down Expand Up @@ -89,8 +89,8 @@ class LyaNeutralHydrogenGasMix : public MaterialMix
density, so this function returns a list containing these two items. */
vector<StateVariable> specificStateVariableInfo() const override;

/** This function initializes the specific state variables requested by this fragmented dust
mix through the specificStateVariableInfo() function except for the number density. For the
/** This function initializes the specific state variables requested by this material mix
through the specificStateVariableInfo() function except for the number density. For the
Lyman-alpha material mix, the function initializes the temperature to the specified
imported temperature, or if this is not available, to the user-configured default
temperature for this material mix. The metallicity and custom parameter arguments are
Expand All @@ -100,6 +100,11 @@ class LyaNeutralHydrogenGasMix : public MaterialMix

//======== Low-level material properties =======

private:
/** This private function calculates the cross section per hydrogen atom for the given
wavelength and temperature. */
double section(double lambda, double T) const;

public:
/** This function returns the mass of neutral hydrogen atom. */
double mass() const override;
Expand Down Expand Up @@ -136,6 +141,15 @@ class LyaNeutralHydrogenGasMix : public MaterialMix
Lyman-alpha material mix, the extinction opacity equals the scattering opacity. */
double opacityExt(double lambda, const MaterialState* state, const PhotonPacket* pp) const override;

private:
/** This private function draws an atom velocity and phase function and stores this information
in the photon packet's scattering information record, unless a previous peel-off stored
this already. The atom velocity sampling is delegated to the LyUtils::sampleAtomVelocity.
The phase function is chosen to be a dipole for all wing events (x > 0.2) and 1/3 of the
core events (x < 0.2). The remaining core events are isotropic. */
void setScatteringInfoIfNeeded(PhotonPacket* pp, const MaterialState* state, const double lambda) const;

public:
/** This function calculates the contribution of the medium component associated with this
material mix to the peel-off photon luminosity, polarization state, and wavelength shift,
for the given wavelength, geometry, material state, and photon properties. The
Expand Down
1 change: 0 additions & 1 deletion SKIRT/core/MediumSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include "FatalError.hpp"
#include "LockFree.hpp"
#include "Log.hpp"
#include "LyaUtils.hpp"
#include "MaterialMix.hpp"
#include "MaterialState.hpp"
#include "NR.hpp"
Expand Down
1 change: 0 additions & 1 deletion SKIRT/core/SelectDustMixFamily.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
///////////////////////////////////////////////////////////////// */

#include "SelectDustMixFamily.hpp"
#include "Constants.hpp"

////////////////////////////////////////////////////////////////////

Expand Down
2 changes: 2 additions & 0 deletions SKIRT/core/SimulationItemRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
#include "VoronoiMeshSpatialGrid.hpp"
#include "WeingartnerDraineDustMix.hpp"
#include "XRayAtomicGasMix.hpp"
#include "XRayIonicGasMix.hpp"
#include "ZubkoDustMix.hpp"
#include "ZubkoGraphiteGrainSizeDistribution.hpp"
#include "ZubkoPAHGrainSizeDistribution.hpp"
Expand Down Expand Up @@ -597,6 +598,7 @@ SimulationItemRegistry::SimulationItemRegistry(string version, string format)
ItemRegistry::add<SpinFlipAbsorptionMix>();
ItemRegistry::add<SpinFlipHydrogenGasMix>();
ItemRegistry::add<XRayAtomicGasMix>();
ItemRegistry::add<XRayIonicGasMix>();
ItemRegistry::add<EmittingGasMix>();
ItemRegistry::add<NonLTELineGasMix>();
ItemRegistry::add<LyaNeutralHydrogenGasMix>();
Expand Down
Loading
Loading