Merge remote-tracking branch 'upstream/main' into feature/mixedPolytrope

# Conflicts:
#	.gitignore
#	build-config/meson.build
#	meson.build
#	meson_options.txt
#	src/composition/public/composition.h
#	src/config/public/config.h
#	src/constants/public/const.h
#	src/meson.build
#	tests/composition_sandbox/comp.cpp
This commit is contained in:
2025-06-11 15:05:11 -04:00
37 changed files with 839 additions and 39 deletions

254
Readme.md
View File

@@ -1,20 +1,94 @@
# New implimentation of 3+1D SSE
New (as yet unnamed) 4DSSE code.
We need an exciting name.
<p style="display: flex; justify-content: center;">
<img src="assets/logo/serifLogo.png" width="300" alt="Logo">
</p>
This code is very early in development and should not be used for scientific purposes yet.
## Stellar Evolution and Rotation in Four dimensions.
> ⚠️ This code is very early in development and should not be used for scientific purposes yet.
----
## Introduction
SERiF is a stellar structure and evolution program written in C++. This README will eventually provide guidance on how end users (i.e. non SERiF developers) can use the code. However, due
to the early stage of development we are in, this README is currently intended only for developers. Its purpose is to provide an overview of the build system, development philosphy, development process,
and current state of the code. Further, general information tasks which need doing will also be included (though for more detailed information on this please refer to the issue tracker or the
4DSSE project board).
## Building
In order to build you will need meson installed on your system. The easiest way to do this is to use the python package manager (pip)
SERiF uses meson as its build system. It may be useful to understand why we selected meson before we dive into
detailed build instructions. The headline for "why meson" is ease of use for us. A primary goal of SERiF is that it
should be easier to use than many current generation SSE code bases. This effectively means that we are looking for a "1
click" install process. Meson does not, out of the box, provide this. However, it does make it easier for us to build
such an installation system around it.
In general all meson projects are built in a similar manner. The challenge, for end users, is often installation and linking
of dependencies. Let us therefore first look at what depdendencies SERiF has.
### Dependencies
There are only a small number of dependencies which must be installed by the user
1. pip
2. clang/gcc (clang >= 16, gcc >= 13)
3. ninja
4. meson
Further, if you use the `mk` script `ninja` and `meson` can even be installed for you!
There are a number of dependencies which are automatically installed by our build system. These are all stored in the `subprojects`
1. MFEM - MFEM is a finite element modeling library developed primarily by a team at Lawrence Livermore National Labs. MFEM is used as our primary solver.
2. opat-core - opat-core is a library for I/O operations related to the opat file format. All tables used by SERiF are in the opat-format. opat-core is maintained by the 4D-STAR collaboration and is very well intergrated with a meson build system.
3. boost - boost is a C++ library which provides a number of useful utilities. It is used in SERiF for a number of tasks including file I/O and string manipulation. We use the ode solver from boost. Boost is complex to install and is a potential pain point. We will address this in more detail below.
4. pybind11 - pybind11 is a library which allows us to create python bindings for C++ code. This is used to allow us to use SERiF in python.
5. quill - quill is a C++ logging library. It is used to provide very fast logging functionality in SERiF.
6. yaml-cpp - yaml-cpp is a C++ library for parsing YAML files. It is used to parse the configuration files used by SERiF.
All of these could be installed using either the system package manager, built from source, or simply manually included (in the case of header only libraries). However, we have chosen to make extensive use of the `wrap` system which meson provides to automatically fetch and build as many dependencies as we can. This *significantly* simplifies the build process for end users. __Great care should be taken when adding any new dependencies to SERiF as we must maintain this ease of use.__
##### Boost and other complex dependencies
Certain dependencies are either too complex or expensive to reasonably build with SERiF, therefore these must be
installed system-wide. It is perfectly acceptable that a user might have these installed already; however, we do still
want to maintain this same "one click setup" where users do not need to think about dependencies. Therefore, we also
include a series of scripts which will automatically detect the system the user is on and install dependencies, such as
boost, for them. Meson is then capable of detecting and using these system installations.
### Compiling
For the moment let us assume that all "system" dependencies have been installed (i.e. boost. One can check if a
dependency is a system dependency or not by looking for a .wrap file in the subprojects directory. If a .wrap file
exists then meson will automatically handle this dependency and nothing need be done). In that case, we can use the
standard meson build commands.
```bash
pip install meson
meson setup build
meson compile -C build
meson test -C build
```
You can then either use the mk script or meson commands automatically. When running either the script or meson commands manually `MFEM` will be pulled from github and built. As part of this a small patch will be applied to the MFEM `CMakeLists.txt` file. This process should only need to happen once as future builds will use the cached version of MFEM in `subprojects` and the cached build files of `MFEM` in `build`.
meson uses an out of source build system so all build artifacts will be in the `build` directory. The first
command above sets up the build directory and configures the build itself (if you are familar with cmake this is
similar to running the cmake command). The second command compiles the code, using the `ninja` backend by default.
The third command runs the tests. Note that this will only run the tests which are in the `tests` directory.
If you wish to run a test manually (i.e. without the meson test command) you must set the `MESON_SOURCE_ROOT`
environment variable to the root directory of SERiF. Then you can navigate to `SERiF/build/tests/<module_name>/<test_name>`
and run the test as you would any executable.
Note that this will *automatically* build all dependencies defined in the meson wrap system. This is a key feature of
meson and is one of the reasons we selected it as our build system.
### Building with the mk script
We also provide a `mk` script which is a wrapper around both the meson build system and the automatic dependency installation
scripts we write. This is intended to be a "one click" install system. All the automatic installation scripts are stored
in `SERiF/build-config/<dependency_name>`. You can then use the `mk` script to build SERiF. This will automatically check
for all dependencies, try to install them if not found, fail over safely if they are not found, and then build the code
if all dependencies are found. __The `mk` script is intended to be the method with end users will use to build SERiF.__
To use `mk` it is as simple as running `mk` in a bash compatible shell (currently tested on `bash` and `zsh`)
```bash
./mk
```
if you want to build with no test suite run
if you want to build with no test suite run. Note that this will neither run nor build the tests (meaning you will not be
able to run them manually).
```bash
./mk --noTest
```
@@ -38,5 +112,167 @@ meson test -C build
## Test Configuration
Some tests use config variables setup in tests/testsConfig.yaml. Specifically for things like the GLVis host and port. You should configre those to point to whatever host you are running GLVis on.
## Development Philosophy
A few core philosophies of SERiF development are
1. Modularity
2. Modernity
3. Testability
4. Usability
5. Documentation
To briefly summarize the importance of each of these
### Modularity
1. All code is organized into modules. We aim to maintain the minimum number of modules possible while still enforcing
that each module has a well-defined job. This is obviously somewhat nebulous so look at the current modules which
exist to get a sense for how we distribute things. When adding a feature to the code first think carefully if it can
or should exist in an already extant module.
2. Within a module all code should maintain a very well documented and robust public interface. This can and will change
dramatically during development but keep in mind that eventually we will likely want to "lock" our public interface
so try to design it with that in mind.
3. Careful thought should be given to what *kind* of module is being added. That is to say is it a: physics module,
utility module, I/O module, infrastructure module, or something else. This will determine how and when this module is
build during compilation. For example all physics modules should be built after all utility modules.
### Modernity
1. We use C++23 as the target language standard. This is a very intentional choice, and it means that code commited should
not be written in older C++ standards where possible. Certainly C++98 code should be avoided.
2. Some effects of this are that, for example, we do not allow raw pointer allocation using `new` or `malloc`. C++
provides very powerful tools which are easy to use and dramatically increase memory saftey (such as
`std::unique_ptr<Type>()` or `std::shared_ptr<T>()`). If you *ever* find yourself wanting to allocate a raw pointer
on the heap consider smart pointers instead.
3. All of that being said, we do interface with external libraries which will sometimes return raw, C style pointers. In
those cases, of course you need to do what needs be done. Just make sure to document things carefully and note in a
comment that there is an enhanced possibility of a memory leak at those locations.
### Testability
1. It is unrealistic, and likely unproductive, to expect fully test driven development or 100% code coverage. However,
we do want to maintain a high degree of testability. As such the recommended development approach is to develop
against tests first. That is to say, instead of a single entry point binary, we have a testing module for each
module. We run the code for each module through that testing module. Eventually, there will be entry points for
users. However, that is very far down the line.
2. While we do not enforce any specific amount of code coverage we do require that all modules have some degree of
testing that a reasonable astronomer would look at and say "that looks well tested". This is a very subjective measure,
but we will be using this as a guideline for code reviews.
3. Testing for all C++ code is done using gtest. Make sure to be cognizant that floating point values __will not__ be
the same from machine to machine and therefore using `EXPECT_EQ` for floating point values will likely result in test
failures. gtest provides tools for just this, use them.
### Usability & Documentation
1. SERiF is intended to be used by astronomers, not astronomers who are also developers. When developing new code or
refactoring existing code keep this in mind. This means that public interface methods and functions should have
*very* clear names (note that very clear __does not mean long__, it means clear). Every public method and function
should include a doxygen comment which describes, at minimum, its purpose, inputs, and outputs. Ideally any
exceptions which can be thrown should also be documented. Further, we *strongly* reccomend that every time a doxygen
comment is written an example for that method, function or class is also written. This will give us a clear example
set from the outset.
2. There are times when complex code is required. In these cases that code should likely be private to the module.
3. All modules will eventually be tied into the python interface. Write the public module with that in mind.
## Current Status
Currently we are working on implimenting modules such as opacity, equation of state, polytrope, and meshing. Builds may not work on any branches at any time.
There are two primary modules currently under construction, many which need to be worked on, and a few which have either
been finished or are at least at their minimum viable state (MVS). Modules where the current assigne is marked as N/A
are currently not being worked on. This does not mean that they do not need work or will not be worked on in the future.
But if a new developer wants somewhere to start, those are good places too.
#### Name Key
- A.D. : Aaron Dotter
- E.B. : Emily Boudreaux
| Module Name | Status | Description | Current Assigne |
|-------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| config | Complete | This module handles all configuration files. It is responsible for parsing the YAML files | N/A |
| composition | MVS | This module tracks a general composition object, allowing for arbitrary species tracking and mixing | N/A |
| const | Complete | This module contains all physical constants from the CODATA 2022 data release as well as some astronomical constants from IAU 2015 | N/A |
| eos | MVS | This module implements both a general interface for equation of states as well as specific equations of states. Currently the helmholtz equation of state is the only one implemented. | N/A |
| meshIO | Complete | This module handles all I/O for the mesh. It is responsible for reading and writing the mesh to disk. This is used to interface with the resource manager which allows for easer configuration on the user end. | N/A |
| misc | WIP | This is a catch all module for misc things. Generally we should *avoid* putting stuff in here but sometimes (such as for debugging macros) it is useful. | N/A |
| network | MVS | This module handles the nuclear network and burning calculations. Currently only Frank Timmes' Approx8 network is implemented. It also implements a general interface for nuclear networks so that other networks can be added. | N/A |
| opac | WIP | This module handles opacity calculation / interpolation. | A.D. |
| poly | WIP | This module computes polytropic models which are used as initial states for solving the structure equations. | E.B. |
| probe | Complete | This module implements the `probe` namespace which is used to hold functions for probing the current state of the code (stuff like `whydt` in `MESA`) | N/A |
| python | WIP | This module contains all code relevant to the python interface. All interface code is then organized in submodules within this (such as python/config) | E.B. |
| resource | Complete | This module handles loading resource from disk in a clean fashion. The key justification here is to avoid users having to explicitly set environmental variables but also to make loading of resources any where in the code easer to handle | N/A |
| types | Complete | This module implements custom datatypes for SERiF which do not cleanly fall into any other module (i.e. __datatypes should not__ go in `misc`) | N/A |
### Future Work
This is a non-comprehensive list of things which still need to be done (and which are not being actively developed). If you pick up one of these projects go ahead and edit the README to mark these checkboxes as marked ([x])
- [ ] Extended nuclear reaction network
- [ ] Extend the equation of state module
- [ ] Atmospheric boundary conditions
- [ ] Mixing length
- [ ] Magnetic fields
- [ ] Rotation
- [ ] Structure equations
- [ ] Time stepping
- [ ] Curvilinear finite elements
- [ ] All sorts of details that I cannot even begin to enumerate at this stage
When thinking about picking up new modules it is important to think first about: one, what that work would depend on,
and two, what work dependency on that. For example there would be very little point in working on time stepping until
there is a stable structure solver. Whereas extending the nuclear network and equation of state can be extended
independent of the primary solver (as is the case with the microphysics in general). If you want to start work and
need some ideas about where to start reach out to Aaron or Emily.
## Developing
There is a detailed development document which all 4D-STAR collaboration members should have access to. First
familiarize yourself with that. I will note that we do not treat that as a 100% hard and fast rule. However, we do try
to stick to that as a general rule. I will summarize some best practices which I find particularly helpful (outside of
the actual source code itself)
#### git
All development should be done on your own fork of the repository. You can organize these forks in whatever way you
want, they are your fork after all. However, I find it helpful to organize my branches into a few categories
- [feature/\<feature name\>] - This is a new feature which I am working on. (i.e. `git checkout -b feature/python/eos_interface`)
- [bugfix/\<bug name\>] - This is a bug which I am working on. (i.e. `git checkout -b bugfix/python/eos_interface_memory_overflow`)
- [perf/\<perf name\>] - This is a performance improvement which I am working on. (i.e. `git checkout -b perf/python/eos_interface_cache`)
What I like about this is that it effectively creates a tree structure for my branches. However, as said before, on your own fork
use any organizational scheme you find effective for yourself.
When you have finished writing code, and you have tested it open a pull request (PR) to the main branch for SERiF in the
4D-STAR organization. There is a template present for this which should automatically populate if you use the GitHub web interface.
Not every PR will need to fill content in every field of the template, use your own judgment for this. Further, most PRs will,
for sake of time, likely not be reviewed (major physics modules will be); however, the first PR by any new developer will
need to be reviewed by one of the current experienced developers (Currently Aaron and Emily, though if and when new
developers come on board with this part of the project that list will hopefully grow).
#### Code Style
Again for this primarily follow the style guide in the developer assets repository (owned by the 4D-STAR organization). In
general though use clear and concise names for variables, functions, methods and classes. Adhear to consistent naming
conventions (preferably also consistent with what is already in the code).
Note that we take an Object-Oriented Programming (OOP) approach to much of the code, but this is a pragmatic not a
dogmatic choice. That is to say that OOP is sometimes a good tool for a job and other times it is not. Because of this
we do have a good number of objects floating around but you, as a developer, should not feel as if you need to use an
OOP design if you do not think it would be best. One thing that we will enforce is that overly complicated inheritance
hierarchies are not allowed. If you find yourself writing a class which has more than 2 levels of inheritance then you
should probably consider using composition instead.
#### Environment
The current team builds with both `clang` and `gcc`, we *will* maintain compatibility with both of these compilers. As such
when developing new code you should, __before opening a PR__, test your compilation with gcc and clang. Meson makes this
super easy as you can simply have two build directories (i.e. `build-gcc` and `build-clang`) and run the same commands in both.
```bash
CXX=g++ meson setup build-gcc
CXX=clang++ meson setup build-clang
```
Aside from this I find it useful to have the `MESON_SOURCE_ROOT` environmental variable set since this makes it easy to
run individual executables. Therefore in your shell profile file (`~/.bashrc`, `~/.zshrc`, etc.) add the following line
```bash
export MESON_SOURCE_ROOT=/path/to/SERiF
```
#### Other
Should you have any questions not answered here or in the development guidelines please feel free to reach out to Emily.

