diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..9177d9d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,47 @@ +name: Bug Report +description: Report a reproducible bug in VisualizePhonon +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thank you for reporting a bug. Please provide enough detail to reproduce the issue with a minimal OUTCAR when possible. + - type: input + id: title + attributes: + label: Bug Summary + placeholder: Short and descriptive summary of the bug + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + placeholder: | + 1. Run `vaspvis generate -i OUTCAR -f xsf -m 0 -s 1.0` + 2. Observe the error + description: Please provide a minimal, reproducible example. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect to happen instead? + validations: + required: false + - type: textarea + id: logs + attributes: + label: Error Traceback or Logs + description: Include the traceback, command output, or relevant OUTCAR parser message. + render: shell + validations: + required: false + - type: input + id: environment + attributes: + label: Environment + placeholder: "e.g., macOS, Python 3.11, VisualizePhonon 0.0.2" + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..4ddcb44 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +name: Feature Request +description: Suggest a new feature or improvement +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Thank you for your suggestion. Please describe the workflow or visualization need you want VisualizePhonon to support. + - type: input + id: summary + attributes: + label: Summary + placeholder: e.g., Add support for exporting selected modes to another visualization format + validations: + required: true + - type: textarea + id: motivation + attributes: + label: Motivation + description: Why is this feature important? What problem does it solve? + validations: + required: false + - type: textarea + id: proposal + attributes: + label: Proposed Solution + description: Describe how you would like the feature to work. + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: Any alternative solutions or tools you have considered? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..97bc6b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,22 @@ +name: Question +description: Ask a question related to usage or development +labels: [question] +body: + - type: markdown + attributes: + value: | + If you have a question, please provide enough context about your VASP output, command, or development task. + - type: input + id: summary + attributes: + label: Question Summary + placeholder: e.g., How should I visualize one imaginary phonon mode from OUTCAR? + validations: + required: true + - type: textarea + id: context + attributes: + label: Context or Background + description: What are you trying to do? Include relevant commands, files, modes, formats, or errors. + validations: + required: false diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..64aa8b6 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,20 @@ +Python: + - changed-files: + - any-glob-to-any-file: + - src/VisualizePhonon/**/* + - pyproject.toml + - setup.py +Docs: + - changed-files: + - any-glob-to-any-file: + - "*.md" + - docs/**/* +Examples: + - changed-files: + - any-glob-to-any-file: examples/**/* +Test: + - changed-files: + - any-glob-to-any-file: test/**/* +CICD: + - changed-files: + - any-glob-to-any-file: .github/**/* diff --git a/.github/workflows/autorelease.yml b/.github/workflows/autorelease.yml new file mode 100644 index 0000000..d08a79a --- /dev/null +++ b/.github/workflows/autorelease.yml @@ -0,0 +1,89 @@ +name: Auto Release on Version Bump + +on: + push: + branches: + - main + +permissions: + contents: write + id-token: write + +jobs: + autorelease: + name: Check version and create release + runs-on: ubuntu-latest + outputs: + tag_exists: ${{ steps.check_tag.outputs.exists }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Get current version + id: get_version + run: | + VERSION=$(python - <<'PY' + import tomllib + + with open("pyproject.toml", "rb") as f: + print(tomllib.load(f)["project"]["version"]) + PY + ) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Check if tag exists + id: check_tag + run: | + TAG_NAME="v${{ env.VERSION }}" + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "Tag $TAG_NAME already exists. Skipping release." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "Tag $TAG_NAME does not exist. A new release will be created." + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub Release + if: steps.check_tag.outputs.exists == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME="v${{ env.VERSION }}" + echo "Creating release for $TAG_NAME..." + gh release create "$TAG_NAME" \ + --title "$TAG_NAME" \ + --generate-notes + + publish: + name: Build & Publish to PyPI + needs: autorelease + if: needs.autorelease.outputs.tag_exists == 'false' + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/VisualizePhonon + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install build dependencies + run: pip install build + + - name: Build sdist and wheel + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/check_cmdline.yml b/.github/workflows/check_cmdline.yml new file mode 100644 index 0000000..363954d --- /dev/null +++ b/.github/workflows/check_cmdline.yml @@ -0,0 +1,37 @@ +name: Test if cmdlines work + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + check-cmdline: + name: Check CLI + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install package + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Check vaspvis commands + run: | + vaspvis --help + vaspvis generate --help + tmpdir="$(mktemp -d)" + cp test/OUTCAR "$tmpdir/OUTCAR" + cd "$tmpdir" + vaspvis generate -i OUTCAR -f xsf -m 0 -s 1.0 + test -f mode_0.xsf diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..ebf39a6 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,15 @@ +name: Pull Request Labeler + +on: + - pull_request_target + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..75e2a57 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,29 @@ +name: pre-commit + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..2de4074 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,48 @@ +name: Test Suite + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + test: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run tests with coverage + run: pytest --cov=src/ --cov-report=xml + + - name: Upload coverage reports to Codecov + if: ${{ matrix.python-version == '3.12' && env.CODECOV_TOKEN != '' }} + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ env.CODECOV_TOKEN }} + + - name: Build package verification + if: ${{ matrix.python-version == '3.12' }} + run: | + pip install build + python -m build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..acd6d33 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +exclude: ^(examples/(OUTCAR|POSCAR|\.latexmkrc|test\.py)|test/(OUTCAR|test_vibrationalanalysis\.py))$ + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + args: ["--maxkb=2048"] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3c13941 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,95 @@ +# Repository Guide + +## 概要 + +VisualizePhononは、VASPの`OUTCAR`からphonon/vibrational modeを抽出し、 +可視化用ファイルへ変換する小型Pythonパッケージです。主なユーザー向け入口は +`vaspvis`コンソールコマンドです。 + +```bash +vaspvis generate -i OUTCAR -f xsf -m -1 -s 1.0 +``` + +CLIで指定できる出力形式は`xsf`、`xyz`、`asy`です。 + +## リポジトリ構造 + +- `src/VisualizePhonon/` + - `vibrational_analysis.py`: 中核のデータ構造。`VibrationalMode`はASEの + atomsオブジェクト、THz単位の周波数、固有ベクトルを保持します。 + `VibrationAnalysis`はmodeのリストを保持し、mode/frequency/eigenvector + の取得とXSF出力メソッドを提供します。 + - `vibrational_analysis_io.py`: ファイルI/Oとエクスポータ。 + `CreateVibrationAnalysis.load_from_outcar()`と`read_file()`によるVASP + `OUTCAR`の解析、`save_xsf()`、`save_xyz()`、 + `generate_asymptote_phonon_code()`、Asymptote補助関数があります。 + - `cmdline.py`: `argparse`による`vaspvis generate`のCLI実装。 + - `__init__.py`: `VibrationalMode`と`VibrationAnalysis`をパッケージから + exportします。 +- `test/` + - `test_vibrationalanalysis.py`: core class、OUTCAR parser、mode filtering、 + accessor、eigenvector、XSF出力のpytest。 + - `OUTCAR`: テストで使う小さなH2Oの参照OUTCAR。 +- `examples/` + - `OUTCAR`と`POSCAR`: example用のVASP入出力。 + - `test.py`: `OUTCAR`を読み、modeを周波数順に並べ、XSF/XYZ/Asymptote + LaTeX出力を作る例。 + - `mode_*`ファイルの多くは可視化の生成物です。明示的にexample出力の更新を + 求められていない限り、sourceではなく生成物として扱ってください。 +- `pyproject.toml`: package metadata、console script、pytest設定、Black/isort設定。 +- `setup.py`: 最小限のsetuptools shim。 +- `README.md`: install方法、CLI usage、citation、関連ツール。 + +## 主要データフロー + +1. `cmdline.py`の`vaspvis generate`が入力ファイルの存在を確認します。 +2. `vibrational_analysis_io.py`の`read_file()`が`VibrationAnalysis`を作ります。 +3. `CreateVibrationAnalysis.load_from_outcar()`がASEで構造を読み、`NIONS`を + parseし、VASPのvibrational analysis sectionを探し、周波数と固有ベクトルを + 抽出して`VibrationalMode`を作ります。 +4. Imaginary modeは負の周波数として表現されています。 +5. 選択されたmodeをexportします。 + - `save_xsf()`: 静的構造とdisplacement vectorをXSFへ書き出します。 + - `save_xyz()`: sinusoidalに変位させたanimation frameをXYZへ書き出します。 + - `generate_asymptote_phonon_code()`: LaTeX/Asymptote sourceを書き出します。 + +## 開発コマンド + +ローカルインストール: + +```bash +pip install -e . +``` + +開発用extra込みのインストール: + +```bash +pip install -e ".[dev]" +``` + +テスト実行: + +```bash +python -m pytest +``` + +同梱fixtureに対するCLI実行例: + +```bash +vaspvis generate -i test/OUTCAR -f xsf -m 0 -s 1 +``` + +## 今後の作業時の注意 + +- codeは`ase`に依存します。parser/exporterやCIを触る場合は、`pyproject.toml`の + dependencyとworkflowのinstall手順を同期させてください。 +- `pyproject.toml`のversionは`0.0.2`、`src/VisualizePhonon/__init__.py`の + `__version__`は`0.1.0`で不一致です。 +- `cmdline.py`の`--scale`は`float`です。README例のように`1.0`を受け取れます。 +- `vibrational_analysis.py`と`vibrational_analysis_io.py`の両方にXSF writer logic + があります。出力形式を変える場合は挙動をそろえてください。 +- CLIやexample実行後に`mode_*.xsf`、`mode_*.xyz`、`mode_*.tex`、`mode_*.pdf`、 + LaTeX auxiliary filesが増えることがあります。生成物を不用意にsource扱いしないで + ください。 +- 現在のtestは`test/OUTCAR`とVASP風のtext layoutに依存しています。parser変更時は + このfixtureに加え、可能なら別の実OUTCARでも確認してください。 diff --git a/pyproject.toml b/pyproject.toml index 370a813..e79af1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,14 +11,12 @@ authors = [ {name = "Tomohito Amano", email = "amanotomohito040@gmail.com"} ] license = {text = "MIT"} -requires-python = ">=3.7" +requires-python = ">=3.9" classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -27,6 +25,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Chemistry", ] dependencies = [ + "ase>=3.22.0", "numpy>=1.20.0", ] @@ -53,11 +52,11 @@ python_files = "test_*.py" [tool.black] line-length = 88 -target-version = ["py37", "py38", "py39", "py310"] +target-version = ["py39", "py310", "py311", "py312"] [tool.isort] profile = "black" line_length = 88 [project.scripts] -vaspvis = "VisualizePhonon:cmdline.main" \ No newline at end of file +vaspvis = "VisualizePhonon.cmdline:main" diff --git a/setup.py b/setup.py index 8ab824c..6068493 100644 --- a/setup.py +++ b/setup.py @@ -1,2 +1,3 @@ from setuptools import setup -setup() \ No newline at end of file + +setup() diff --git a/src/VisualizePhonon/cmdline.py b/src/VisualizePhonon/cmdline.py index b1f35e4..75516d4 100644 --- a/src/VisualizePhonon/cmdline.py +++ b/src/VisualizePhonon/cmdline.py @@ -20,7 +20,7 @@ def main(): 'xsf', 'xyz', 'asy'], help='Output data format. default to xsf') gen_parser.add_argument('--mode', '-m', type=int, default=-1, help='The vibration mode. -1 for all modes. default to -1.') - gen_parser.add_argument('--scale', '-s', type=int, default=1, + gen_parser.add_argument('--scale', '-s', type=float, default=1.0, help='Scale factor of the eigenvector. default to 1.') # sample :: generate a sample