qmfcalc 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ sdist:
9
+ name: Build source distribution
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Check out source
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.13"
20
+
21
+ - name: Build sdist
22
+ run: |
23
+ python -m pip install --upgrade pip build
24
+ python -m build --sdist --outdir dist
25
+
26
+ - name: Upload sdist
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: qmfcalc-sdist
30
+ path: dist/*.tar.gz
31
+
32
+ wheels:
33
+ name: Build wheel
34
+ runs-on: ${{ matrix.os }}
35
+ strategy:
36
+ fail-fast: false
37
+ matrix:
38
+ os: [ubuntu-latest, windows-latest]
39
+
40
+ steps:
41
+ - name: Check out source
42
+ uses: actions/checkout@v4
43
+
44
+ - name: Set up Python
45
+ uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.13"
48
+
49
+ - name: Build wheel
50
+ uses: pypa/cibuildwheel@v4.1.0
51
+ env:
52
+ CIBW_BUILD: cp313-*
53
+ CIBW_ARCHS_LINUX: x86_64
54
+ CIBW_ARCHS_WINDOWS: AMD64
55
+ CIBW_SKIP: "*-musllinux_*"
56
+ CIBW_TEST_COMMAND: >-
57
+ python -c "import qmfcalc; r = qmfcalc.calculate_voltages(15.0, 250.0, 250.0, 1.0); assert r['rf_voltage_v'] > 0.0; assert r['dc_voltage_v'] > 0.0; print(r)"
58
+ with:
59
+ output-dir: dist
60
+
61
+ - name: Upload wheel
62
+ uses: actions/upload-artifact@v4
63
+ with:
64
+ name: qmfcalc-${{ matrix.os }}-py3.13-wheel
65
+ path: dist/*.whl
66
+
67
+ publish:
68
+ name: Publish distributions to PyPI
69
+ runs-on: ubuntu-latest
70
+ needs: [sdist, wheels]
71
+ environment: pypi
72
+ permissions:
73
+ id-token: write
74
+ contents: read
75
+
76
+ steps:
77
+ - name: Download distributions
78
+ uses: actions/download-artifact@v4
79
+ with:
80
+ pattern: qmfcalc-*
81
+ merge-multiple: true
82
+ path: dist
83
+
84
+ - name: List distributions
85
+ run: ls -l dist
86
+
87
+ - name: Publish to PyPI
88
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ name: Python package
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ wheel:
10
+ name: Build wheel
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest]
16
+ python-version: ["3.13"]
17
+
18
+ steps:
19
+ - name: Check out source
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Build wheel
28
+ uses: pypa/cibuildwheel@v4.1.0
29
+ env:
30
+ CIBW_BUILD: cp313-*
31
+ CIBW_ARCHS_LINUX: x86_64
32
+ CIBW_ARCHS_WINDOWS: AMD64
33
+ CIBW_SKIP: "*-musllinux_*"
34
+ CIBW_TEST_COMMAND: >-
35
+ python -c "import qmfcalc; r = qmfcalc.calculate_voltages(15.0, 250.0, 250.0, 1.0); assert r['rf_voltage_v'] > 0.0; assert r['dc_voltage_v'] > 0.0; print(r)"
36
+ with:
37
+ output-dir: wheelhouse
38
+
39
+ - name: Upload wheel
40
+ uses: actions/upload-artifact@v4
41
+ with:
42
+ name: qmfcalc-${{ matrix.os }}-py${{ matrix.python-version }}-wheel
43
+ path: wheelhouse/*.whl
@@ -0,0 +1,7 @@
1
+ *.clangd
2
+ build/
3
+ dist/
4
+ wheelhouse/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.pyc
@@ -0,0 +1,3 @@
1
+ [submodule "ext/roothelper"]
2
+ path = ext/roothelper
3
+ url = https://github.com/kenji0923/roothelper.git
@@ -0,0 +1,88 @@
1
+ # -----
2
+ # Fundamental settings
3
+ # -----
4
+
5
+ cmake_minimum_required(VERSION 3.28 FATAL_ERROR)
6
+
7
+ project(qmfcalc VERSION 0.1.0 LANGUAGES CXX)
8
+
9
+ set(CMAKE_CXX_STANDARD 17)
10
+ set(CMAKE_CXX_STANDARD_REQUIRED True)
11
+
12
+ option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
13
+ option(QMFCALC_BUILD_EXECUTABLES "Build ROOT-backed command line executables" ON)
14
+ option(QMFCALC_BUILD_DOCS "Build Doxygen documentation target" ON)
15
+ option(QMFCALC_BUILD_PYTHON "Build Python extension module" OFF)
16
+
17
+ if(QMFCALC_BUILD_PYTHON)
18
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
19
+ endif()
20
+
21
+ if(NOT CMAKE_BUILD_TYPE)
22
+ set(CMAKE_BUILD_TYPE Release)
23
+ endif()
24
+
25
+
26
+ # -----
27
+ # External packages
28
+ # -----
29
+
30
+ include(FetchContent)
31
+
32
+ if(QMFCALC_BUILD_EXECUTABLES)
33
+ # argparse: command line argument parsing (header-only).
34
+ FetchContent_Declare(
35
+ argparse
36
+ GIT_REPOSITORY https://github.com/p-ranav/argparse.git
37
+ GIT_TAG v3.2
38
+ )
39
+
40
+ # fkYAML: YAML serialization for result output (header-only).
41
+ FetchContent_Declare(
42
+ fkYAML
43
+ GIT_REPOSITORY https://github.com/fktn-k/fkYAML.git
44
+ GIT_TAG v0.4.2
45
+ )
46
+
47
+ FetchContent_MakeAvailable(argparse fkYAML)
48
+
49
+ find_package(ROOT REQUIRED COMPONENTS Core Hist Gpad Tree RIO)
50
+
51
+ # roothelper (CERN ROOT data management and plot formatting) provides the
52
+ # RootHelper INTERFACE target. Its own CMake calls find_package(ROOT).
53
+ add_subdirectory(${PROJECT_SOURCE_DIR}/ext/roothelper)
54
+ endif()
55
+
56
+ if(QMFCALC_BUILD_PYTHON)
57
+ find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
58
+ set(PYBIND11_FINDPYTHON ON)
59
+ find_package(pybind11 CONFIG QUIET)
60
+
61
+ if(NOT pybind11_FOUND)
62
+ FetchContent_Declare(
63
+ pybind11
64
+ GIT_REPOSITORY https://github.com/pybind/pybind11.git
65
+ GIT_TAG v2.13.6
66
+ )
67
+ FetchContent_MakeAvailable(pybind11)
68
+ endif()
69
+ endif()
70
+
71
+
72
+ # -----
73
+ # Sub-directories
74
+ # -----
75
+
76
+ add_subdirectory(${PROJECT_SOURCE_DIR}/core)
77
+
78
+ if(QMFCALC_BUILD_DOCS)
79
+ add_subdirectory(${PROJECT_SOURCE_DIR}/doc)
80
+ endif()
81
+
82
+ if(QMFCALC_BUILD_EXECUTABLES)
83
+ add_subdirectory(${PROJECT_SOURCE_DIR}/exec)
84
+ endif()
85
+
86
+ if(QMFCALC_BUILD_PYTHON)
87
+ add_subdirectory(${PROJECT_SOURCE_DIR}/python)
88
+ endif()
qmfcalc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.2
2
+ Name: qmfcalc
3
+ Version: 0.1.0
4
+ Summary: Python bindings for quadrupole mass filter voltage calculations
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3.13
7
+ Classifier: Programming Language :: C++
8
+ Classifier: Topic :: Scientific/Engineering :: Physics
9
+ Requires-Python: >=3.13
10
+ Description-Content-Type: text/markdown
11
+
12
+ # qmfcalc
13
+
14
+ Quadrupole mass filter calculation utilities.
15
+
16
+ ## Python
17
+
18
+ The Python package builds a native extension with `pybind11`:
19
+
20
+ ```sh
21
+ python -m pip install .
22
+ ```
23
+
24
+ Python 3.13 is the supported target.
25
+
26
+ or directly from a Git checkout:
27
+
28
+ ```sh
29
+ python -m pip install git+https://github.com/<owner>/<repo>.git
30
+ ```
31
+
32
+ Use it from Python:
33
+
34
+ ```python
35
+ import qmfcalc
36
+
37
+ result = qmfcalc.calculate_voltages(
38
+ r0_mm=15.0,
39
+ target_m_over_q_amu_per_z=250.0,
40
+ resolution=250.0,
41
+ rf_frequency_mhz=1.0,
42
+ )
43
+
44
+ print(result["rf_voltage_v"], result["dc_voltage_v"])
45
+ ```
46
+
47
+ `rf_voltage_v` is the per-rod peak-to-peak RF voltage. `dc_voltage_v` is the
48
+ pole-to-pole DC voltage.
49
+
50
+ The GitHub Actions workflow in `.github/workflows/python-package.yml` builds and
51
+ tests Python 3.13 Linux and Windows wheels on pushes and pull requests.
52
+
53
+ ## PyPI release
54
+
55
+ Configure a PyPI pending trusted publisher with:
56
+
57
+ - PyPI project name: `qmfcalc`
58
+ - Owner: `kenji0923`
59
+ - Repository: `qmfcalc`
60
+ - Workflow name: `publish-pypi.yml`
61
+ - Environment name: `pypi`
62
+
63
+ Then publish a GitHub Release from a version tag, for example:
64
+
65
+ ```sh
66
+ git tag v0.1.0
67
+ git push origin v0.1.0
68
+ ```
69
+
70
+ The `.github/workflows/publish-pypi.yml` workflow builds the Python 3.13 Linux
71
+ and Windows wheels, builds the source distribution, and publishes them to PyPI.
@@ -0,0 +1,60 @@
1
+ # qmfcalc
2
+
3
+ Quadrupole mass filter calculation utilities.
4
+
5
+ ## Python
6
+
7
+ The Python package builds a native extension with `pybind11`:
8
+
9
+ ```sh
10
+ python -m pip install .
11
+ ```
12
+
13
+ Python 3.13 is the supported target.
14
+
15
+ or directly from a Git checkout:
16
+
17
+ ```sh
18
+ python -m pip install git+https://github.com/<owner>/<repo>.git
19
+ ```
20
+
21
+ Use it from Python:
22
+
23
+ ```python
24
+ import qmfcalc
25
+
26
+ result = qmfcalc.calculate_voltages(
27
+ r0_mm=15.0,
28
+ target_m_over_q_amu_per_z=250.0,
29
+ resolution=250.0,
30
+ rf_frequency_mhz=1.0,
31
+ )
32
+
33
+ print(result["rf_voltage_v"], result["dc_voltage_v"])
34
+ ```
35
+
36
+ `rf_voltage_v` is the per-rod peak-to-peak RF voltage. `dc_voltage_v` is the
37
+ pole-to-pole DC voltage.
38
+
39
+ The GitHub Actions workflow in `.github/workflows/python-package.yml` builds and
40
+ tests Python 3.13 Linux and Windows wheels on pushes and pull requests.
41
+
42
+ ## PyPI release
43
+
44
+ Configure a PyPI pending trusted publisher with:
45
+
46
+ - PyPI project name: `qmfcalc`
47
+ - Owner: `kenji0923`
48
+ - Repository: `qmfcalc`
49
+ - Workflow name: `publish-pypi.yml`
50
+ - Environment name: `pypi`
51
+
52
+ Then publish a GitHub Release from a version tag, for example:
53
+
54
+ ```sh
55
+ git tag v0.1.0
56
+ git push origin v0.1.0
57
+ ```
58
+
59
+ The `.github/workflows/publish-pypi.yml` workflow builds the Python 3.13 Linux
60
+ and Windows wheels, builds the source distribution, and publishes them to PyPI.
@@ -0,0 +1,46 @@
1
+ # ----- Custom function definition start -----
2
+
3
+ # Add the standard include directory.
4
+ function(add_include_directory_std target_name scope)
5
+ target_include_directories(${target_name} ${scope}
6
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
7
+ $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/${PROJECT_NAME}>
8
+ )
9
+ endfunction()
10
+
11
+ # Add library with a standard filenames
12
+ # The third argument can be used for specifying library type (STATIC|SHARED|INTERFACE)
13
+ function(add_library_std target_name library_file_name)
14
+ set(library_type "")
15
+
16
+ if(${ARGC} GREATER 2)
17
+ set(library_type ${ARGV2})
18
+ endif()
19
+
20
+ set(include_scope "PUBLIC")
21
+
22
+ if("${library_type}" STREQUAL "INTERFACE")
23
+ add_library(${target_name} ${library_type} include/${PROJECT_NAME}/${library_file_name}.h)
24
+ set(include_scope "INTERFACE")
25
+ else()
26
+ add_library(${target_name} ${library_type} ${library_file_name}.cpp include/${PROJECT_NAME}/${library_file_name}.h)
27
+ endif()
28
+
29
+ add_include_directory_std(${target_name} ${include_scope})
30
+
31
+ add_library(${PROJECT_NAME}::${target_name} ALIAS ${target_name})
32
+ endfunction()
33
+
34
+ # ----- Function definiion end -----
35
+
36
+ add_library_std(StabilityDiagram stability_diagram INTERFACE)
37
+
38
+ add_library_std(ResolutionEstimator resolution_estimator)
39
+ target_link_libraries(ResolutionEstimator PUBLIC
40
+ StabilityDiagram
41
+ )
42
+
43
+ add_library_std(VoltageCalculator voltage_calculator)
44
+ target_link_libraries(VoltageCalculator PUBLIC
45
+ ResolutionEstimator
46
+ )
@@ -0,0 +1,65 @@
1
+ #ifndef QMFCALC_ION_CONFIG_H
2
+ #define QMFCALC_ION_CONFIG_H
3
+
4
+
5
+ #include <vector>
6
+
7
+
8
+ namespace qmfcalc {
9
+
10
+
11
+ struct IonMotion
12
+ {
13
+ IonMotion(
14
+ const double x,
15
+ const double y,
16
+ const double v_x,
17
+ const double v_y,
18
+ const double v_z
19
+ )
20
+ : x(x),
21
+ y(y),
22
+ v_x(v_x),
23
+ v_y(v_y),
24
+ v_z(v_z)
25
+ { }
26
+
27
+ double x;
28
+ double y;
29
+ double v_x;
30
+ double v_y;
31
+ double v_z;
32
+ };
33
+
34
+
35
+ struct IonSet
36
+ {
37
+ IonSet(
38
+ const double mass,
39
+ const std::vector<IonMotion>& motions
40
+ )
41
+ : mass(mass),
42
+ motions(motions)
43
+ { }
44
+
45
+ const double mass;
46
+ const std::vector<IonMotion> motions;
47
+ };
48
+
49
+
50
+ struct PhaseSpacePoint
51
+ {
52
+ static double get_u_dot(const double omega, const double v) { return 2. / omega * v; }
53
+
54
+ PhaseSpacePoint(const double omega, const double u, const double v) : omega(omega), u(u), u_dot(get_u_dot(omega, v)) { }
55
+
56
+ const double omega;
57
+ double u;
58
+ double u_dot;
59
+ };
60
+
61
+
62
+ } // namespace qmfcalc
63
+
64
+
65
+ #endif
@@ -0,0 +1,43 @@
1
+ #ifndef QMFCALC_MATRIX_METHOD_CALCULATOR_H
2
+ #define QMFCALC_MATRIX_METHOD_CALCULATOR_H
3
+
4
+
5
+ #include <array>
6
+ #include <vector>
7
+
8
+ #include <qmfcalc/ion_config.h>
9
+ #include <qmfcalc/result.h>
10
+ #include <qmfcalc/rod_config.h>
11
+
12
+
13
+ namespace qmfcalc {
14
+
15
+
16
+ class MatrixMethodCalculator
17
+ {
18
+ public:
19
+ // double get_efficiency(const double mass, const Result::PhaseSpacePoint& initial_point) const;
20
+
21
+ double get_u_max_infinite_length(
22
+ const double mass,
23
+ const IonMotion& ion_motion,
24
+ const double rod_length
25
+ ) const;
26
+
27
+ std::vector<Result::Efficiency> get_efficiency_mass_spectrum_infinite_length(
28
+ const std::array<double, 2>& mass_range,
29
+ const double mass_step,
30
+ const std::vector<IonMotion>& ion_motions,
31
+ const RodConfig& rod_config
32
+ ) const;
33
+
34
+ private:
35
+ const double RF_omega_;
36
+ const double RF_period_;
37
+ };
38
+
39
+
40
+ } // namespace qmfcalc
41
+
42
+
43
+ #endif
@@ -0,0 +1,49 @@
1
+ #ifndef QMFCALC_RESOLUTION_ESTIMATOR_H
2
+ #define QMFCALC_RESOLUTION_ESTIMATOR_H
3
+
4
+
5
+ namespace qmfcalc {
6
+
7
+
8
+ // Result of estimating the mass resolution for a single operating line.
9
+ //
10
+ // In a quadrupole mass filter every mass sits on a scan line through the origin
11
+ // of the (q, a) diagram with slope k = a/q = 2U/V (mass independent for fixed
12
+ // rod voltages, frequency and geometry). The line crosses the tip of the first
13
+ // stability region at q_low (rising a0 branch) and q_high (falling b1 branch).
14
+ // Because q is proportional to 1/m, the transmitted mass band corresponds to the
15
+ // q-window delta_q = q_high - q_low, giving m/dm = q_center / delta_q.
16
+ struct ResolutionEstimate
17
+ {
18
+ bool transmitted; // false if the scan line clears the apex (no window)
19
+ double slope; // k = a/q of the operating line
20
+ double q_low; // crossing on the rising a0 branch
21
+ double q_high; // crossing on the falling b1 branch
22
+ double q_center; // (q_low + q_high) / 2 : transmitted mass center in q
23
+ double a_center; // slope * q_center
24
+ double delta_q; // q_high - q_low
25
+ double mass_resolution; // m/dm = q_center / delta_q
26
+ };
27
+
28
+
29
+ // Estimate the mass resolution from the line through the origin and (q, a).
30
+ // Only the slope a/q matters; q is required to be non-zero.
31
+ ResolutionEstimate estimate_mass_resolution(double q, double a);
32
+
33
+
34
+ // Estimate the mass resolution directly from the operating-line slope k = a/q.
35
+ ResolutionEstimate estimate_mass_resolution_from_slope(double slope);
36
+
37
+
38
+ // Inverse of the estimator: the operating-line slope (i.e. the 2U/V ratio)
39
+ // whose window yields target_resolution. The resolution increases monotonically
40
+ // as the slope approaches the apex slope, so a bisection on (0, apex slope) is
41
+ // used. Returns a negative value if target_resolution is below the minimum
42
+ // achievable. This is the kernel of the future RF/DC amplitude solver.
43
+ double find_slope_for_mass_resolution(double target_resolution);
44
+
45
+
46
+ } // namespace qmfcalc
47
+
48
+
49
+ #endif
@@ -0,0 +1,26 @@
1
+ #ifndef QMFCALC_RESULT_H
2
+ #define QMFCALC_RESULT_H
3
+
4
+
5
+ namespace qmfcalc {
6
+
7
+
8
+ namespace Result {
9
+
10
+
11
+ struct Efficiency
12
+ {
13
+ Efficiency(const double mass, const double transmission) : mass(mass), transmission(transmission) { }
14
+
15
+ const double mass;
16
+ const double transmission;
17
+ };
18
+
19
+
20
+ } // namespace qmfcalc::Result
21
+
22
+
23
+ } // namespace qmfcalc
24
+
25
+
26
+ #endif
@@ -0,0 +1,20 @@
1
+ #ifndef QMFCALC_ROD_CONFIG_H
2
+ #define QMFCALC_ROD_CONFIG_H
3
+
4
+
5
+ namespace qmfcalc {
6
+
7
+
8
+ struct RodConfig
9
+ {
10
+ RodConfig(const double u_max_acceptable, const double length) : u_max_acceptable(u_max_acceptable), length(length) { }
11
+
12
+ const double u_max_acceptable;
13
+ const double length;
14
+ };
15
+
16
+
17
+ } // namespace qmfcalc
18
+
19
+
20
+ #endif
@@ -0,0 +1,57 @@
1
+ #ifndef QMFCALC_STABILITY_DIAGRAM_H
2
+ #define QMFCALC_STABILITY_DIAGRAM_H
3
+
4
+
5
+ #include <cmath>
6
+
7
+
8
+ namespace qmfcalc {
9
+
10
+
11
+ // q value of the apex (tip) of the first stability region, where the rising
12
+ // a0 branch and the falling b1 branch meet.
13
+ constexpr double kFirstStabilityQPeak = 0.706252;
14
+
15
+ // q value where the falling b1 branch returns to a = 0 (right end of the tip).
16
+ constexpr double kFirstStabilityQMax = 0.909104;
17
+
18
+
19
+ // Upper boundary of the first stability region of the Mathieu equation as a
20
+ // single-valued function of q. The region's tip is bounded by two branches that
21
+ // meet at (kFirstStabilityQPeak, apex):
22
+ // - q < q_peak : rising a0 characteristic curve
23
+ // - q >= q_peak : falling b1 characteristic curve
24
+ // Asymptotic series expansions are used for each branch.
25
+ inline double get_first_stability_boundary(const double q, const double q_peak = kFirstStabilityQPeak)
26
+ {
27
+ const double q2 = std::pow(q, 2);
28
+ const double q4 = std::pow(q, 4);
29
+ const double q6 = std::pow(q, 6);
30
+ const double q8 = std::pow(q, 8);
31
+
32
+ if (q < q_peak) {
33
+ const double a0 = -q2 / 2 + 7 * q4 / 128 - 29 * q6 / 2304 + 68687 * q8 / 18874368;
34
+ return -a0;
35
+ } else {
36
+ const double q3 = std::pow(q, 3);
37
+ const double q5 = std::pow(q, 5);
38
+ const double q7 = std::pow(q, 7);
39
+
40
+ const double b1 = 1 - q - q2 / 8 + q3 / 64 + q4 / 1536 + 11 * q5 / 36864 + 49 * q6 / 589824 - 55 * q7 / 9437184 - 265 * q8 / 113246208;
41
+
42
+ return b1;
43
+ }
44
+ }
45
+
46
+
47
+ // a value of the apex of the first stability region (a_peak ~ 0.237).
48
+ inline double get_first_stability_apex_a(const double q_peak = kFirstStabilityQPeak)
49
+ {
50
+ return get_first_stability_boundary(q_peak, q_peak);
51
+ }
52
+
53
+
54
+ } // namespace qmfcalc
55
+
56
+
57
+ #endif