BIN
assets/logo/serifLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

100
assets/logo/serifLogo.svg Normal file
View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="500mm"
height="300mm"
viewBox="0 0 500 300"
version="1.1"
id="svg1"
sodipodi:docname="serifLogo.svg"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
inkscape:export-filename="serifLogo.png"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.31892609"
inkscape:cx="801.12605"
inkscape:cy="660.02753"
inkscape:window-width="1728"
inkscape:window-height="962"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="0"
inkscape:current-layer="layer1">
<inkscape:page
x="0"
y="0"
width="500"
height="300"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#4c7cbc;stroke:#000000;stroke-width:0.263773"
d="m 390.37455,205.22986 h 35.41653 l 24.81413,-56.39575 h 45.34218 l 12.18148,-28.19787 h -80.08196 z"
transform="scale(0.93412862,1.0705164)"
id="path7" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#3d907f;stroke:#000000;stroke-width:0.263773;fill-opacity:1"
d="m 437.29581,99.205852 h 80.53313 l 12.18148,-28.197874 h -80.08196 z"
transform="scale(0.93412862,1.0705164)"
id="path6" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#408496;stroke:#000000;stroke-width:0.263773;fill-opacity:1"
d="m 394.09472,72.246105 -11.02484,25.386289 25.07976,0.08255 10.85491,-25.534574 z"
transform="scale(0.93412862,1.0705164)"
id="path5" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#4c7cbc;stroke:#000000;stroke-width:0.263773"
d="m 336.23478,205.22986 h 25.71646 l 36.10165,-84.0768 h -24.84286 z"
transform="scale(0.93412862,1.0705164)"
id="path4" />
<path
id="path3"
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#4c7cbc;stroke:#000000;stroke-width:0.263773"
d="m 363.96719,101.97552 -35.27484,0.004 c -1.8739,4.3543 -3.83937,8.67994 -5.7669,13.01714 -2.48141,3.83491 -5.41412,5.63967 -9.24903,5.63967 H 260.4388 l -37.67213,84.59358 h 35.41617 l 24.81455,-56.39572 h 16.46724 c 1.57908,0.45117 2.93248,1.12801 3.83481,2.03034 0.90234,0.90233 1.3537,2.03006 1.3537,3.15798 l -0.45087,2.03034 -22.33287,49.17706 h 35.19101 l 17.82094,-39.02585 c 1.12792,-2.03025 1.57884,-4.2859 1.57884,-6.76731 0,-2.48141 -0.67633,-4.73755 -2.25541,-6.7678 -1.57908,-1.80466 -2.93274,-3.15801 -4.28623,-3.83476 1.57908,0 3.38366,-0.45123 5.86507,-1.35356 2.48142,-0.90233 3.83494,-1.35341 4.06052,-1.57899 4.06049,-2.25583 6.99295,-5.63955 8.79762,-10.15121 z"
transform="scale(0.93412862,1.0705164)"
sodipodi:nodetypes="cccscccccscccccsccsccc" />
<path
id="path16"
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#947abe;stroke:#000000;stroke-width:0.263773;fill-opacity:1"
d="M 282.32026 71.007785 L 269.68783 99.205645 L 320.44381 99.205645 C 320.5261 99.214788 320.60289 99.235115 320.68445 99.245711 L 320.65624 99.229781 L 365.21632 99.221575 L 371.87691 84.542893 L 372.55348 81.158995 C 372.55348 78.226417 371.20007 75.74515 368.71866 73.714903 C 366.23725 71.910239 363.30492 71.007785 360.14676 71.007785 L 282.32026 71.007785 z "
transform="scale(0.93412862,1.0705164)" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#4c7cbc;stroke:#000000;stroke-width:0.263773"
d="m 124.63791,205.22986 h 80.75871 L 218.9316,173.64824 H 173.815 l 11.05357,-24.81413 h 45.34218 l 12.18148,-28.19787 h -80.08196 z"
transform="scale(0.93412862,1.0705164)"
id="path2" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#7a7cc1;stroke:#000000;stroke-width:0.263773;fill-opacity:1"
d="M 171.55917,99.205852 H 252.0923 L 264.27378,71.007978 H 184.19182 Z"
transform="scale(0.93412862,1.0705164)"
id="path1" />
<path
style="font-weight:500;font-size:225.583px;font-family:Federation;-inkscape-font-specification:'Federation Medium';fill:#4c7cbc;stroke:#000000;stroke-width:0.263773"
d="M 19.06486,173.64824 4.4019656,205.22986 H 72.302446 c 16.467559,0 29.776954,-5.41399 40.379354,-16.01639 10.37682,-10.6024 15.56523,-23.46064 15.56523,-38.12353 0,-14.66289 -6.54191,-24.13738 -19.40014,-28.19787 -13.083812,-3.83491 -19.625719,-7.89541 -19.625719,-11.9559 0,-4.96283 1.127915,-8.12099 3.609328,-9.70007 2.481413,-1.353499 6.541906,-2.030248 12.407061,-2.030248 h 29.10021 L 146.51925,71.007978 H 88.544422 c -13.760563,0 -23.009465,3.834911 -28.197874,11.730315 -5.188409,7.895405 -7.669822,17.595477 -7.669822,29.325787 0,13.53498 6.316324,22.78388 19.174554,27.74671 12.632648,4.96283 18.948972,10.6024 18.948972,16.91872 0,6.54191 -2.030247,11.05357 -6.316324,13.53498 -4.962826,2.25583 -11.053567,3.38375 -18.272222,3.38375 z"
transform="scale(0.93412862,1.0705164)"
id="text1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,5 +1,4 @@
#ifndef SPECIES_MASS_DATA_H
#define SPECIES_MASS_DATA_H
#pragma once
#include <unordered_map>
#include <string_view>
#include <string>
@@ -7205,4 +7204,3 @@ namespace chemSpecies {
{"Og-295", Og_295},
};
}; // namespace chemSpecies
#endif // SPECIES_MASS_DATA_H

