diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index c06d3621..c40130af 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -34,8 +34,8 @@ jobs: - name: Print Versions run: | - .github/workflows/pip-versions.sh - + .github/workflows/pip-versions.sh + centos-stream: runs-on: ubuntu-latest @@ -60,8 +60,8 @@ jobs: - name: Print Versions run: | - .github/workflows/pip-versions.sh - + .github/workflows/pip-versions.sh + ubuntu: @@ -88,7 +88,7 @@ jobs: - name: Print Versions run: | - .github/workflows/pip-versions.sh + .github/workflows/pip-versions.sh conda: @@ -120,7 +120,7 @@ jobs: - name: Print Versions run: | - .github/workflows/conda-versions.sh + .github/workflows/conda-versions.sh mpich: runs-on: ubuntu-latest @@ -145,4 +145,7 @@ jobs: - name: Print Versions run: | - .github/workflows/pip-versions.sh \ No newline at end of file + .github/workflows/pip-versions.sh + + build-docs: + uses: ./.github/workflows/docs-build.yml diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 00000000..7d49d10e --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,42 @@ +on: + workflow_call: + inputs: + upload-artifact: + required: false + type: boolean + default: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 + with: + activate-environment: po3 + channels: conda-forge,defaults + auto-update-conda: true + environment-file: docs-environment.yml + + - name: Install PyORBIT3 + shell: bash -l {0} + run: | + # @woodtp -- MPI isn't required to build docs and enabling it causes Sphinx to segfault. + conda activate po3 + pip install --config-settings=setup-args="-DUSE_MPI='none'" . + + - name: Build Documentation + shell: bash -l {0} + run: | + conda activate po3 + cd doc + make html + + - name: Upload artifact + if: ${{ inputs.upload-artifact }} + uses: actions/upload-pages-artifact@v3 + with: + path: './doc/build/html' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ce5f24dc..4bfcdd70 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,12 +1,9 @@ -# Simple workflow for deploying static content to GitHub Pages name: Deploy Documentation to Pages on: - # Runs on pushes targeting the default branch push: branches: ["main"] - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages @@ -22,29 +19,18 @@ concurrency: cancel-in-progress: false jobs: - # Single deploy job since we're just deploying + build: + uses: ./.github/workflows/docs-build.yml + with: + upload-artifact: true + deploy: + needs: build + runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v2 - with: - activate-environment: po3 - environment-file: docs-environment.yml - - uses: ammaraskar/sphinx-action@master - with: - docs-folder: "doc/" - # - name: Setup Pages - # uses: actions/configure-pages@v5 - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: './doc/build/html' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 47650c0e..cddf9522 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ PyORBIT.egg-info *.so .*.swp .eggs +.venv +_autosummary/ diff --git a/README.md b/README.md index 8e5b4277..b896f29f 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,16 @@ The following commands may require root access.
Click for Ubuntu-based distributions - + ```bash apt-get update apt-get install -y build-essential python3 libfftw3-dev python3-venv libpython3-dev pkg-config git - ``` + ```
Click for Redhat-based distributions - + ```bash dnf group install -y "Development Tools" dnf install -y python3-devel fftw3-devel @@ -47,16 +47,18 @@ The following commands may require root access.
Click for Mac - + Install Homebrew, make sure that homebrew programs are in the **$PATH** (optional step in Homebrew installation) + ```bash brew install pkg-config fftw ```
- + #### Create Python virtual environment Make sure that you have the correct python version installed. We require python>3.9.
Create virtual environment. + ``` python3 -m venv .po3 . .po3/bin/activate @@ -70,6 +72,7 @@ The following commands may require root access. First of all make sure you have conda installed and development packages.
Development packages for Ubuntu: + ``` apt update -y apt install -y curl gpg git build-essential @@ -87,18 +90,18 @@ pip install -U meson-python setuptools setuptools-scm ## 3. Build -If you plan to modify PyORBIT's code, install it in editable mode. +If you plan to modify PyORBIT's code, install it in editable mode. You will NOT need to rebuild after modifications to the code. [Meson](MesonBuild.md) will rebuild as necessary on import. ``` pip install --no-build-isolation --editable . ``` Alternatively if you don't plan to modify PyORBIT's code + ``` pip install . ``` - ## 4. Run full SNS linac example Navigate to your **examples** directory and launch tracking of SNS linac. @@ -116,9 +119,9 @@ pip install --config-settings=setup-args="-DUSE_MPI=none" . Above will build PyORBIT without MPI even if MPI is present. You can change that option to `mpich`, `ompi`, `none` or `auto` (default).
| MPI flavor | Installation command | |---------------|--------------| -| No MPI | `pip install --config-settings=setup-args="-DUSE_MPI=none" .` | -| The first found MPI installation if any | `pip install --config-settings=setup-args="-DUSE_MPI=auto" .` | -| OpenMPI | `pip install --config-settings=setup-args="-DUSE_MPI=ompi" .` | +| No MPI | `pip install --config-settings=setup-args="-DUSE_MPI=none" .` | +| The first found MPI installation if any | `pip install --config-settings=setup-args="-DUSE_MPI=auto" .` | +| OpenMPI | `pip install --config-settings=setup-args="-DUSE_MPI=ompi" .` | | MPICH | `pip install --config-settings=setup-args="-DUSE_MPI=mpich" .` | Meson uses PKG_CONFIG to discover packages. It could be useful to help it to find your MPI installation: diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 00000000..ca5796cb --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +_doxygen +source/api diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..e5bfb095 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile clean + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + rm -rf "$(SOURCEDIR)"/_doxygen "$(SOURCEDIR)"/api "$(SOURCEDIR)"/_autosummary + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..747ffb7b --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/placeholder b/doc/source/_static/.gitkeep similarity index 100% rename from doc/placeholder rename to doc/source/_static/.gitkeep diff --git a/doc/source/_templates/.gitkeep b/doc/source/_templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..8f0fd2f5 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,78 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent / "py/")) + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "PyORBIT3" +copyright = "2025, PyORBIT Collaboration" +author = "PyORBIT Collaboration" +release = "v3.0.1" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.coverage", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx_copybutton", + "myst_parser", + "breathe", + "exhale", +] + +napoleon_numpy_docstring = True +autosummary_imported_members = True + +templates_path = ["_templates"] +exclude_patterns = [] + +# -- Breathe and Exhale Options --------------------------------------------------------- +breathe_projects = { + "PyORBIT3": "./_doxygen/xml", +} +breathe_default_project = "PyORBIT3" + +doxyfile = "\n".join( + [ + "INPUT = ../../src", + "EXCLUDE_PATTERNS = *wrap* *_init.cc", + "PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS", + 'PREDEFINED += PyObject_HEAD="PyObject ob_base;"', + ] +) + +exhale_args = { + "containmentFolder": "./api", + "rootFileName": "pyorbit_root.rst", + "doxygenStripFromPath": "..", + "rootFileTitle": "PyORBIT3 C++ API", + "createTreeView": True, + "exhaleExecutesDoxygen": True, + "exhaleDoxygenStdin": doxyfile, +} + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "pydata_sphinx_theme" +html_static_path = ["_static"] +html_show_source_link = True +html_theme_options = { + "github_url": "https://github.com/PyORBIT-Collaboration/PyORBIT3", + "logo": { + "text": "PyORBIT3", + }, +} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..0afbf680 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,31 @@ +.. PyORBIT3 documentation master file, created by + sphinx-quickstart on Mon Dec 15 20:07:23 2025. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +PyORBIT3 +======== + +.. toctree:: + :maxdepth: 2 + :caption: Getting Started + + install.md + +.. toctree:: + :maxdepth: 2 + :caption: Modules + + modules + +.. toctree:: + :maxdepth: 2 + :caption: C++ API + + api/pyorbit_root.rst + +Indices and tables +================== +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/install.md b/doc/source/install.md new file mode 120000 index 00000000..fe840054 --- /dev/null +++ b/doc/source/install.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/doc/source/modules.rst b/doc/source/modules.rst new file mode 100644 index 00000000..d8cb2ccd --- /dev/null +++ b/doc/source/modules.rst @@ -0,0 +1,42 @@ +Modules +======= +.. toctree:: + :maxdepth: 2 + :caption: Modules + +.. autosummary:: + :toctree: _autosummary/ + :recursive: + + orbit.aperture + orbit.bumps + orbit.bunch_generators + orbit.bunch_utils + orbit.collimation + orbit.core + orbit.diagnostics + orbit.errors + orbit.fieldtracker + orbit.foils + orbit.impedances + orbit.injection + orbit.kickernodes + orbit.lattice + orbit.matching + orbit.matrix_lattice + orbit.orbit_correction + orbit.parsers + orbit.py_linac.errors + orbit.py_linac.lattice + orbit.py_linac.lattice_modifications + orbit.py_linac.linac_parsers + orbit.py_linac.materials + orbit.py_linac.orbit_correction + orbit.py_linac.overlapping_fields + orbit.py_linac.rf_field_readers + orbit.rf_cavities + orbit.space_charge + orbit.teapot + orbit.teapot_base + orbit.time_dep + orbit.utils diff --git a/docs-environment.yml b/docs-environment.yml new file mode 100644 index 00000000..f27037c3 --- /dev/null +++ b/docs-environment.yml @@ -0,0 +1,20 @@ +name: docs +channels: + - conda-forge +dependencies: + - doxygen + - exhale + - pydata-sphinx-theme + - python=3.13 + - sphinx + - sphinx-copybutton + - myst-parser + - breathe + - standard-imghdr + - fftw + - numpy + - scipy + - matplotlib + - meson + - pkg-config + - ninja diff --git a/src/mpi/orbit_mpi.cc b/src/mpi/orbit_mpi.cc index 8a36065e..2dc97e71 100644 --- a/src/mpi/orbit_mpi.cc +++ b/src/mpi/orbit_mpi.cc @@ -5,6 +5,7 @@ #include #include +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) /** A C wrapper around MPI_Init. */ int ORBIT_MPI_Init(){ int res = 0; @@ -620,3 +621,4 @@ int ORBIT_MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count){ #endif return res; } +#endif // !defined(DOXYGEN_SHOULD_SKIP_THIS) diff --git a/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc b/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc index 2063b6ae..0a361326 100644 --- a/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc +++ b/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc @@ -337,10 +337,6 @@ void BunchTwissAnalysis::computeBunchMoments(Bunch* bunch, int order, int disper } - - - -/** Returns the centered correlation <(x-)*(y-)> = - * */ double BunchTwissAnalysis::getCorrelation(int ic, int jc){ if(ic < 0 || ic > 5 || jc < 0 || jc >5) return 0.; return (corr_arr[ic+6*jc] - avg_arr[ic]* avg_arr[jc]); diff --git a/src/orbit/BunchDiagnostics/BunchTwissAnalysis.hh b/src/orbit/BunchDiagnostics/BunchTwissAnalysis.hh index df6e2b4b..deaa20ee 100644 --- a/src/orbit/BunchDiagnostics/BunchTwissAnalysis.hh +++ b/src/orbit/BunchDiagnostics/BunchTwissAnalysis.hh @@ -26,7 +26,16 @@ class BunchTwissAnalysis: public OrbitUtils::CppPyWrapper /** Performs the Twiss analysis of the bunch */ void analyzeBunch(Bunch* bunch); - /** Returns the centered correlation <(x-)*(y-)> = - * */ + /** + * @brief Returns the centered correlation between two components. + * + * \f$\langle (x-\langle x\rangle)(y-\langle y\rangle)\rangle + * = \langle x y\rangle - \langle x\rangle\langle y\rangle\f$ + * + * @param ic Index of the first component (x). + * @param jc Index of the second component (y). + * @return The centered correlation value as a double. + */ double getCorrelation(int ic, int jc); /** Returns the average value for coordinate with index ic */ diff --git a/src/orbit/MaterialInteractions/numrecipes.cc b/src/orbit/MaterialInteractions/numrecipes.cc index eadd37ee..a38dd327 100644 --- a/src/orbit/MaterialInteractions/numrecipes.cc +++ b/src/orbit/MaterialInteractions/numrecipes.cc @@ -29,9 +29,8 @@ namespace OrbitUtils{ return sin(x)*exp(-fac1*pow(2*p*sin(x/2), 2))/pow(sin(x/2),4); } - int zbrak(float (*fx)(float, float, float, float), float x1, float x2, - int n, float xb1[], float xb2[], int &nb, float param1, + int n, float *xb1, float *xb2, int &nb, float param1, float param2, float param3) { int nbb, i; @@ -54,7 +53,9 @@ namespace OrbitUtils{ } #undef JMAX +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) #define JMAX 40 +#endif float rtbis(float (*func)(float, float, float, float), float x1, float x2, float xacc, float param1, float param2, float param3) @@ -134,8 +135,10 @@ namespace OrbitUtils{ #undef EPS #undef JMAX +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) #define EPS 1.0e-6 #define JMAX 20 +#endif float qsimp(float (*func)(float, float, float), float a, float b, float p, float fac1) @@ -164,7 +167,9 @@ namespace OrbitUtils{ /* note #undef's at end of file */ +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) #define FUNC(x, p, fac1) ((*rfunc)(x, p, fac1)) +#endif float trapzd(float (*rfunc)(float, float, float), float a, float b, int n, float p, float fac1) diff --git a/src/orbit/MaterialInteractions/numrecipes.hh b/src/orbit/MaterialInteractions/numrecipes.hh index 25f884e0..8059db80 100644 --- a/src/orbit/MaterialInteractions/numrecipes.hh +++ b/src/orbit/MaterialInteractions/numrecipes.hh @@ -16,7 +16,54 @@ namespace OrbitUtils{ float fstep(float s, float r_o, float pr_o, float theta); float rfunc(float x, float p, float fac1); - int zbrak(float (*fx)(float, float, float, float), float x1, float x2, int n, float xb1[], float xb2[], int &nb, float param1, + + /** + * @brief Bracket subintervals where a function changes sign. + * + * Scans the interval [x1, x2] by dividing it into @p n equal subintervals + * and records subinterval endpoints where the function @p fx changes sign + * (i.e., a root is bracketed). + * + * The function @p fx must have the signature: + * float fx(float x, float p1, float p2, float p3); + * It is evaluated at the grid points x1, x1 + dx, x1 + 2*dx, ..., x2, + * where dx = (x2 - x1) / n. + * + * When an interval [xb1[k], xb2[k]] is found such that fx(xb1[k]) and + * fx(xb2[k]) have opposite signs (fc*fp <= 0), the endpoints are stored + * in the output arrays xb1 and xb2 at the same index k (1-based indexing + * is used internally; the caller should account for this). + * + * @param fx Pointer to the function to examine. Signature: + * float fx(float x, float param1, float param2, float param3). + * @param x1 Left end of the search interval. + * @param x2 Right end of the search interval. + * @param n Number of equal subintervals to split [x1, x2] into. + * @param xb1 Output array to receive left endpoints of bracketing subintervals. + * Must be large enough to hold up to the capacity indicated by the + * integer referenced by @p nb (see @p nb description). + * @param xb2 Output array to receive right endpoints of bracketing subintervals. + * Same required capacity as @p xb1. + * @param nb Reference to an integer indicating the maximum number of bracket + * intervals the caller can accept on input; on return this integer + * is set to the actual number of brackets found (nb <= original nb). + * @param param1 First user parameter passed through to @p fx. + * @param param2 Second user parameter passed through to @p fx. + * @param param3 Third user parameter passed through to @p fx. + * + * @return Returns 0 on completion. If the number of found brackets reaches the + * input capacity (nb), the function returns immediately with xb1/xb2 + * filled up to that capacity and nb set equal to that capacity. + * + * @note + * - The function uses sequential evaluations of @p fx and treats a zero or a + * sign change (fc*fp <= 0.0) as a bracket. Adjacent subintervals that both + * satisfy this condition will each be reported. + * - The implementation uses 1-based indexing internally when filling xb1/xb2. + * The caller should allocate arrays accordingly and interpret the filled + * entries consistent with the calling convention used in the surrounding code. + */ + int zbrak(float (*fx)(float, float, float, float), float x1, float x2, int n, float *xb1, float *xb2, int &nb, float param1, float param2, float param3); float rtbis(float (*func)(float, float, float, float), float x1, float x2, float xacc, float param1, float param2, float param3); float bessj0(float x); diff --git a/src/spacecharge/UniformEllipsoidFieldCalculator.cc b/src/spacecharge/UniformEllipsoidFieldCalculator.cc index 42fc6970..811eb52d 100644 --- a/src/spacecharge/UniformEllipsoidFieldCalculator.cc +++ b/src/spacecharge/UniformEllipsoidFieldCalculator.cc @@ -15,6 +15,7 @@ using namespace OrbitUtils; //macros for max and min +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) #ifndef max #define max( a, b ) ( ((a) > (b)) ? (a) : (b) ) #endif @@ -22,6 +23,7 @@ using namespace OrbitUtils; #ifndef min #define min( a, b ) ( ((a) < (b)) ? (a) : (b) ) #endif +#endif /** Constructor. There is no parameters */ UniformEllipsoidFieldCalculator::UniformEllipsoidFieldCalculator(): CppPyWrapper(NULL) diff --git a/src/utils/Random.cc b/src/utils/Random.cc index c19bafca..4725232a 100644 --- a/src/utils/Random.cc +++ b/src/utils/Random.cc @@ -21,6 +21,7 @@ using namespace OrbitUtils; // By default the random generator is initialized using a random seed (current time) +#if !defined(DOXYGEN_SHOULD_SKIP_THIS) std::mt19937 mt(time(0)); void Random::seed(int seed){ @@ -31,3 +32,4 @@ void Random::seed(int seed){ double Random::ran1(){ return ((double) mt() / (mt.max())); } +#endif