Contributing#
This page is the entry point for anyone who wants to fix a bug, add a feature or improve the documentation of CrystOD. It describes how the repository is organized, how it is tested and how a release is made.
Getting started#
Bug reports and feature requests go to ahntaeyoung1212/CrystOD#issues. For a wrong or missing symmetry result, attach the input structure (POSCAR or XYZ) and the exact command line; if you compared against another program (Bilbao Crystallographic Server, ISOTROPY/ISODISTORT, AMPLIMODES, phonopy), say which one and paste its answer.
Small fixes (a typo, a docstring, an error message) can go straight into a pull request.
Larger changes are best discussed in an issue first, so that the design is settled before the work is done.
Development setup#
CrystOD is developed on Linux and macOS with Python 3.11 in a conda environment; the
package itself supports Python 3.10 and later (requires-python in pyproject.toml).
conda create -n crystod python=3.11 && conda activate crystod
git clone https://github.com/ahntaeyoung1212/CrystOD.git && cd CrystOD
pip install -e ".[dev]"
The editable install (-e) makes the checkout importable and puts the nine console
scripts (crystod, crystod-group, crystod-bz, crystod-phonon, crystod-mag,
crystod-md, crystod-mol, crystod-xrd, crystod-search) on the path, so an edit
takes effect on the next run. The extras defined in pyproject.toml are:
extra |
contents |
needed for |
|---|---|---|
|
|
the |
|
|
building the documentation |
|
|
everything a contributor needs: linting, executing the tutorial notebooks, building a release |
|
|
a user install with every optional feature |
PySCF (about 500 MB with its dependencies) is optional on purpose: everything except
the --pyscf engines runs without it, and a --pyscf command stops with a one-line
ERROR that names the extra to install (crystod/_optional.py). Do not add an
unconditional import pyscf anywhere.
Running the tests#
The regression suite is testsuite.py at the repository root: plain Python, no
test-runner dependency. Run it from the repository root inside the environment:
python testsuite.py # all 37 sections
python testsuite.py 13 # section 13 only
python testsuite.py 3 33 # sections 3 and 33
Each check prints [PASS] or [FAIL] with its name (and, on failure, the output of
the command it ran); the run ends with Total: N passed, M failed and exits with
status 1 if anything failed. The suite reads its inputs from example/ and writes to
temporary directories, and every individual command has a 900 s timeout. Without
PySCF the PySCF-dependent checks in sections 3, 6 and 33 are skipped with a [SKIP]
line and the rest of the suite still runs; the CI matrix installs [quantum], so
nothing is skipped there.
The MCP server. crystod-mcp/ is a separate package with its own pytest suite
(22 tests: every tool on the bundled inputs plus a protocol round trip); run it after
installing the package next to CrystOD:
pip install -e "./crystod-mcp[test]"
python -m pytest -q crystod-mcp/tests
The mcp job of the Tests workflow runs it on every push.
Section numbers. One number identifies a feature everywhere in the repository:
section N of testsuite.py tests it, example/NN_*/ holds its worked example (the
inputs, plus a line in example/README with the command), and section N of the
command’s page in doc/ documents it (## 13. Isotropy subgroups (--parent) in
doc/crystod-group.md, for example). The docstring at the top of testsuite.py
lists all 37 sections grouped by command: 1 library core, 2-6 crystod, 7-17
crystod-group, 18-20 crystod-bz, 21-27 crystod-phonon, 28-29 crystod-mag,
30-31 crystod-md, 32-34 crystod-mol, 35 Python API, 36 crystod-xrd, 37
crystod-search. A few sections hold the “extras” of a command (aliases, error
messages, removed flags) and have no example directory of their own.
.github/workflows/test.yml runs the full suite on Linux (Python 3.10, 3.12, 3.13)
and macOS (3.12) for every push and pull request to main.
Building the docs#
The documentation is MyST Markdown under doc/, built with Sphinx and
sphinx-book-theme (phonopy-style). With the doc (or dev) extra installed:
sphinx-build -b html -W --keep-going doc doc/_build/html
then open doc/_build/html/index.html. -W turns warnings into errors, and that is
what .github/workflows/docs.yml checks on every push. The published site,
https://mochizuki-tus.github.io/CrystOD/, lives in the laboratory website
repository and is copied there by the maintainer, so the workflow deploys nothing:
it verifies that the build is clean and keeps the HTML as a run artifact. README.md
and the corresponding pages in doc/ (install.md, quickstart.md, the command
tables) are maintained in parallel; change both.
Code style#
ruff.
ruff check .from the repository root must pass;.github/workflows/lint.ymlruns the same command. The configuration isruff.toml: Python 3.10 target, line length 100 for new code, and a deliberately small rule set (syntax errors, undefined and redefined names, invalid comparisons, bareexcept), the rules that point at a bug rather than a preference, each enabled because it is clean on the whole tree. The comments inruff.tomllist the rules and the candidates that are not enabled yet.Docstrings are Google style (
Args:,Returns:,Raises:) with a one-line summary first. Every public function and class needs one; they are whathelp()and the API documentation show.English in code, comments, docstrings, documentation and commit messages.
Errors. The implementation modules double as command-line code and report bad input with
raise SystemExit("ERROR: ..."): one sentence that names the remedy. The API layer (crystod/_api.py) turns that into aValueErrorfor library callers, so a function that follows this rule serves both. A dependency’s traceback is never the message a user sees.Imports. An implementation module may import phonopy, spgrep, pyscf or matplotlib at module level, because the API domain modules load implementation modules lazily (PEP 562).
crystod/__init__.py, the domain modules and any--helppath must stay free of them (import crystodplus all nine domains is measured at about 0.09 s and a test in section 35 keeps it that way).
How to add a feature#
A feature touches the same places every time, in this order:
Implementation module,
crystod/<feature>.py: a function or class that takes plain Python/NumPy inputs and returns data; the terminal report and any HTML or VESTA output are separate functions. Existing modules are the template (crystod/isotropy_subgroup.py,crystod/phonon_subgroups.py).API export: add the public name to
_EXPORTSin the domain module of the command it belongs to (crystod/salc.py,group.py,phonon.py,bz.py,mag.py,md.py,mol.py,xrd.py,search.py) as"name": ("implementation_module", "attribute"). That is the whole registration;crystod.<domain>.namethen resolves lazily.Command line: the flag in
build_parser()ofcrystod/cli/<command>.pyand its branch inmain(), with an example in the--helpepilog. A feature that needs PySCF callsrequire_pyscf_or_exit()before importing it.Tests: checks in the section of that command in
testsuite.py(or a newtest_NN_...function registered in theSECTIONSdict at the bottom of the file), written withrun_cli()andreport()like the neighbouring checks. Assert on the physics (a space group, an irrep label, an amplitude), not only on the exit status, and say where the reference value comes from.Documentation: the numbered section in
doc/<command>.mdwith the command line and its output; the command table inREADME.mdif a new mode was added; an entry indoc/changelog.md.Example:
example/NN_<feature>/with the inputs and a line inexample/README. Generated outputs are git-ignored (example/**/*.html,*.csv,*.chk, …); commit the inputs only.
Commit messages and pull requests#
Commit subjects follow the Conventional-Commits-like pattern already in the log: a
lower-case area prefix, a colon and an imperative summary, as in
doc: add a desktop toggle for the right-hand table of contents or
diagram: URL-selectable k point and embed-friendly level panel. Use the command or
module as the area (phonon:, group:, mol:, diagram:, api:, cli:), or
doc:, test:, ci:, examples: for the surrounding files. The body says why,
and for a symmetry result, what it was validated against. A release commit is simply
CrystOD vX.Y.Z.
A pull request should
target
mainand pass the three workflows (Tests, Lint, docs);add or extend test-suite checks for the change, and update the documentation and the changelog together with the code;
describe the change, the reason and the validation in its description, in English.
Release process#
Bump the version in three files,
pyproject.toml(version),crystod/__init__.py(__version__) andCITATION.cff(versionanddate-released), and add the release section todoc/changelog.md(newest first). The documentation reads its version frompyproject.toml.Run the full suite and the docs build, then build and check the distribution locally:
python -m build && twine check dist/*.Commit, tag and push:
git commit -am "CrystOD v0.4.0" git tag v0.4.0 git push origin main --tags
The tag triggers
.github/workflows/publish.yml: thebuildjob refuses a tag that does not match the three version fields, builds the sdist and the wheel, runstwine checkand stores them as an artifact; thepublishjob then waits for approval in thereleaseenvironment and uploads with PyPI Trusted Publishing (OpenID Connect; no API token is stored in the repository). The one-time setup of the publisher on pypi.org and of thereleaseenvironment on GitHub is spelled out at the top ofpublish.yml.Copy the freshly built documentation to the laboratory website repository.
About DEVELOPMENT.md#
DEVELOPMENT.md is the internal development log: a dated, Japanese-language record
of decisions, validations and dead ends. It is kept outside the public repository
(it is listed in .gitignore) and is not translated. This file is the entry point
for new contributors; anything a contributor needs to know belongs here or in doc/.