View File

@@ -6,4 +6,18 @@ subdir('quill')
subdir('boost')
subdir('opatIO')
subdir('mpi')
subdir('hypre')
subdir('hypre')
subdir('pybind')
# Set the config file error handling options
configErr = get_option('config_error_handling')
# build up any -D flags we need
commonCppArgs = []
if configErr == 'warn'
commonCppArgs += ['-DCONFIG_WARN']
elif configErr == 'harsh'
commonCppArgs += ['-DCONFIG_HARSH']
endif
add_project_arguments(commonCppArgs, language: 'cpp')

View File

@@ -4,10 +4,11 @@ mfem_cmake_options.add_cmake_defines({
'MFEM_ENABLE_TESTING': 'OFF',
'MFEM_ENABLE_MINIAPPS': 'OFF',
'MFEM_USE_BENCMARK': 'OFF',
'BUILD_SHARED_LIBS': 'ON',
'CMAKE_SKIP_INSTALL_RULES': 'ON'
})
mfem_sp = cmake.subproject(
'mfem',
options: mfem_cmake_options)
mfem_dep = mfem_sp.dependency('mfem')
add_project_arguments('-I' + meson.current_build_dir() + '/subprojects/mfem/__CMake_build/config', language: 'cpp')

View File

@@ -0,0 +1,5 @@
if get_option('build_python')
pybind11_proj = subproject('pybind11')
pybind11_dep = pybind11_proj.get_variable('pybind11_dep')
python3_dep = dependency('python3')
endif

