Python Interface¶
Galacticus exposes a subset of its functionality through a shared library, libgalacticus.so, together with a generated Python module, galacticus.py, that provides a ctypes-based interface to that library. This chapter describes how to build and install the library, how to use it from Python, and how to diagnose common problems.
Building and Installing the Python Library¶
Prerequisites¶
Building the Python library requires the same toolchain used to build the main Galacticus executable (a Fortran 95 compiler, a C compiler, and the GNU Make build system) plus the following additional requirements:
Python 3 (\(\geq\) 3.9), together with the Python packages used by the build-time preprocessing and code-generation scripts (such as
scripts/build/preprocess.pyandscripts/build/buildCode.py). These are declared in the top-levelpyproject.tomland installed in one step from the repository root with:pip install -e .
This editable install places the modules under
python/on the Python import path and installsnumpy,scipy,h5py,lxml,matplotlib, and the other declared dependencies. Alternatively, exportingPYTHONPATH=$(pwd)/pythonbefore invokingmakeis sufficient for the build itself (the third-party dependencies must then be available in the active Python environment by some other means).
No additional Python packages (e.g., numpy) are required merely to use the library—the generated module relies only on the Python standard library’s ctypes module.
Build¶
The shared library and its Python interface module are built by invoking make with the GALACTICUS_BUILD_OPTION=lib flag and specifying the libgalacticus.so target:
make GALACTICUS_BUILD_OPTION=lib libgalacticus.so
This build step performs the following actions:
The Python script
scripts/build/libraryInterfaces.pyis run. It reads the list of classes to be exposed fromsource/libraryClasses.xml, generates C-binding Fortran wrapper code in${BUILDPATH}/libgalacticus/, and writes the Python interface module togalacticus.pyin the Galacticus root directory.The wrapper code is preprocessed and compiled.
Everything is linked into the shared library
libgalacticus.soin the Galacticus root directory.
Both galacticus.py and libgalacticus.so are placed directly in the Galacticus root directory immediately after the build completes.
Installation¶
After building, the two artifacts must be placed in a directory layout that galacticus.py expects. The standard layout used by the Galacticus release bundles is:
<prefix>/
galacticus/
lib/
libgalacticus.so
<runtime dependency .so files>
python/
galacticus.py
To create this layout from the build directory, run:
mkdir -p galacticus/lib galacticus/python
mv galacticus.py galacticus/python/
mv libgalacticus.so galacticus/lib/
Making the Module Importable¶
Python must be able to find galacticus.py. Set the PYTHONPATH environment variable to the galacticus/python directory before running any Python scripts that import the module:
export PYTHONPATH=/path/to/prefix/galacticus/python:$PYTHONPATH
where /path/to/prefix is the directory that contains the galacticus/ tree created in Section Installation.
The module also requires that galacticus/lib/libgalacticus.so can be found relative to the current working directory at import time. Therefore, Python scripts using the library should be run from /path/to/prefix:
cd /path/to/prefix
python3 myScript.py
Using the Python Interface¶
Importing the Module¶
Once the prerequisites in Section Making the Module Importable are satisfied, the module is imported with a standard Python import:
import galacticus
Importing the module automatically loads libgalacticus.so via ctypes and calls the internal Galacticus initialisation routine libGalacticusInitL, which sets up event hooks and the HDF5 access lock.
Class Mapping¶
For each Galacticus functionClass listed in source/libraryClasses.xml, the generated module contains:
An abstract base class bearing the functionClass name (e.g.
galacticus.cosmologyParameters).One concrete subclass for each concrete implementation that is not explicitly excluded in
libraryClasses.xml(e.g.galacticus.cosmologyParametersSimple).
Concrete classes are instantiated by calling their constructor with the same arguments that the corresponding Galacticus Fortran constructor expects, converted to their Python equivalents (floats for double-precision reals, booleans for logicals, integers for integers, and other class instances for object arguments). Methods exposed on the Fortran side are available as Python methods of the same name.
The set of currently exposed functionClasses is defined in source/libraryClasses.xml and includes, among others: cosmologyParameters, cosmologyFunctions, powerSpectrumPrimordial, transferFunction, linearGrowth, cosmologicalMassVariance, criticalOverdensity, virialDensityContrast, and haloMassFunction.
Basic Example¶
The following example constructs a simple flat \(\Lambda\)CDM cosmology and evaluates some basic quantities:
import galacticus
# Cosmological parameters: OmegaMatter, OmegaBaryon, OmegaDarkEnergy,
# temperatureCMB, HubbleConstant
cospar = galacticus.cosmologyParametersSimple(0.3, 0.045, 0.7, 2.78, 70.0)
print("Omega_Matter =", cospar.OmegaMatter())
print("Hubble constant =", cospar.HubbleConstant(), "km/s/Mpc")
# Cosmological functions
cosfun = galacticus.cosmologyFunctionsMatterLambda(cospar)
t0 = cosfun.cosmicTime(1.0)
print("Age of the universe =", t0, "Gyr")
print("Expansion factor at t =", t0, "Gyr:", cosfun.expansionFactor(t0))
Typical Workflows¶
A common use-case is to build up a chain of Galacticus objects that mirrors the parameter-file object hierarchy. For example, to compute the halo mass function:
import galacticus
cospar = galacticus.cosmologyParametersSimple(0.3, 0.045, 0.7, 2.78, 70.0)
cosfun = galacticus.cosmologyFunctionsMatterLambda(cospar)
dmpart = galacticus.darkMatterParticleCDM()
transfn = galacticus.transferFunctionCAMB(
dmpart, cospar, cosfun, 0.0, 0)
lingrow = galacticus.linearGrowthCollisionlessMatter(cospar, cosfun)
psprim = galacticus.powerSpectrumPrimordialPowerLaw(
0.965, 0.0, 0.0, 1.0, False)
pswin = galacticus.powerSpectrumWindowFunctionTopHat(cospar)
pstran = galacticus.powerSpectrumPrimordialTransferredSimple(
psprim, transfn, lingrow)
cmv = galacticus.cosmologicalMassVarianceFilteredPower(
sigma8=0.8, tolerance=1.0e-4, toleranceTopHat=1.0e-4,
nonMonotonicIsFatal=True, monotonicInterpolation=False,
truncateAtParticleHorizon=False,
cosmologyParameters_=cospar, cosmologyFunctions_=cosfun,
linearGrowth_=lingrow,
powerSpectrumPrimordialTransferred_=pstran,
powerSpectrumWindowFunction_=pswin)
crover = galacticus.criticalOverdensitySphericalCollapseClsnlssMttrCsmlgclCnstnt(
lingrow, cosfun, cmv, dmpart, True)
hmf = galacticus.haloMassFunctionShethTormen(
cospar, cmv, crover, 0.707, 0.3, 0.322183)
m = 1.0e12 # Solar masses
print("dn/dln(M) at M=1e12 Msun, z=0:", hmf.differential(13.8, m) * m)
Notes and Limitations¶
Object lifetimes: Galacticus objects created via the Python interface are owned by the Python garbage collector. Destructors are called automatically when objects go out of scope, but care should be taken not to hold references to a child object longer than its parent—for example, do not use a
cosmologyFunctionsobject after thecosmologyParametersobject it was constructed from has been garbage-collected.Limited class coverage: Only the functionClasses listed in
source/libraryClasses.xmlare exposed. Classes that rely on unlimited polymorphic constructor arguments, or that have been explicitly excluded, may not be available.No direct HDF5 I/O: The Python interface does not currently support reading or writing Galacticus output files; use
h5pyor similar tools for that purpose.Single Galacticus instance: Only one
libgalacticus.soinstance can be loaded per Python process, because the library maintains global Fortran state.
Troubleshooting¶
ModuleNotFoundError: No module named 'galacticus'¶
Python cannot locate galacticus.py. Ensure that PYTHONPATH includes the directory containing galacticus.py (i.e. galacticus/python/ relative to the install prefix):
export PYTHONPATH=/path/to/prefix/galacticus/python:$PYTHONPATH
Then verify:
python3 -c "import galacticus; print('OK')"
Missing Runtime Dependencies¶
If libgalacticus.so loads but then immediately raises an OSError about a missing .so file (e.g. libgfortran.so.5: cannot open shared object file), the runtime library dependencies were not copied during packaging. Re-run the packaging step described in Section Installation, making sure that all non-system libraries are included. To inspect what is needed:
ldd /path/to/prefix/galacticus/lib/libgalacticus.so
Any library listed as not found must be located and copied into galacticus/lib/.
Alternatively, set LD_LIBRARY_PATH to include the directory containing the missing library. For example, on a system with GCC 12 installed on an x86_64 architecture:
export LD_LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12:$LD_LIBRARY_PATH
Replace 12 with your installed GCC version (e.g. 11, 13) and adjust the architecture component (e.g. aarch64-linux-gnu) to match your system.
Wrong Python Version or ABI Mismatch¶
The galacticus.py module is a pure Python 3 script that uses only the standard library; it has no compiled extension module and therefore no Python ABI requirement of its own. However, if the Fortran runtime linked into libgalacticus.so was built against a different version of libgfortran than the one on the system, you may see symbol errors at load time. In that case, ensure that the Fortran compiler version used to build libgalacticus.so matches what is installed. Inspect the required libgfortran version with:
objdump -p /path/to/prefix/galacticus/lib/libgalacticus.so \
| grep GFORTRAN
Runtime Errors: Symbol Not Found¶
An AttributeError or OSError of the form symbol not found when calling a method usually means the libgalacticus.so on disk is older than galacticus.py, or vice versa (e.g. after a partial rebuild). Rebuild the library completely and re-install both galacticus.py and libgalacticus.so together:
make GALACTICUS_BUILD_OPTION=lib libgalacticus.so
mv galacticus.py /path/to/prefix/galacticus/python/
mv libgalacticus.so /path/to/prefix/galacticus/lib/
Always keep galacticus.py and libgalacticus.so in sync; they are generated together and must be deployed together.
Segmentation Fault or Fortran Initialization Error¶
If Python crashes with a segmentation fault immediately after import galacticus, the most likely cause is that the Fortran runtime was not properly initialized. This can happen if libgalacticus.so is loaded more than once in the same process, or if the GALACTICUS_EXEC_PATH and GALACTICUS_DATA_PATH environment variables are not set. Set these before running:
export GALACTICUS_EXEC_PATH=/path/to/galacticus/source
export GALACTICUS_DATA_PATH=/path/to/galacticus/datasets