diff --git a/README.md b/README.md index d6e1178..b122ce1 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,79 @@ and uses some lightweight symbolic manipulation from [sympy](https://docs.sympy.org/latest/index.html) to be able to output C++ constexpr expressions. -Constants, units and prefixes are all are organised in namespaces. +Constants, units and prefixes are all organised in namespaces. + +## Generated files + +Generated files are checked in under `output/`: + +- `codata_.hpp`: C++ headers with CODATA constants in the + `physicalconstants::codata` namespace. +- `codata_.py`: Python modules with the same CODATA constants. +- `units.hpp`: HEP unit conversion constants from `hepunits`, in the + `physicalconstants::units` namespace. +- `prefixes.hpp`: SI prefix constants from `hepunits`, in the + `physicalconstants::prefixes` namespace. + +For example, use the 2022 CODATA constants and HEP units from C++: + +```cpp +#include "output/codata_2022.hpp" +#include "output/prefixes.hpp" +#include "output/units.hpp" + +int main() { + using namespace physicalconstants::codata2022; + using namespace physicalconstants::prefixes; + using namespace physicalconstants::units; + + constexpr double electron_mass_mev = electron_mass_energy_equivalent_in_MeV; + constexpr double one_gev_in_mev = GeV / MeV; + constexpr double kilometer = kilo * meter; + + static_assert(speed_of_light_in_vacuum == 299792458.0); + static_assert(one_gev_in_mev == 1000.0); + + return electron_mass_mev > 0.0 && kilometer > meter ? 0 : 1; +} +``` + +See `examples/basic_codata_usage.cpp` for a complete example that can be +compiled from the repository root: + +```console +g++ -std=c++17 -I. examples/basic_codata_usage.cpp -o basic_codata_usage +./basic_codata_usage +``` + +## Regenerating outputs + +The generator scripts are run from the repository root. The default CODATA +generator command writes C++ headers to `output/`: + +```console +python3 src/codata_constants.py +``` + +To regenerate the Python CODATA modules, select the Python template and suffix: + +```console +python3 src/codata_constants.py --template templates/codata_py.jinja2 --suffix py +``` + +The HEP units and SI prefixes are generated separately: + +```console +python3 src/generate_units.py +python3 src/generate_prefixes.py +``` + +## Tests + +Run the Python tests with: + +```console +python3 -m pytest +``` *This is a work in progress, contributions welcome.* diff --git a/examples/basic_codata_usage.cpp b/examples/basic_codata_usage.cpp new file mode 100644 index 0000000..dd3ad79 --- /dev/null +++ b/examples/basic_codata_usage.cpp @@ -0,0 +1,27 @@ +#include + +#include "output/codata_2022.hpp" +#include "output/prefixes.hpp" +#include "output/units.hpp" + +int main() { + using namespace physicalconstants::codata2022; + using namespace physicalconstants::prefixes; + using namespace physicalconstants::units; + + constexpr double electron_mass_mev = electron_mass_energy_equivalent_in_MeV; + constexpr double proton_mass_mev = proton_mass_energy_equivalent_in_MeV; + constexpr double one_gev_in_mev = GeV / MeV; + constexpr double kilometer = kilo * meter; + + static_assert(speed_of_light_in_vacuum == 299792458.0); + static_assert(one_gev_in_mev == 1000.0); + static_assert(kilometer > meter); + + std::cout << "electron mass energy: " << electron_mass_mev << " MeV\n"; + std::cout << "proton mass energy: " << proton_mass_mev << " MeV\n"; + std::cout << "1 GeV in MeV: " << one_gev_in_mev << "\n"; + std::cout << "1 km in HEP length units: " << kilometer << "\n"; + + return 0; +} diff --git a/tests/test_basic.py b/tests/test_basic.py index 5acb9f7..954dad9 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,93 +1,77 @@ #!/usr/bin/env python3 """Basic tests for the CODATA parser and C++ header generator.""" -import os +import shutil +import subprocess import sys -import tempfile from pathlib import Path +import pytest + # Add src directory to Python path sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) -from main import parse_codata_file, generate_cpp_header +from codata_constants import generate_cpp_header, parse_codata_file -def test_parse_codata_file(): +def test_parse_codata_file(tmp_path): """Test parsing of a simple CODATA file.""" - # Create a temporary CODATA file with proper separator - with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: - f.write("""# Test CODATA file + codata_file = tmp_path / "allascii_2022.txt" + codata_file.write_text("""# Test CODATA file Quantity Value Uncertainty Unit ----------------------------------------------------------------------------------------------------------------------------- -speed_of_light 299792458 0 m s^-1 -Planck_constant 6.62607015e-34 0 J s +speed of light in vacuum 299 792 458 (exact) m s^-1 +Planck constant 6.626 070 15 e-34 (exact) J Hz^-1 """) - temp_file = f.name - - try: - # Parse the file - constants = parse_codata_file(Path(temp_file)) - - # Verify results (new format includes unit and hep_unit) - assert len(constants) == 2 - assert constants[0][0] == 'speed_of_light' - print(f"Actual value: '{constants[0][1]}'") - assert constants[0][1] == '2997924580' # Note: includes uncertainty for exact values - assert constants[0][2] == '' # No uncertainty for exact values - print(f"Second constant: name='{constants[1][0]}', value='{constants[1][1]}', uncertainty='{constants[1][2]}'") - assert constants[1][0] == 'Planck_constant' - assert constants[1][1] == '6.62607015e-340' # Includes uncertainty 0 - assert constants[1][2] == '' # No separate uncertainty - - print("✓ parse_codata_file test passed") - - finally: - # Clean up - os.unlink(temp_file) - - -def test_generate_cpp_header(): + + constants = parse_codata_file(codata_file) + + assert constants == [ + ("speed_of_light_in_vacuum", "299792458", "(exact)", "m s^-1"), + ("Planck_constant", "6.62607015e-34", "(exact)", "J Hz^-1"), + ] + + +def test_generate_cpp_header(tmp_path): """Test generation of C++ header from constants.""" - # Test constants constants = [ - ('speed_of_light', '299792458', '0'), - ('Planck_constant', '6.62607015e-34', '0') + ("speed_of_light_in_vacuum", "299792458", "(exact)", "m s^-1"), + ("Planck_constant", "6.62607015e-34", "(exact)", "J Hz^-1"), ] - - # Create temporary template file - with tempfile.NamedTemporaryFile(mode='w', suffix='.jinja2', delete=False) as f: - f.write("""// Test header -{% for name, value, uncertainty in constants %} + template_file = tmp_path / "test_header.jinja2" + output_file = tmp_path / "test.hpp" + + template_file.write_text("""// Test header +{% for name, value, uncertainty, unit in constants %} constexpr double {{ name }} = {{ value }}; {% endfor %} """) - template_file = f.name - - # Create temporary output file - with tempfile.NamedTemporaryFile(mode='w', suffix='.hpp', delete=False) as f: - output_file = f.name - - try: - # Generate header - generate_cpp_header(constants, Path(template_file), Path(output_file), "2010") - - # Read and verify output - with open(output_file, 'r') as f: - content = f.read() - - assert 'constexpr double speed_of_light = 299792458;' in content - assert 'constexpr double Planck_constant = 6.62607015e-34;' in content - - print("✓ generate_cpp_header test passed") - - finally: - # Clean up - os.unlink(template_file) - os.unlink(output_file) + + generate_cpp_header(constants, template_file, output_file, "2022") + + content = output_file.read_text() + + assert "constexpr double speed_of_light_in_vacuum = 299792458;" in content + assert "constexpr double Planck_constant = 6.62607015e-34;" in content + + +def test_basic_codata_usage_example_compiles(tmp_path): + """Test the C++ usage example against the checked-in generated headers.""" + compiler = shutil.which("g++") or shutil.which("c++") + if compiler is None: + pytest.skip("C++ compiler not available") + + repo_root = Path(__file__).parent.parent + source_file = repo_root / "examples" / "basic_codata_usage.cpp" + executable = tmp_path / "basic_codata_usage" + + subprocess.run( + [compiler, "-std=c++17", "-I", str(repo_root), str(source_file), "-o", str(executable)], + check=True, + ) + subprocess.run([str(executable)], check=True) if __name__ == '__main__': - test_parse_codata_file() - test_generate_cpp_header() - print("All tests passed!") \ No newline at end of file + raise SystemExit(pytest.main([__file__]))