Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 35 additions & 48 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,62 +1,49 @@
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Docs

on:
# Runs on pushes targeting the default branch
push:
branches:
- '*'

# Allows you to run this workflow manually from the Actions tab
branches: [main, dev]
pull_request:
branches: [main, dev]
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: write
pages: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
contents: read

jobs:
# Build job
build:
build-docs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
- uses: actions/checkout@v7
with:
python-version: 3.12
fetch-depth: 0
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip

- name: Requirements
run: |
sudo apt-get update
sudo apt-get install -y python3-sphinx
python -m pip install --upgrade pip
python -m pip install -e ".[docs]"

- name: Build
run: |
cd docs
make clean
rm -f source/code/*
bash autodoc.sh
make html
cd build/html
touch .nojekyll
shell: bash

# Deployment job
- name: deploy
uses: JamesIves/github-pages-deploy-action@releases/v4
- name: Install the package and documentation dependencies
run: python -m pip install -e ".[docs]"
- name: Build the documentation
run: python -m sphinx -W --keep-going -b html docs/source docs/build/html
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/build/html # The folder the action should deploy.
if: github.ref_name == 'main' # Only deploy on pushes to the main branch
path: docs/build/html

deploy-docs:
needs: build-docs
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
concurrency:
group: pages
cancel-in-progress: false
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
2 changes: 1 addition & 1 deletion PQAnalysis/io/restart_file/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def write_restart_file(
mode: FileWritingMode | str = 'w'
) -> None:
"""
API function for reading a restart file.
Write an atomic system to a restart file.

Parameters
----------
Expand Down
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
[![codecov](https://codecov.io/gh/MolarVerse/PQAnalysis/graph/badge.svg?token=IDFK8L6IIQ)](https://codecov.io/gh/MolarVerse/PQAnalysis)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

The main purpose of this package is to provide useful tools for the analysis of the Molecular Dynamics software package [PQ](https://github.com/MolarVerse/PQ). Furthermore, the intent of this package is to enable straightforward implementations of newly developed analysis tools on top of the provided API.
PQAnalysis reads structures, trajectories, velocities and Hessians produced by
[PQ](https://github.com/MolarVerse/PQ). Its command-line and Python interfaces
share parsers, numerical kernels and schema-defined outputs for RDF, MSD, VACF,
vibrational, spectral and momentum analyses.

The future development of this package focuses on two main goals. On the one hand the enhancement of the provided analysis tools and extending its API to be compatible with many other different Molecular Dynamics engines. As this project is only a *hobby* project of the maintainers, any contributions considering enhancement or bug fixes are highly welcomed.
Development focuses on validated analysis methods and support for additional
molecular-dynamics engines. The maintainers develop PQAnalysis in their free
time; focused analysis contributions and bug fixes are welcome.

## Installation

Expand All @@ -19,22 +24,29 @@ Install with pip:

## Development

Clone the PQAnalysis GitHub repository and navigate into the directory:
Clone the repository and install the development, test and documentation
dependencies in an isolated environment:

git clone https://github.com/MolarVerse/PQAnalysis.git
cd PQAnalysis
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev,test,docs]"

Install in editable mode with test dependencies:
Run the test suite with both debug and release runtime type checking:

pip install -e ".[test]"
bash pytest.sh

Run the test suite:
The [developer documentation](https://molarverse.github.io/PQAnalysis/developerGuide/developerGuide.html)
covers package architecture, adding an analysis, scientific validation and the
tag-driven release process. The
[function index](https://molarverse.github.io/PQAnalysis/reference/functions.html)
lists the supported Python entry points directly.

python -m pytest

Use squash merges for pull requests. The pull request title becomes the commit
message on the target branch, so PR titles must follow
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/):
Pull request titles must follow
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/); CI
validates them. Keep individual commits scoped because multi-commit pull
requests may retain their commit history:

feat: add a new analysis command
fix(io): handle missing trajectory data
Expand Down
48 changes: 48 additions & 0 deletions docs/source/_plots/_style.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Shared Matplotlib style for the scientific documentation figures."""

from pathlib import Path

import matplotlib as mpl


PROJECT_ROOT = Path(__file__).resolve().parents[3]

COLORS = {
"blue": "#176c8c",
"green": "#008f72",
"orange": "#c7521c",
"magenta": "#a84d84",
"ink": "#202428",
"muted": "#66717a",
"grid": "#d7dde1",
"shell": "#dcecf2",
}


def apply_style(figsize: tuple[float, float]) -> None:
"""Apply a restrained, colorblind-safe style to one figure."""

mpl.rcParams.update({
"figure.figsize": figsize,
"figure.dpi": 120,
"figure.facecolor": "white",
"savefig.facecolor": "white",
"savefig.bbox": "tight",
"font.size": 9.5,
"axes.labelsize": 10,
"axes.labelcolor": COLORS["ink"],
"axes.edgecolor": COLORS["muted"],
"axes.linewidth": 0.8,
"axes.spines.top": False,
"axes.spines.right": False,
"axes.axisbelow": True,
"axes.grid": True,
"grid.color": COLORS["grid"],
"grid.linewidth": 0.7,
"grid.alpha": 0.8,
"xtick.color": COLORS["ink"],
"ytick.color": COLORS["ink"],
"legend.frameon": False,
"legend.fontsize": 8.5,
"lines.linewidth": 1.8,
})
50 changes: 50 additions & 0 deletions docs/source/_plots/msd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""MSD components and diffusion-fit interval from the validation fixture."""

import matplotlib.pyplot as plt
import numpy as np

from _style import COLORS, PROJECT_ROOT, apply_style


apply_style((7.2, 4.3))

data = np.loadtxt(PROJECT_ROOT / "tests/data/msd/msd_ref_O.dat")
time = data[:, 0] * 0.5
components = data[:, 1:4]
total = np.sum(components, axis=1)

fit_start = len(time) - 20
fit_coefficients = np.polyfit(time[fit_start:], total[fit_start:], 1)
fit = np.polyval(fit_coefficients, time[fit_start:])

figure, axis = plt.subplots()
for values, label, color in zip(
components.T,
(r"$\mathrm{MSD}_x$", r"$\mathrm{MSD}_y$", r"$\mathrm{MSD}_z$"),
(COLORS["blue"], COLORS["green"], COLORS["magenta"]),
):
axis.plot(time, values, color=color, linewidth=1.35, label=label)

axis.plot(time, total, color=COLORS["ink"], linewidth=2.2, label="total")
axis.axvspan(
time[fit_start],
time[-1],
color=COLORS["shell"],
label="fit interval",
)
axis.plot(
time[fit_start:],
fit,
color=COLORS["orange"],
linestyle="--",
linewidth=1.7,
label="linear fit",
)
axis.set_xlabel(r"Lag time $t$ / ps")
axis.set_ylabel(r"Mean square displacement / $\mathrm{\AA}^2$")
axis.set_xlim(time[0], time[-1])
axis.set_ylim(bottom=0.0)
axis.legend(ncol=3, loc="upper left")

figure.tight_layout()
plt.show()
73 changes: 73 additions & 0 deletions docs/source/_plots/rdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Analytic RDF profile used to explain structural features."""

import matplotlib.pyplot as plt
import numpy as np

from _style import COLORS, apply_style


apply_style((7.2, 5.0))

r = np.linspace(0.02, 8.0, 800)
excluded_volume = 1.0 - np.exp(-(r / 1.65)**8)
structure = (
1.0
+ 2.2 * np.exp(-0.5 * ((r - 2.80) / 0.22)**2)
- 0.55 * np.exp(-0.5 * ((r - 3.55) / 0.30)**2)
+ 0.65 * np.exp(-0.5 * ((r - 4.65) / 0.38)**2)
- 0.18 * np.exp(-0.5 * ((r - 5.55) / 0.45)**2)
)
g_r = np.clip(excluded_volume * structure, 0.0, None)

number_density = 0.0334
coordination_integrand = 4.0 * np.pi * number_density * r**2 * g_r
coordination = np.concatenate((
[0.0],
np.cumsum(
0.5
* (coordination_integrand[1:] + coordination_integrand[:-1])
* np.diff(r)
),
))

first_minimum = 3.55
figure, (rdf_axis, coordination_axis) = plt.subplots(
2,
1,
sharex=True,
gridspec_kw={"height_ratios": (2.0, 1.25)},
)

rdf_axis.axvspan(
0.0,
first_minimum,
color=COLORS["shell"],
label="first coordination shell",
)
rdf_axis.plot(r, g_r, color=COLORS["blue"])
rdf_axis.axhline(1.0, color=COLORS["muted"], linestyle=":", linewidth=1.1)
rdf_axis.axvline(
first_minimum,
color=COLORS["orange"],
linestyle="--",
linewidth=1.2,
label="first minimum",
)
rdf_axis.set_ylabel(r"$g(r)$")
rdf_axis.set_ylim(0.0, 3.6)
rdf_axis.legend(loc="upper right")

coordination_axis.plot(r, coordination, color=COLORS["orange"])
coordination_axis.axvline(
first_minimum,
color=COLORS["orange"],
linestyle="--",
linewidth=1.2,
)
coordination_axis.set_xlabel(r"Distance $r$ / $\mathrm{\AA}$")
coordination_axis.set_ylabel(r"$N(r)$")
coordination_axis.set_xlim(0.0, 8.0)
coordination_axis.set_ylim(bottom=0.0)

figure.tight_layout()
plt.show()
Loading
Loading