View File

@@ -1,5 +1,10 @@
quill_cmake_options = cmake.subproject_options()
quill_cmake_options.add_cmake_defines({
'BUILD_SHARED_LIBS': 'ON',
'CMAKE_SKIP_INSTALL_RULES': 'ON'
})
quill_sp = cmake.subproject(
'quill'
'quill',
options: quill_cmake_options,
)
quill_dep = quill_sp.dependency('quill')
add_project_arguments('-I' + meson.current_build_dir() + '/subprojects/quill/__CMake_build', language: 'cpp')
quill_dep = quill_sp.dependency('quill')

View File

@@ -1,10 +1,11 @@
yaml_cpp_cmake_options = cmake.subproject_options()
yaml_cpp_cmake_options.add_cmake_defines({
'CMAKE_POLICY_VERSION_MINIMUM': '3.5'
'CMAKE_POLICY_VERSION_MINIMUM': '3.5',
'BUILD_SHARED_LIBS': 'ON',
'CMAKE_SKIP_INSTALL_RULES': 'ON'
})
yaml_cpp_sp = cmake.subproject(
'yaml-cpp',
options: yaml_cpp_cmake_options,
)
yaml_cpp_dep = yaml_cpp_sp.dependency('yaml-cpp')
add_project_arguments('-I' + meson.current_build_dir() + '/subprojects/yaml-cpp/__CMake_build', language: 'cpp')
yaml_cpp_dep = yaml_cpp_sp.dependency('yaml-cpp')

22
build-python/meson.build Normal file
View File

@@ -0,0 +1,22 @@
# --- Python Extension Setup ---
py_installation = import('python').find_installation('python3')
py_mod = py_installation.extension_module(
'fourdsse_bindings', # Name of the generated .so/.pyd file (without extension)
sources: [
meson.project_source_root() + '/src/python/bindings.cpp',
meson.project_source_root() + '/src/python/composition/bindings.cpp',
meson.project_source_root() + '/src/python/const/bindings.cpp',
meson.project_source_root() + '/src/python/config/bindings.cpp',
],
dependencies : [
pybind11_dep,
const_dep,
config_dep,
composition_dep,
species_weight_dep
],
cpp_args : ['-UNDEBUG'], # Example: Ensure assertions are enabled if needed
install : true,
subdir: 'fourdstar' # Optional: Install the module inside a 'fourdsse' Python package directory
)

View File

@@ -54,6 +54,10 @@ if get_option('build_tests')
subdir('tests')
endif
if get_option('build_python')
subdir('build-python')
endif
if get_option('build_post_run_utils')
subdir('utils')
endif

View File

@@ -1,4 +1,20 @@
# Build options for the project
# Usage:
# -Doption_name=value
# -Dbuild_tests=true (default) build tests
# -Duser_mode=false (default) enable user mode (set mode = 0) If user mode is enabled then the optimization level is set to 3 and the build type is set to release
# -Dbuild_python=true (default) build Python bindings
option('build_tests', type: 'boolean', value: true, description: 'Build tests')
option('user_mode', type: 'boolean', value: false, description: 'Enable user mode (set mode = 0)')
option('build_python', type: 'boolean', value: true, description: 'Build Python bindings')
option(
'config_error_handling',
type: 'combo',
choices: [ 'none', 'warn', 'harsh' ],
value: 'none',
description: 'What to do if a config file fails to load: silent (none), warning (warn), or error (harsh)'
)
option('build_post_run_utils', type: 'boolean', value: true, description: 'Build Helper Utilities')
option('build_debug_utils', type: 'boolean', value: true, description: 'Build Debug Utilities')

21
pyproject.toml Normal file
View File

@@ -0,0 +1,21 @@
[build-system]
requires = [
"meson-python>=0.15.0", # Use a recent version
"meson>=1.6.0", # Specify your Meson version requirement
"pybind11>=2.10" # pybind11 headers needed at build time
]
build-backend = "mesonpy"
[project]
name = "fourdstar" # Choose your Python package name
version = "0.1.0" # Your project's version
description = "Python interface for the 4DSSE C++ project"
readme = "Readme.md"
license = { file = "LICENSE.txt" } # Reference your license file [cite: 2]
authors = [
{name = "Emily M. Boudreaux", email = "emily@boudreauxmail.com"},
]
maintainers = [
{name = "Emily M. Boudreaux", email = "emily@boudreauxmail.com"}
]

View File

@@ -9,13 +9,12 @@ composition_headers = files(
dependencies = [
probe_dep,
config_dep,
quill_dep,
species_weight_dep
]
# Define the libcomposition library so it can be linked against by other parts of the build system
libcomposition = static_library('composition',
libcomposition = library('composition',
composition_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

View File

@@ -8,7 +8,7 @@ config_headers = files(
)
# Define the libconfig library so it can be linked against by other parts of the build system
libconfig = static_library('config',
libconfig = library('config',
config_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

View File

@@ -18,8 +18,7 @@
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// *********************************************************************** */
#ifndef CONFIG_H
#define CONFIG_H
#pragma once
#include <string>
#include <iostream>
@@ -28,7 +27,6 @@
#include <map>
#include <algorithm>
#include <stdexcept>
#include <stdexcept>
// Required for YAML parsing
#include "yaml-cpp/yaml.h"
@@ -162,7 +160,12 @@ public:
template <typename T>
T get(const std::string &key, T defaultValue) {
if (!m_loaded) {
throw std::runtime_error("Configuration file not loaded! This should be done at the very start of whatever main function you are using (and only done once!)");
// ONLY THROW ERROR IF HARSH OR WARN CONFIGURATION
#if defined(CONFIG_HARSH)
throw std::runtime_error("Error! Config file not loaded. To disable this error, recompile with CONFIG_HARSH=0");
#elif defined(CONFIG_WARN)
std::cerr << "Warning! Config file not loaded. This instance of 4DSSE was compiled with CONFIG_WARN so the code will continue using only default values" << std::endl;
#endif
}
// --- Check if the key has already been checked for existence
if (std::find(unknownKeys.begin(), unknownKeys.end(), key) != unknownKeys.end()) {
@@ -241,5 +244,3 @@ public:
} // namespace config
} // namespace serif
#endif

View File

@@ -20,9 +20,7 @@
// *********************************************************************** */
#pragma once
#include <string>
#include <fstream>
#include <iostream>
#include <vector>
#include <set>
#include <map>
@@ -95,7 +93,7 @@ private:
public:
/**
* @brief get instance of constants singelton
* @brief get instance of constants singleton
* @return instance of constants
*/
static Constants& getInstance() {
@@ -107,7 +105,7 @@ public:
* @brief Check if constants are loaded.
* @return True if constants are loaded, false otherwise.
*/
bool isLoaded() { return loaded_; }
bool isLoaded() const { return loaded_; }
/**
* @brief Get a constant by key.

View File

@@ -18,7 +18,7 @@ dependencies = [
macros_dep,
]
# Define the libconst library so it can be linked against by other parts of the build system
libeos = static_library('eos',
libeos = library('eos',
eos_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

View File

@@ -10,7 +10,7 @@ dependencies = [
mfem_dep
]
# Define the libmeshIO library so it can be linked against by other parts of the build system
libmeshIO = static_library('meshIO',
libmeshIO = library('meshIO',
meshIO_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

View File

@@ -22,3 +22,6 @@ subdir('resource')
# Physics Libraries
subdir('network')
subdir('polytrope')
# Python Bindings
subdir('python')

View File

@@ -19,7 +19,7 @@ dependencies = [
]
# Define the libnetwork library so it can be linked against by other parts of the build system
libnetwork = static_library('network',
libnetwork = library('network',
network_sources,
include_directories: include_directories('public'),
dependencies: dependencies,

View File

@@ -34,7 +34,7 @@ dependencies = [
mfemanalysis_dep,
]
libpolyutils = static_library('polyutils',
libpolyutils = library('polyutils',
polyutils_sources,
include_directories : include_directories('./public'),
cpp_args: ['-fvisibility=default'],

View File

@@ -35,7 +35,7 @@ dependencies = [
]
# Define the liblogger library so it can be linked against by other parts of the build system
libprobe = static_library('probe',
libprobe = library('probe',
probe_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

20
src/python/bindings.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include <pybind11/pybind11.h>
#include <string>
#include "const/bindings.h"
#include "composition/bindings.h"
#include "config/bindings.h"
PYBIND11_MODULE(fourdsse_bindings, m) {
m.doc() = "Python bindings for the 4DSSE project";
auto compMod = m.def_submodule("composition", "Composition-module bindings");
register_comp_bindings(compMod);
auto constMod = m.def_submodule("constants", "Constants-module bindings");
register_const_bindings(constMod);
auto configMod = m.def_submodule("config", "Configuration-module bindings");
register_config_bindings(configMod);
}

View File

@@ -0,0 +1,163 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // Needed for vectors, maps, sets, strings
#include <pybind11/stl_bind.h> // Needed for binding std::vector, std::map etc if needed directly
#include <string>
#include "composition.h"
#include "atomicSpecies.h"
#include "bindings.h"
namespace py = pybind11;
std::string sv_to_string(std::string_view sv) {
return std::string(sv);
}
std::string get_ostream_str(const composition::Composition& comp) {
std::ostringstream oss;
oss << comp;
return oss.str();
}
void register_comp_bindings(pybind11::module &comp_submodule) {
// --- Bindings for composition and species module ---
py::class_<composition::GlobalComposition>(comp_submodule, "GlobalComposition")
.def_readonly("specificNumberDensity", &composition::GlobalComposition::specificNumberDensity)
.def_readonly("meanParticleMass", &composition::GlobalComposition::meanParticleMass)
.def("__repr__", // Add a string representation for easy printing in Python
[](const composition::GlobalComposition &gc) {
return "<GlobalComposition(specNumDens=" + std::to_string(gc.specificNumberDensity) +
", meanMass=" + std::to_string(gc.meanParticleMass) + ")>";
});
py::class_<composition::CompositionEntry>(comp_submodule, "CompositionEntry")
.def("symbol", &composition::CompositionEntry::symbol)
.def("mass_fraction",
py::overload_cast<>(&composition::CompositionEntry::mass_fraction, py::const_),
"Gets the mass fraction of the species.")
.def("mass_fraction",
py::overload_cast<double>(&composition::CompositionEntry::mass_fraction, py::const_),
py::arg("meanMolarMass"), // Name the argument in Python
"Gets the mass fraction of the species given the mean molar mass.")
.def("number_fraction",
py::overload_cast<>(&composition::CompositionEntry::number_fraction, py::const_),
"Gets the number fraction of the species.")
.def("number_fraction",
py::overload_cast<double>(&composition::CompositionEntry::number_fraction, py::const_),
py::arg("totalMoles"),
"Gets the number fraction of the species given the total moles.")
.def("rel_abundance", &composition::CompositionEntry::rel_abundance)
.def("isotope", &composition::CompositionEntry::isotope) // Assuming Species is bound or convertible
.def("getMassFracMode", &composition::CompositionEntry::getMassFracMode)
.def("__repr__", // Optional: nice string representation
[](const composition::CompositionEntry &ce) {
// You might want to include more info here now
return "<CompositionEntry(symbol='" + ce.symbol() + "', " +
"mass_frac=" + std::to_string(ce.mass_fraction()) + ", " +
"num_frac=" + std::to_string(ce.number_fraction()) + ")>";
});
// --- Binding for the main Composition class ---
py::class_<composition::Composition>(comp_submodule, "Composition")
// Constructors
.def(py::init<>(), "Default constructor")
.def(py::init<const std::vector<std::string>&>(),
py::arg("symbols"),
"Constructor taking a list of symbols to register (defaults to mass fraction mode)")
// .def(py::init<const std::set<std::string>&>(), py::arg("symbols")) // Binding std::set constructor is possible but often less convenient from Python
.def(py::init<const std::vector<std::string>&, const std::vector<double>&, bool>(),
py::arg("symbols"), py::arg("fractions"), py::arg("massFracMode") = true,
"Constructor taking symbols, fractions, and mode (True=Mass, False=Number)")
// Methods
.def("finalize", &composition::Composition::finalize, py::arg("norm") = false,
"Finalize the composition, optionally normalizing fractions to sum to 1.")
.def("registerSymbol", py::overload_cast<const std::string&, bool>(&composition::Composition::registerSymbol),
py::arg("symbol"), py::arg("massFracMode") = true, "Register a single symbol.")
.def("registerSymbol", py::overload_cast<const std::vector<std::string>&, bool>(&composition::Composition::registerSymbol),
py::arg("symbols"), py::arg("massFracMode") = true, "Register multiple symbols.")
.def("getRegisteredSymbols", &composition::Composition::getRegisteredSymbols,
"Get the set of registered symbols.")
.def("setMassFraction", py::overload_cast<const std::string&, const double&>(&composition::Composition::setMassFraction),
py::arg("symbol"), py::arg("mass_fraction"), "Set mass fraction for a single symbol (requires massFracMode). Returns old value.")
.def("setMassFraction", py::overload_cast<const std::vector<std::string>&, const std::vector<double>&>(&composition::Composition::setMassFraction),
py::arg("symbols"), py::arg("mass_fractions"), "Set mass fractions for multiple symbols (requires massFracMode). Returns list of old values.")
.def("setNumberFraction", py::overload_cast<const std::string&, const double&>(&composition::Composition::setNumberFraction),
py::arg("symbol"), py::arg("number_fraction"), "Set number fraction for a single symbol (requires !massFracMode). Returns old value.")
.def("setNumberFraction", py::overload_cast<const std::vector<std::string>&, const std::vector<double>&>(&composition::Composition::setNumberFraction),
py::arg("symbols"), py::arg("number_fractions"), "Set number fractions for multiple symbols (requires !massFracMode). Returns list of old values.")
.def("mix", &composition::Composition::mix, py::arg("other"), py::arg("fraction"),
"Mix with another composition. Returns new Composition.")
.def("getMassFraction", py::overload_cast<const std::string&>(&composition::Composition::getMassFraction, py::const_),
py::arg("symbol"), "Get mass fraction for a symbol (calculates if needed). Requires finalization.")
.def("getMassFraction", py::overload_cast<>(&composition::Composition::getMassFraction, py::const_),
"Get dictionary of all mass fractions. Requires finalization.")
.def("getNumberFraction", py::overload_cast<const std::string&>(&composition::Composition::getNumberFraction, py::const_),
py::arg("symbol"), "Get number fraction for a symbol (calculates if needed). Requires finalization.")
.def("getNumberFraction", py::overload_cast<>(&composition::Composition::getNumberFraction, py::const_),
"Get dictionary of all number fractions. Requires finalization.")
// Note: pybind11 automatically converts std::pair to a Python tuple
.def("getComposition", py::overload_cast<const std::string&>(&composition::Composition::getComposition, py::const_),
py::arg("symbol"), "Returns a tuple (CompositionEntry, GlobalComposition) for the symbol. Requires finalization.")
// Binding the version returning map<string, Entry> requires a bit more care or helper function
// to convert the map to a Python dict if needed directly. Let's bind the pair version for now.
.def("getComposition", py::overload_cast<>(&composition::Composition::getComposition, py::const_),
"Returns a tuple (dict[str, CompositionEntry], GlobalComposition) for all symbols. Requires finalization.")
.def("subset", &composition::Composition::subset, py::arg("symbols"), py::arg("method") = "norm",
"Create a new Composition containing only the specified symbols.")
.def("hasSymbol", &composition::Composition::hasSymbol, py::arg("symbol"),
"Check if a symbol is registered.")
.def("setCompositionMode", &composition::Composition::setCompositionMode, py::arg("massFracMode"),
"Set the mode (True=Mass, False=Number). Requires finalization before switching.")
// Operator overload
.def(py::self + py::self, "Mix equally with another composition.") // Binds operator+
// Add __repr__ or __str__
.def("__repr__", [](const composition::Composition &comp) {
return get_ostream_str(comp); // Use helper for C++ operator<<
});
}
void register_species_bindings(pybind11::module &chem_submodule) {
// --- Bindings for species module ---
py::class_<chemSpecies::Species>(chem_submodule, "Species")
.def("mass", &chemSpecies::Species::mass, "Get atomic mass (amu)")
.def("massUnc", &chemSpecies::Species::massUnc, "Get atomic mass uncertainty (amu)")
.def("bindingEnergy", &chemSpecies::Species::bindingEnergy, "Get binding energy (keV/nucleon?)") // Check units
.def("betaDecayEnergy", &chemSpecies::Species::betaDecayEnergy, "Get beta decay energy (keV?)") // Check units
.def("betaCode", [](const chemSpecies::Species& s){ return sv_to_string(s.betaCode()); }, "Get beta decay code") // Convert string_view
.def("name", [](const chemSpecies::Species& s){ return sv_to_string(s.name()); }, "Get species name (e.g., 'H-1')") // Convert string_view
.def("el", [](const chemSpecies::Species& s){ return sv_to_string(s.el()); }, "Get element symbol (e.g., 'H')") // Convert string_view
.def("nz", &chemSpecies::Species::nz, "Get NZ value")
.def("n", &chemSpecies::Species::n, "Get neutron number N")
.def("z", &chemSpecies::Species::z, "Get proton number Z")
.def("a", &chemSpecies::Species::a, "Get mass number A")
.def("__repr__",
[](const chemSpecies::Species &s) {
std::ostringstream oss;
oss << s;
return oss.str();
});
chem_submodule.attr("species") = py::cast(chemSpecies::species); // Expose the species map
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include <pybind11/pybind11.h>
void register_comp_bindings(pybind11::module &m);
void register_species_bindings(pybind11::module &m);

View File

@@ -0,0 +1,18 @@
# Define the library
bindings_sources = files('bindings.cpp')
bindings_headers = files('bindings.h')
dependencies = [
composition_dep,
species_weight_dep,
python3_dep,
pybind11_dep,
]
shared_module('py_composition',
bindings_sources,
cpp_args: ['-fvisibility=default'],
install : true,
dependencies: dependencies,
include_directories: include_directories('.')
)

View File

@@ -0,0 +1,55 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // Needed for vectors, maps, sets, strings
#include <pybind11/stl_bind.h> // Needed for binding std::vector, std::map etc if needed directly
#include <string>
#include "bindings.h"
#include "config.h"
namespace py = pybind11;
// Helper function template for binding Config::get
template <typename T>
void def_config_get(py::module &m) {
m.def("get",
[](const std::string &key, T defaultValue) {
return Config::getInstance().get<T>(key, defaultValue);
},
py::arg("key"), py::arg("defaultValue"),
"Get configuration value (type inferred from default)");
}
void register_config_bindings(pybind11::module &config_submodule) {
def_config_get<int>(config_submodule);
def_config_get<double>(config_submodule);
def_config_get<std::string>(config_submodule);
def_config_get<bool>(config_submodule);
config_submodule.def("loadConfig",
[](const std::string& configFilePath) {
return Config::getInstance().loadConfig(configFilePath);
},
py::arg("configFilePath"),
"Load configuration from a YAML file.");
config_submodule.def("has",
[](const std::string &key) {
return Config::getInstance().has(key);
},
py::arg("key"),
"Check if a key exists in the configuration.");
config_submodule.def("keys",
[]() {
return py::cast(Config::getInstance().keys());
},
"Get a list of all configuration keys.");
config_submodule.def("__repr__",
[]() {
std::ostringstream oss;
oss << Config::getInstance(); // Use the existing operator<<
return std::string("<fourdsse_bindings.config module accessing C++ Singleton>\n") + oss.str();
});
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <pybind11/pybind11.h>
void register_config_bindings(pybind11::module &config_submodule);

View File

@@ -0,0 +1,17 @@
# Define the library
bindings_sources = files('bindings.cpp')
bindings_headers = files('bindings.h')
dependencies = [
config_dep,
python3_dep,
pybind11_dep,
]
shared_module('py_config',
bindings_sources,
include_directories: include_directories('.'),
cpp_args: ['-fvisibility=default'],
install : true,
dependencies: dependencies,
)

View File

@@ -0,0 +1,54 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // Needed for vectors, maps, sets, strings
#include <pybind11/stl_bind.h> // Needed for binding std::vector, std::map etc if needed directly
#include <string>
#include "const.h"
#include "bindings.h"
namespace py = pybind11;
void register_const_bindings(pybind11::module &const_submodule) {
py::class_<Constant>(const_submodule, "Constant")
.def_readonly("name", &Constant::name)
.def_readonly("value", &Constant::value)
.def_readonly("uncertainty", &Constant::uncertainty)
.def_readonly("unit", &Constant::unit)
.def_readonly("reference", &Constant::reference)
.def("__repr__", [](const Constant &c) {
return "<Constant(name='" + c.name + "', value=" + std::to_string(c.value) +
", uncertainty=" + std::to_string(c.uncertainty) +
", unit='" + c.unit + "')>";
});
py::class_<Constants>(const_submodule, "Constants")
.def_property_readonly("loaded", &Constants::isLoaded)
.def_static("get",
[](const std::string &name) {
return py::cast(
Constants::getInstance().get(name)
);
},
"Get a constant by name. Returns None if not found."
)
.def_static("has",
[](const std::string &name) {
return Constants::getInstance().has(name);
},
"Check if a constant exists by name.")
.def_static("keys",
[]() {
return py::cast(
Constants::getInstance().keys()
);
},
"Get a list of all constant names.")
.def_static("__class_getitem__",
[](const std::string &name) {
return py::cast(
Constants::getInstance().get(name)
);
});
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include <pybind11/pybind11.h>
void register_const_bindings(pybind11::module &const_submodule);

View File

@@ -0,0 +1,17 @@
# Define the library
bindings_sources = files('bindings.cpp')
bindings_headers = files('bindings.h')
dependencies = [
const_dep,
python3_dep,
pybind11_dep,
]
shared_module('py_const',
bindings_sources,
include_directories: include_directories('.'),
cpp_args: ['-fvisibility=default'],
install : true,
dependencies: dependencies,
)

View File

3
src/python/meson.build Normal file
View File

@@ -0,0 +1,3 @@
subdir('composition')
subdir('const')
subdir('config')

View File

@@ -23,7 +23,7 @@ dependencies = [
libResourceHeader_dep = declare_dependency(include_directories: include_directories('public'))
# Define the libresourceManager library so it can be linked against by other parts of the build system
libresourceManager = static_library('resourceManager',
libresourceManager = library('resourceManager',
resourceManager_sources,
include_directories: include_directories('public'),
cpp_args: ['-fvisibility=default'],

13
subprojects/pybind11.wrap Normal file
View File

@@ -0,0 +1,13 @@
[wrap-file]
directory = pybind11-2.13.5
source_url = https://github.com/pybind/pybind11/archive/refs/tags/v2.13.5.tar.gz
source_filename = pybind11-2.13.5.tar.gz
source_hash = b1e209c42b3a9ed74da3e0b25a4f4cd478d89d5efbb48f04b277df427faf6252
patch_filename = pybind11_2.13.5-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/pybind11_2.13.5-1/get_patch
patch_hash = ecb031b830481560b3d8487ed63ba4f5509a074be42f5d19af64d844c795e15b
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/pybind11_2.13.5-1/pybind11-2.13.5.tar.gz
wrapdb_version = 2.13.5-1
[provide]
pybind11 = pybind11_dep