├── .coveragerc ├── .gitattributes ├── .github ├── release.yml └── workflows │ ├── changelog.yml │ ├── ci.yml │ ├── codeql-analysis.yml │ └── draft-pdf.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.txt ├── README.md ├── configs └── .gitignore ├── data ├── .gitignore ├── external │ ├── .gitignore │ ├── V2NiSe4.cif │ └── Zn2B2PbO6.cif ├── interim │ ├── .gitignore │ ├── V2NiSe4.csv │ └── Zn2B2PbO6.csv ├── preprocessed │ ├── .gitignore │ └── examples │ │ ├── V4Ni2Se8,volume=243,uid=8b92.png │ │ └── Zn8B8Pb4O24,volume=623,uid=b62a.png └── raw │ └── .gitignore ├── docs ├── Makefile ├── _static │ ├── .gitignore │ ├── favicon-32x32.png │ └── favicon.ico ├── authors.md ├── changelog.md ├── conf.py ├── contributing.md ├── examples.md ├── index.md ├── license.md ├── logo.png ├── notebooks │ ├── 1.0-xtal2png-tutorial.nblink │ ├── 2.0-materials-project-feature-ranges.nblink │ ├── 2.0.1-materials-project-feature-ranges-conventional.nblink │ ├── 2.1-xtal2png-cnn-classification.nblink │ ├── 2.2-xgboost-matbench-benchmark.nblink │ ├── 3.0-denoising-diffusion.nblink │ └── 3.1-imagen-pytorch.nblink ├── readme.md ├── requirements.txt └── scripts │ ├── denoising_diffusion_pytorch_example.md │ ├── denoising_diffusion_pytorch_pretrained_sample.md │ └── imagen_pytorch_example.md ├── environment.yml ├── equations.nb ├── models └── .gitignore ├── notebooks ├── 1.0-xtal2png-tutorial.ipynb ├── 2.0-materials-project-feature-ranges.ipynb ├── 2.0.1-materials-project-feature-ranges-conventional.ipynb ├── 2.1-xtal2png-cnn-classification.ipynb ├── 2.1.1-xtal2png-cnn-regression.ipynb ├── 2.2-xgboost-matbench-benchmark.ipynb ├── 3.0-denoising-diffusion.ipynb ├── 3.1-imagen-pytorch.ipynb ├── 3.1.1-imagen-pytorch-100-epochs.ipynb ├── README.md ├── data │ └── preprocessed │ │ ├── V4Ni2Se8,volume=243,uid=0674.png │ │ └── Zn8B8Pb4O24,volume=623,uid=9376.png ├── pillow_grayscale_example.ipynb └── template.ipynb ├── pyproject.toml ├── references └── .gitignore ├── reports ├── figures │ ├── .gitignore │ ├── Zn8B8Pb4O24,volume=623,uid=bc2d.png │ ├── Zn8B8Pb4O24,volume=623,uid=bc2d_upsampled.png │ ├── a_hist.html │ ├── example-and-legend.png │ ├── lattice_a_conventional.png │ ├── lattice_a_matplotlibified.png │ ├── lattice_a_plotly_default.png │ ├── legend.png │ ├── mp-time-split-train-fold-0-forced-equimolar.png │ ├── original-decoded.png │ ├── xtal2png-docs-qr-code.png │ └── xtal2png-imagen-pytorch-epoch=1999-6x5.png ├── paper.bib ├── paper.md └── parameters-notes.txt ├── requirements.txt ├── scripts ├── denoising_diffusion_pytorch_example.py ├── denoising_diffusion_pytorch_pretrained_sample.py ├── imagen_pytorch_example.py ├── run_grayskull.py └── train_model.py ├── setup.cfg ├── setup.py ├── src └── xtal2png │ ├── __init__.py │ ├── cli.py │ ├── core.py │ └── utils │ ├── HKUST-1.cif │ ├── README.md │ ├── V2NiSe4.cif │ ├── Zn2B2PbO6.cif │ ├── __init__.py │ ├── data.py │ ├── mp_cifs.bib │ └── plotting.py ├── tests ├── __init__.py ├── cli_test.py ├── conftest.py ├── customization_test.py ├── decoding_test.py ├── encoding_test.py ├── methods_test.py └── test_files │ └── disordered_structure.cif └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | source = xtal2png 5 | # omit = bad_file.py 6 | 7 | [paths] 8 | source = 9 | src/ 10 | */site-packages/ 11 | 12 | [report] 13 | # Regexes for lines to exclude from consideration 14 | exclude_lines = 15 | # Have to re-enable the standard pragma 16 | pragma: no cover 17 | 18 | # Don't complain about missing debug-only code: 19 | def __repr__ 20 | if self\.debug 21 | 22 | # Don't complain if tests don't hit defensive assertion code: 23 | raise AssertionError 24 | raise NotImplementedError 25 | 26 | # Don't complain if non-runnable code isn't run: 27 | if 0: 28 | if __name__ == .__main__.: 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.html linguist-vendored=false 2 | **/*.html linguist-detectable=false 3 | **/*.ipynb linguist-vendored=false 4 | **/*.ipynb linguist-detectable=false 5 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - ignore-for-release 7 | authors: 8 | - octocat 9 | - dependabot 10 | categories: 11 | - title: Breaking Changes 🛠 12 | labels: 13 | - Semver-Major 14 | - breaking-change 15 | - title: Exciting New Features 🎉 16 | labels: 17 | - Semver-Minor 18 | - enhancement 19 | - title: Other Changes 20 | labels: 21 | - "*" 22 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate changelog 2 | on: 3 | release: 4 | types: [created, edited, published] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | generate-changelog: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - uses: BobAnkh/auto-generate-changelog@master 15 | with: 16 | REPO_NAME: 'sparks-baird/xtal2png' 17 | ACCESS_TOKEN: ${{secrets.CHANGELOG}} 18 | PATH: 'CHANGELOG.md' 19 | COMMIT_MESSAGE: 'docs(CHANGELOG): update release notes' 20 | TYPE: 'feat:Feature,fix:Bug Fixes,docs:Documentation,refactor:Refactor,perf:Performance Improvements' 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions configuration **EXAMPLE**, 2 | # MODIFY IT ACCORDING TO YOUR NEEDS! 3 | # Reference: https://docs.github.com/en/actions 4 | 5 | name: tests 6 | 7 | on: 8 | push: 9 | # Avoid using all the resources/limits available by checking only 10 | # relevant branches and tags. Other branches can be checked via PRs. 11 | branches: [main] 12 | tags: ["v*"] # Push events to matching v*, i.e. v1.0, v20.15.10 13 | pull_request: # Run in every PR 14 | workflow_dispatch: # Allow manually triggering the workflow 15 | schedule: 16 | # Run roughly every 15 days at 00:00 UTC 17 | # (useful to check if updates on dependencies break the package) 18 | - cron: "0 0 1,16 * *" 19 | 20 | concurrency: 21 | group: >- 22 | ${{ github.workflow }}-${{ github.ref_type }}- 23 | ${{ github.event.pull_request.number || github.sha }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | prepare: 28 | runs-on: ubuntu-latest 29 | outputs: 30 | wheel-distribution: ${{ steps.wheel-distribution.outputs.path }} 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: { fetch-depth: 0 } # deep clone for setuptools-scm 34 | - uses: actions/setup-python@v2 35 | with: { python-version: "3.10" } 36 | - name: Lint code 37 | run: pipx run pre-commit run --all-files --show-diff-on-failure 38 | - name: Build package distribution files 39 | run: pipx run tox -e clean,build 40 | - name: Record the path of wheel distribution 41 | id: wheel-distribution 42 | run: echo "::set-output name=path::$(ls dist/*.whl)" 43 | - name: Store the distribution files for use in other stages 44 | # `tests` and `publish` will use the same pre-built distributions, 45 | # so we make sure to release the exact same package that was tested 46 | uses: actions/upload-artifact@v2 47 | with: 48 | name: python-distribution-files 49 | path: dist/ 50 | retention-days: 1 51 | 52 | test: 53 | needs: prepare 54 | strategy: 55 | matrix: 56 | python: 57 | - 3.7 # oldest Python supported by PSF 58 | - "3.10" # newest Python that is stable 59 | platform: 60 | - ubuntu-latest 61 | # - macos-latest 62 | # - windows-latest 63 | runs-on: ${{ matrix.platform }} 64 | steps: 65 | - uses: actions/checkout@v2 66 | - uses: actions/setup-python@v2 67 | with: 68 | python-version: ${{ matrix.python }} 69 | - name: Retrieve pre-built distribution files 70 | uses: actions/download-artifact@v2 71 | with: { name: python-distribution-files, path: dist/ } 72 | - name: Run tests 73 | run: >- 74 | pipx run tox 75 | --installpkg '${{ needs.prepare.outputs.wheel-distribution }}' 76 | -- -rFEx --durations 10 --color yes 77 | - name: Generate coverage report 78 | run: pipx run coverage lcov -o coverage.lcov 79 | - name: Upload partial coverage report 80 | uses: coverallsapp/github-action@master 81 | with: 82 | path-to-lcov: coverage.lcov 83 | github-token: ${{ secrets.github_token }} 84 | flag-name: ${{ matrix.platform }} - py${{ matrix.python }} 85 | parallel: true 86 | 87 | finalize: 88 | needs: test 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Finalize coverage report 92 | uses: coverallsapp/github-action@master 93 | with: 94 | github-token: ${{ secrets.GITHUB_TOKEN }} 95 | parallel-finished: true 96 | 97 | publish: 98 | needs: finalize 99 | if: ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags/') }} 100 | runs-on: ubuntu-latest 101 | steps: 102 | - uses: actions/checkout@v2 103 | - uses: actions/setup-python@v2 104 | with: { python-version: "3.10" } 105 | - name: Retrieve pre-built distribution files 106 | uses: actions/download-artifact@v2 107 | with: { name: python-distribution-files, path: dist/ } 108 | # - name: Build Changelog 109 | # id: github_release 110 | # uses: mikepenz/release-changelog-builder-action@v3 111 | # env: 112 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 113 | # - name: Create Release 114 | # uses: softprops/action-gh-release@v1 115 | # with: 116 | # tag_name: ${{ github.ref }} 117 | # name: ${{ github.ref }} 118 | # body: ${{steps.github_release.outputs.changelog}} 119 | # env: 120 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 121 | # - name: "✏️ Generate release changelog" # updates CHANGELOG.md 122 | # uses: heinrichreimer/github-changelog-generator-action@v2.3 123 | # with: 124 | # token: ${{ secrets.GITHUB_TOKEN }} 125 | - name: Publish Package 126 | env: 127 | # - https://pypi.org/help/#apitoken 128 | # - https://docs.github.com/en/actions/security-guides/encrypted-secrets 129 | TWINE_REPOSITORY: pypi 130 | TWINE_USERNAME: __token__ 131 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 132 | run: pipx run tox -e publish 133 | # - name: grayskull 134 | # run: | 135 | # sleep 60s 136 | # pip install grayskull conda-souschef 137 | # python scripts/run_grayskull.py 138 | # - name: Push meta.yaml to `xtal2png-feedstock` fork 139 | # uses: dmnemec/copy_file_to_another_repo_action@main 140 | # env: 141 | # API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} 142 | # with: 143 | # source_file: 'xtal2png/meta.yaml' 144 | # destination_repo: 'sparks-baird/xtal2png-feedstock' 145 | # destination_folder: 'recipe' 146 | # user_email: 'sterling.baird@utah.edu' 147 | # user_name: 'sgbaird' 148 | # commit_message: 'Update meta.yaml based on latest PyPI version as faster version of regro-cf-autotick-bot)' 149 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | # branches: [ main ] 17 | tags: ["v*"] 18 | # pull_request: 19 | # # The branches below must be a subset of the branches above 20 | # branches: [ main ] 21 | schedule: 22 | - cron: '41 6 * * 6' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | permissions: 29 | actions: read 30 | contents: read 31 | security-events: write 32 | 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | language: [ 'python' ] 37 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 38 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v3 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v2 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | 53 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 54 | # queries: security-extended,security-and-quality 55 | 56 | 57 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 58 | # If this step fails, then you should remove it and run the build manually (see below) 59 | - name: Autobuild 60 | uses: github/codeql-action/autobuild@v2 61 | 62 | # ℹ️ Command-line programs to run using the OS shell. 63 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 64 | 65 | # If the Autobuild fails above, remove it and uncomment the following three lines. 66 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 67 | 68 | # - run: | 69 | # echo "Run, Build Application using script" 70 | # ./location_of_script_within_repo/buildscript.sh 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v2 74 | -------------------------------------------------------------------------------- /.github/workflows/draft-pdf.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths: 4 | - 'reports/paper.md' 5 | - 'reports/paper.bib' 6 | - 'reports/figures/**' 7 | pull_request: 8 | paths: 9 | - 'reports/paper.md' 10 | - 'reports/paper.bib' 11 | - 'reports/figures/**' 12 | 13 | jobs: 14 | paper: 15 | runs-on: ubuntu-latest 16 | name: Paper Draft 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | - name: Build draft PDF 21 | uses: openjournals/openjournals-draft-action@master 22 | with: 23 | journal: joss 24 | # This should be the path to the paper within your repo. 25 | paper-path: reports/paper.md 26 | - name: Upload 27 | uses: actions/upload-artifact@v1 28 | with: 29 | name: paper 30 | # This is the output path where Pandoc will write the compiled 31 | # PDF. Note, this should be the same directory as the input 32 | # paper.md 33 | path: reports/paper.pdf 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | !.isort.cfg 7 | !setup.cfg 8 | *.orig 9 | *.log 10 | *.pot 11 | __pycache__/* 12 | .cache/* 13 | .*.swp 14 | */.ipynb_checkpoints/* 15 | .DS_Store 16 | 17 | # Project files 18 | .ropeproject 19 | .project 20 | .pydevproject 21 | .settings 22 | .idea 23 | .vscode 24 | tags 25 | 26 | # Package files 27 | *.egg 28 | *.eggs/ 29 | .installed.cfg 30 | *.egg-info 31 | 32 | # Unittest and coverage 33 | htmlcov/* 34 | .coverage 35 | .coverage.* 36 | .tox 37 | junit*.xml 38 | coverage.xml 39 | .pytest_cache/ 40 | 41 | # Build and docs folder/files 42 | build/* 43 | dist/* 44 | sdist/* 45 | docs/api/* 46 | docs/_rst/* 47 | docs/_build/* 48 | cover/* 49 | MANIFEST 50 | 51 | # Per-project virtualenvs 52 | .venv*/ 53 | .conda*/ 54 | .python-version 55 | src/xtal2png/meta.yaml 56 | xtal2png/meta.yaml 57 | tmp/** 58 | results/**/model-*.pt 59 | results/**/sample-*.png 60 | notebooks/data/external/structures.pkl 61 | reports/figures/tmp.png 62 | reports/figures/tmp.html 63 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile = black 3 | known_first_party = xtal2png 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: '^docs/conf.py' 2 | 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: check-added-large-files 9 | args: ['--maxkb=10000'] 10 | - id: check-ast 11 | - id: check-json 12 | - id: check-merge-conflict 13 | - id: check-xml 14 | - id: check-yaml 15 | - id: debug-statements 16 | - id: end-of-file-fixer 17 | - id: requirements-txt-fixer 18 | - id: mixed-line-ending 19 | args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows 20 | 21 | # If you want to avoid flake8 errors due to unused vars or imports: 22 | - repo: https://github.com/myint/autoflake 23 | rev: v2.1.1 24 | hooks: 25 | - id: autoflake 26 | args: [ 27 | --in-place, 28 | --remove-all-unused-imports, 29 | # --remove-unused-variables, 30 | ] 31 | 32 | - repo: https://github.com/pycqa/isort 33 | rev: 5.12.0 34 | hooks: 35 | - id: isort 36 | 37 | - repo: https://github.com/psf/black 38 | rev: 23.3.0 39 | hooks: 40 | - id: black 41 | language_version: python3 42 | 43 | # If like to embrace black styles even in the docs: 44 | - repo: https://github.com/asottile/blacken-docs 45 | rev: 1.13.0 46 | hooks: 47 | - id: blacken-docs 48 | name: blacken-docs 49 | description: Run `black` on python code blocks in documentation files 50 | additional_dependencies: [black] 51 | entry: blacken-docs 52 | language: python 53 | language_version: python3 54 | files: '\.(rst|md|markdown|py|tex)$' 55 | args: ["--skip-errors"] 56 | 57 | - repo: https://github.com/PyCQA/flake8 58 | rev: 6.0.0 59 | hooks: 60 | - id: flake8 61 | ## You can add flake8 plugins via `additional_dependencies`: 62 | # additional_dependencies: [flake8-bugbear] 63 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | # Build documentation with MkDocs 12 | #mkdocs: 13 | # configuration: mkdocs.yml 14 | 15 | # Optionally build your docs in additional formats such as PDF 16 | formats: 17 | - pdf 18 | 19 | python: 20 | version: 3.8 21 | install: 22 | - requirements: docs/requirements.txt 23 | - {path: ., method: pip} 24 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | * sgbaird [sterling.baird@utah.edu](mailto:sterling.baird@utah.edu) 4 | * hasan-sayeed [hasan.sayeed@utah.edu](mailto:hasan.sayeed@utah.edu) 5 | * kjappelbaum 6 | * faris-k 7 | * cseeg 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [v0.9.4](https://github.com/sparks-baird/xtal2png/releases/tag/v0.9.4) - 2022-07-29 21:41:07 4 | 5 | 6 | 7 | ## [v0.9.3](https://github.com/sparks-baird/xtal2png/releases/tag/v0.9.3) - 2022-07-29 21:25:35 8 | 9 | 10 | 11 | Fixup references for JOSS manuscript. https://github.com/openjournals/joss-reviews/issues/4528#issuecomment-1199942110 12 | 13 | **Full Changelog**: https://github.com/sparks-baird/xtal2png/compare/v0.9.2...v0.9.3 14 | 15 | ## [v0.9.2](https://github.com/sparks-baird/xtal2png/releases/tag/v0.9.2) - 2022-07-29 16:21:51 16 | 17 | 18 | 19 | ## [v0.9.1](https://github.com/sparks-baird/xtal2png/releases/tag/v0.9.1) - 2022-07-28 22:06:22 20 | 21 | 22 | 23 | ## [v0.8.0](https://github.com/sparks-baird/xtal2png/releases/tag/untagged-1cfe4c6287b8c846dc48) - 2022-07-08 07:13:50 24 | 25 | 26 | 27 | ## [v0.7.0](https://github.com/sparks-baird/xtal2png/releases/tag/v0.7.0) - 2022-06-23 04:40:25 28 | 29 | 30 | 31 | ## [v0.6.3](https://github.com/sparks-baird/xtal2png/releases/tag/v0.6.3) - 2022-06-23 04:09:02 32 | 33 | 34 | 35 | ## [v0.6.2](https://github.com/sparks-baird/xtal2png/releases/tag/v0.6.2) - 2022-06-20 18:47:21 36 | 37 | 38 | 39 | ## [v0.5.1](https://github.com/sparks-baird/xtal2png/releases/tag/v0.5.1) - 2022-06-18 02:40:01 40 | 41 | 42 | 43 | ## [v0.5.0](https://github.com/sparks-baird/xtal2png/releases/tag/v0.5.0) - 2022-06-17 04:13:38 44 | 45 | 46 | 47 | ## [v0.4.0](https://github.com/sparks-baird/xtal2png/releases/tag/v0.4.0) - 2022-06-03 02:01:29 48 | 49 | 50 | 51 | ## [v0.3.0](https://github.com/sparks-baird/xtal2png/releases/tag/v0.3.0) - 2022-05-31 17:28:49 52 | 53 | 54 | 55 | ## [v0.2.1](https://github.com/sparks-baird/xtal2png/releases/tag/v0.2.1) - 2022-05-28 17:12:26 56 | 57 | 58 | 59 | 60 | 61 | 62 | **Full Changelog**: https://github.com/sparks-baird/xtal2png/compare/v0.2.0...v0.2.1 63 | 64 | ## [v0.2.0](https://github.com/sparks-baird/xtal2png/releases/tag/v0.2.0) - 2022-05-28 09:21:45 65 | 66 | - no changes 67 | 68 | 69 | 70 | ## [v0.1.6](https://github.com/sparks-baird/xtal2png/releases/tag/v0.1.6) - 2022-05-27 05:36:46 71 | 72 | 73 | ## What's Changed 74 | ### Other Changes 75 | * minor typos, clarification by @sgbaird in https://github.com/sparks-baird/xtal2png/pull/29 76 | * Fix png2xtal shape mismatch by np.stack-ing on correct axis (and add unit tests) by @sgbaird in https://github.com/sparks-baird/xtal2png/pull/30 77 | 78 | 79 | **Full Changelog**: https://github.com/sparks-baird/xtal2png/compare/v0.1.5...v0.1.6 80 | 81 | ### Bug Fixes 82 | 83 | - general: 84 | - fix logic for when to np.squeeze arrays after disassembly ([ae48ce3](https://github.com/sparks-baird/xtal2png/commit/ae48ce3b32bf30bb27b8338c9f29c4a381269eeb)) ([#30](https://github.com/sparks-baird/xtal2png/pull/30)) 85 | 86 | ## [v0.1.5](https://github.com/sparks-baird/xtal2png/releases/tag/v0.1.5) - 2022-05-27 04:39:02 87 | 88 | 89 | 90 | ## [v0.1.4](https://github.com/sparks-baird/xtal2png/releases/tag/v0.1.4) - 2022-05-27 04:04:54 91 | 92 | 93 | 94 | ## [v0.1.3](https://github.com/sparks-baird/xtal2png/releases/tag/v0.1.3) - 2022-05-24 05:08:44 95 | 96 | `setup.cfg` updated 97 | **Full Changelog**: https://github.com/sparks-baird/xtal2png/compare/v0.1.2...v0.1.3 98 | 99 | ## [v0.1.2](https://github.com/sparks-baird/xtal2png/releases/tag/v0.1.2) - 2022-05-24 04:57:00 100 | 101 | \* *This CHANGELOG was automatically generated by [auto-generate-changelog](https://github.com/BobAnkh/auto-generate-changelog)* 102 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome to `xtal2png` contributor's guide. 4 | 5 | This document focuses on getting any potential contributor familiarized with 6 | the development processes, but [other kinds of contributions] are also appreciated. 7 | 8 | If you are new to using [git] or have never collaborated in a project previously, 9 | please have a look at [contribution-guide.org]. Other resources are also 10 | listed in the excellent [guide created by FreeCodeCamp] [^contrib1]. 11 | 12 | Please notice, all users and contributors are expected to be **open, 13 | considerate, reasonable, and respectful**. When in doubt, 14 | [Python Software Foundation's Code of Conduct] is a good reference in terms of 15 | behavior guidelines. 16 | 17 | ## Issue Reports 18 | 19 | If you experience bugs or general issues with `xtal2png`, please have a look 20 | on the [issue tracker]. 21 | If you don't see anything useful there, please feel free to fire an issue report. 22 | 23 | :::{tip} 24 | Please don't forget to include the closed issues in your search. 25 | Sometimes a solution was already reported, and the problem is considered 26 | **solved**. 27 | ::: 28 | 29 | New issue reports should include information about your programming environment 30 | (e.g., operating system, Python version) and steps to reproduce the problem. 31 | Please try also to simplify the reproduction steps to a very minimal example 32 | that still illustrates the problem you are facing. By removing other factors, 33 | you help us to identify the root cause of the issue. 34 | 35 | ## Discussions 36 | 37 | The discussions page is a great place for topics that don't fit directly into 38 | specific future code development plans (i.e. feature requests) or bugs in 39 | the software. Issues are easily converted to discussions and vice-versa, 40 | so don't worry too much about where to put it if you're not sure. 41 | We want to hear from you! 42 | 43 | ## Documentation Improvements 44 | 45 | You can help improve `xtal2png` docs by making them more readable and coherent, or 46 | by adding missing information and correcting mistakes. 47 | 48 | `xtal2png` documentation uses [Sphinx] as its main documentation compiler. 49 | This means that the docs are kept in the same repository as the project code, and 50 | that any documentation update is done in the same way was a code contribution. 51 | 52 | We use [CommonMark] with [MyST] extensions, meaning most of the documentation files 53 | are Markdown (`.md`) files. 54 | 55 | :::{tip} 56 | Please notice that the [GitHub web interface] provides a quick way of 57 | propose changes in `xtal2png`'s files. While this mechanism can 58 | be tricky for normal code contributions, it works perfectly fine for 59 | contributing to the docs, and can be quite handy. 60 | 61 | If you are interested in trying this method out, please navigate to 62 | the `docs` folder in the source [repository], find which file you 63 | would like to propose changes and click in the little pencil icon at the 64 | top, to open [GitHub's code editor]. Once you finish editing the file, 65 | please write a message in the form at the bottom of the page describing 66 | which changes have you made and what are the motivations behind them and 67 | submit your proposal. 68 | ::: 69 | 70 | When working on documentation changes in your local machine, you can 71 | compile them using [tox] : 72 | 73 | ``` 74 | tox -e docs 75 | ``` 76 | 77 | and use Python's built-in web server for a preview in your web browser 78 | (`http://localhost:8000`): 79 | 80 | ``` 81 | python3 -m http.server --directory 'docs/_build/html' 82 | ``` 83 | 84 | ## Code Contributions 85 | 86 | `xtal2png` has two main functionalities: encoding crystal structures as PNG 87 | images, and decoding appropriately encoded PNG images back to crystal 88 | structures. Where appropriate, we prefer to use 89 | [pymatgen](https://pymatgen.org/)'s built-in functionality rather than write 90 | the functionality from scratch. As the main use-case for `xtal2png` is 91 | generative modeling of crystal structures using models directly from the 92 | image-processing domain, many of `xtal2png`'s features are implemented to make 93 | this process better and smoother. On one hand, we're interested in finding an 94 | optimal representation in conjunction with an image generative model (see 95 | [`hyperparameters`](https://github.com/sparks-baird/xtal2png/issues?q=is%3Aissue+is%3Aopen+label%3Ahyperparameter) 96 | label in issue tracker). In order to know whwat is "better", we also need a 97 | notion of best fit (see 98 | [`notion-of-best`](https://github.com/sparks-baird/xtal2png/issues?q=is%3Aissue+is%3Aopen+label%3Anotion-of-best) 99 | label). The plan is to expose notions of best fit (i.e. metrics) for crystal 100 | generative modeling via 101 | [`matbench-genmetrics`](https://github.com/sparks-baird/matbench-genmetrics), 102 | which is being developed in parallel with but separate from `xtal2png`. 103 | 104 | ### Submit an issue 105 | 106 | Before you work on any non-trivial code contribution it's best to first create 107 | a report in the [issue tracker] to start a discussion on the subject. 108 | This often provides additional considerations and avoids unnecessary work. 109 | 110 | ### Create an environment 111 | 112 | Before you start coding, we recommend creating an isolated [virtual environment] 113 | to avoid any problems with your installed Python packages. 114 | This can easily be done via either [virtualenv]: 115 | 116 | ``` 117 | virtualenv 118 | source /bin/activate 119 | ``` 120 | 121 | or [Miniconda]: 122 | 123 | ``` 124 | conda create -n xtal2png python=3 six virtualenv pytest pytest-cov 125 | conda activate xtal2png 126 | ``` 127 | 128 | ### Clone the repository 129 | 130 | 1. Create an user account on GitHub if you do not already have one. 131 | 132 | 2. Fork the project [repository]: click on the *Fork* button near the top of the 133 | page. This creates a copy of the code under your account on GitHub. 134 | 135 | 3. Clone this copy to your local disk: 136 | 137 | ``` 138 | git clone git@github.com:YourLogin/xtal2png.git 139 | cd xtal2png 140 | ``` 141 | 142 | 4. You should run: 143 | 144 | ``` 145 | pip install -U pip setuptools -e . 146 | ``` 147 | 148 | to be able to import the package under development in the Python REPL. 149 | 150 | 5. Install [pre-commit]: 151 | 152 | ``` 153 | pip install pre-commit 154 | pre-commit install 155 | ``` 156 | 157 | `xtal2png` comes with a lot of hooks configured to automatically help the 158 | developer to check the code being written. 159 | 160 | ### Implement your changes 161 | 162 | 1. Create a branch to hold your changes: 163 | 164 | ``` 165 | git checkout -b my-feature 166 | ``` 167 | 168 | and start making changes. Never work on the main branch! 169 | 170 | 2. Start your work on this branch. Don't forget to add [docstrings] to new 171 | functions, modules and classes, especially if they are part of public APIs. 172 | 173 | 3. Add yourself to the list of contributors in `AUTHORS.rst`. 174 | 175 | 4. When you’re done editing, do: 176 | 177 | ``` 178 | git add 179 | git commit 180 | ``` 181 | 182 | to record your changes in [git]. 183 | 184 | Please make sure to see the validation messages from [pre-commit] and fix 185 | any eventual issues. 186 | This should automatically use [flake8]/[black] to check/fix the code style 187 | in a way that is compatible with the project. 188 | 189 | :::{important} 190 | Don't forget to add unit tests and documentation in case your 191 | contribution adds an additional feature and is not just a bugfix. 192 | 193 | Moreover, writing a [descriptive commit message] is highly recommended. 194 | In case of doubt, you can check the commit history with: 195 | 196 | ``` 197 | git log --graph --decorate --pretty=oneline --abbrev-commit --all 198 | ``` 199 | 200 | to look for recurring communication patterns. 201 | ::: 202 | 203 | 5. Please check that your changes don't break any unit tests with: 204 | 205 | ``` 206 | tox 207 | ``` 208 | 209 | (after having installed [tox] with `pip install tox` or `pipx`). 210 | 211 | You can also use [tox] to run several other pre-configured tasks in the 212 | repository. Try `tox -av` to see a list of the available checks. 213 | 214 | ### Submit your contribution 215 | 216 | 1. If everything works fine, push your local branch to the remote server with: 217 | 218 | ``` 219 | git push -u origin my-feature 220 | ``` 221 | 222 | 2. Go to the web page of your fork and click "Create pull request" 223 | to send your changes for review. 224 | 225 | 226 | Find more detailed information in [creating a PR]. You might also want to open 227 | the PR as a draft first and mark it as ready for review after the feedbacks 228 | from the continuous integration (CI) system or any required fixes. 229 | 230 | ### Troubleshooting 231 | 232 | The following tips can be used when facing problems to build or test the 233 | package: 234 | 235 | 1. Make sure to fetch all the tags from the upstream [repository]. 236 | The command `git describe --abbrev=0 --tags` should return the version you 237 | are expecting. If you are trying to run CI scripts in a fork repository, 238 | make sure to push all the tags. 239 | You can also try to remove all the egg files or the complete egg folder, i.e., 240 | `.eggs`, as well as the `*.egg-info` folders in the `src` folder or 241 | potentially in the root of your project. 242 | 243 | 2. Sometimes [tox] misses out when new dependencies are added, especially to 244 | `setup.cfg` and `docs/requirements.txt`. If you find any problems with 245 | missing dependencies when running a command with [tox], try to recreate the 246 | `tox` environment using the `-r` flag. For example, instead of: 247 | 248 | ``` 249 | tox -e docs 250 | ``` 251 | 252 | Try running: 253 | 254 | ``` 255 | tox -r -e docs 256 | ``` 257 | 258 | 3. Make sure to have a reliable [tox] installation that uses the correct 259 | Python version (e.g., 3.7+). When in doubt you can run: 260 | 261 | ``` 262 | tox --version 263 | # OR 264 | which tox 265 | ``` 266 | 267 | If you have trouble and are seeing weird errors upon running [tox], you can 268 | also try to create a dedicated [virtual environment] with a [tox] binary 269 | freshly installed. For example: 270 | 271 | ``` 272 | virtualenv .venv 273 | source .venv/bin/activate 274 | .venv/bin/pip install tox 275 | .venv/bin/tox -e all 276 | ``` 277 | 278 | 4. [Pytest can drop you] in an interactive session in the case an error occurs. 279 | In order to do that you need to pass a `--pdb` option (for example by 280 | running `tox -- -k --pdb`). 281 | You can also setup breakpoints manually instead of using the `--pdb` option. 282 | 283 | ## Maintainer tasks 284 | 285 | ### Releases 286 | 287 | If you are part of the group of maintainers and have correct user permissions 288 | on [PyPI], the following steps can be used to release a new version for 289 | `xtal2png`: 290 | 291 | 1. Make sure all unit tests are successful. 292 | 2. Tag the current commit on the main branch with a release tag, e.g., `v1.2.3`. 293 | 3. Push the new tag to the upstream [repository], 294 | e.g., `git push upstream v1.2.3` 295 | 4. Verify that the [corresponding GitHub action](https://github.com/sparks-baird/xtal2png/actions) completes successfully 296 | 305 | 306 | A bot on `conda-forge` will recognize the new release on PyPI (typically within a 307 | few hours) and automatically open a pull request (PR) on [xtal2png-feedstock]. See 308 | [updating-feedstock] for detailed instructions. 309 | 310 | [^contrib1]: Even though, these resources focus on open source projects and 311 | communities, the general ideas behind collaborating with other developers 312 | to collectively create software are general and can be applied to all sorts 313 | of environments, including private companies and proprietary code bases. 314 | 315 | 316 | [black]: https://pypi.org/project/black/ 317 | [commonmark]: https://commonmark.org/ 318 | [contribution-guide.org]: http://www.contribution-guide.org/ 319 | [creating a pr]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request 320 | [descriptive commit message]: https://chris.beams.io/posts/git-commit 321 | [docstrings]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html 322 | [first-contributions tutorial]: https://github.com/firstcontributions/first-contributions 323 | [flake8]: https://flake8.pycqa.org/en/stable/ 324 | [git]: https://git-scm.com 325 | [github web interface]: https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/editing-files-in-your-repository 326 | [github's code editor]: https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/editing-files-in-your-repository 327 | [github's fork and pull request workflow]: https://guides.github.com/activities/forking/ 328 | [guide created by freecodecamp]: https://github.com/freecodecamp/how-to-contribute-to-open-source 329 | [miniconda]: https://docs.conda.io/en/latest/miniconda.html 330 | [myst]: https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html 331 | [other kinds of contributions]: https://opensource.guide/how-to-contribute 332 | [pre-commit]: https://pre-commit.com/ 333 | [pypi]: https://pypi.org/ 334 | [pyscaffold's contributor's guide]: https://pyscaffold.org/en/stable/contributing.html 335 | [pytest can drop you]: https://docs.pytest.org/en/stable/usage.html#dropping-to-pdb-python-debugger-at-the-start-of-a-test 336 | [python software foundation's code of conduct]: https://www.python.org/psf/conduct/ 337 | [restructuredtext]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/ 338 | [sphinx]: https://www.sphinx-doc.org/en/master/ 339 | [tox]: https://tox.readthedocs.io/en/stable/ 340 | [virtual environment]: https://realpython.com/python-virtual-environments-a-primer/ 341 | [virtualenv]: https://virtualenv.pypa.io/en/stable/ 342 | 343 | [repository]: https://github.com/sparks-baird/xtal2png 344 | [issue tracker]: https://github.com/sparks-baird/xtal2png/issues 345 | [feedstock]: https://github.com/conda-forge/xtal2png-feedstock 346 | [updating-feedstock]: https://github.com/conda-forge/xtal2png-feedstock#updating-xtal2png-feedstock 347 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | # * builder steps: 3 | # - create environment and build package 4 | # - save environment for runner 5 | # * runner steps: 6 | # - recreate the same environment and install built package 7 | # - optionally execute provided console_script in ENTRYPOINT 8 | # 9 | # Alternatively, remove builder steps, take `environment.docker.yml` from your repo 10 | # and `pip install xtal2png` using an artifact store. Faster and more robust. 11 | 12 | FROM condaforge/mambaforge AS builder 13 | WORKDIR /root 14 | 15 | COPY . /root 16 | RUN rm -rf dist build 17 | 18 | RUN mamba env create -f environment.yml 19 | # RUN pip install -e . # not needed since it's in environment.yml 20 | SHELL ["mamba", "run", "-n", "xtal2png", "/bin/bash", "-c"] 21 | 22 | # Build package 23 | RUN tox -e build 24 | RUN mamba env export -n xtal2png -f environment.docker.yml 25 | 26 | 27 | FROM condaforge/mambaforge AS runner 28 | 29 | # Create default pip.conf. 30 | # Replace value of `index-url` with your artifact store if needed. 31 | RUN echo "[global]\n" \ 32 | "timeout = 60\n" \ 33 | "index-url = https://pypi.org/simple/\n" > /etc/pip.conf 34 | 35 | # Create and become user with lower privileges than root 36 | RUN useradd -u 1001 -m app && usermod -aG app app 37 | USER app 38 | WORKDIR /home/app 39 | 40 | # WORKAROUND: Assure ownership for older versions of docker 41 | RUN mkdir /home/app/.cache 42 | RUN mkdir /home/app/.conda 43 | 44 | # Copy caches, environment.docker.yml and built package 45 | COPY --from=builder --chown=app:app /root/.cache/pip /home/app/.cache/pip 46 | COPY --from=builder --chown=app:app /opt/conda/pkgs /home/app/.conda/pkgs 47 | COPY --from=builder --chown=app:app /root/environment.docker.yml environment.yml 48 | COPY --from=builder --chown=app:app /root/dist/*.whl . 49 | 50 | RUN mamba env create -f environment.yml 51 | RUN mamba clean --packages --yes 52 | RUN rm -rf /home/app/.cache/pip 53 | 54 | # Make RUN commands use the conda environment 55 | SHELL ["conda", "run", "-n", "xtal2png", "/bin/bash", "-c"] 56 | 57 | RUN pip install ./*.whl 58 | 59 | # Expose a port for a web-service for instance. 60 | # EXPOSE 80/tcp 61 | 62 | # Code to run when container is started. Replace `YOUR_CONSOLE_SCRIPT` with the 63 | # value of `console_scripts` in section `[options.entry_points]` of `setup.cfg`. 64 | # ENTRYPOINT ["conda", "run", "--no-capture-output", "-n", "xtal2png", "YOUR_CONSOLE_SCRIPT"] 65 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 sgbaird 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Project generated with PyScaffold](https://img.shields.io/badge/-PyScaffold-005CA0?logo=pyscaffold)](https://pyscaffold.org/) 2 | [![ReadTheDocs](https://readthedocs.org/projects/xtal2png/badge/?version=latest)](https://xtal2png.readthedocs.io/en/stable/) 3 | [![Coveralls](https://img.shields.io/coveralls/github/sparks-baird/xtal2png/main.svg)](https://coveralls.io/r/sparks-baird/xtal2png) 4 | [![PyPI-Server](https://img.shields.io/pypi/v/xtal2png.svg)](https://pypi.org/project/xtal2png/) 5 | [![Conda-Forge](https://img.shields.io/conda/vn/conda-forge/xtal2png.svg)](https://anaconda.org/conda-forge/xtal2png) 6 | ![Lines of code](https://img.shields.io/tokei/lines/github/sparks-baird/xtal2png) 7 | [![status](https://joss.theoj.org/papers/0c704f6ae9739c1e97e05ae0ad57aecb/status.svg)](https://joss.theoj.org/papers/0c704f6ae9739c1e97e05ae0ad57aecb) 8 | [![DOI](https://zenodo.org/badge/494577970.svg)](https://zenodo.org/badge/latestdoi/494577970) 9 | [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Twitter)](https://twitter.com/xtal2png) 10 | 14 | 15 | > ⚠️ Manuscript and results using a generative model coming soon ⚠️ 16 | 17 | # xtal2png [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/sparks-baird/xtal2png/blob/main/notebooks/1.0-xtal2png-tutorial.ipynb) 18 | 19 | > Encode/decode a crystal structure to/from a grayscale PNG image for direct use with image-based machine learning models such as [Google's Imagen](https://imagen.research.google/). 20 | 21 | The latest advances in machine learning are often in natural language such as with LSTMs and transformers or image processing such as with GANs, VAEs, and guided diffusion models. Encoding/decoding crystal structures via grayscale PNG images is akin to making/reading a QR code for crystal structures. This allows you, as a materials informatics practitioner, to get streamlined results for new state-of-the-art image-based machine learning models applied to crystal structure. Let's take Google's text-to-image diffusion model, [Imagen](https://imagen.research.google/) ([unofficial](https://github.com/lucidrains/imagen-pytorch)), which [can also be used as an image-to-image model](https://github.com/lucidrains/imagen-pytorch#usage). Rather than dig into the code spending hours, days, or weeks modifying, debugging, and playing GitHub phone tag with the developers before you can (maybe) get preliminary results, `xtal2png` lets you get those results using the default instructions on the repository. 22 | 23 | After getting preliminary results, you get to decide whether it's worth it to you to take on the higher-cost/higher-expertise task of modifying the codebase and using a more customized approach. Or, you can stick with the results of `xtal2png`. It's up to you! 24 | 25 | ## Getting Started 26 | 27 | ### Installation 28 | 29 | ```bash 30 | conda create -n xtal2png -c conda-forge xtal2png m3gnet 31 | conda activate xtal2png 32 | ``` 33 | > NOTE: [`m3gnet`](https://github.com/materialsvirtuallab/m3gnet) is an optional dependency that performs surrogate DFT relaxation. 34 | 35 | ### Example 36 | 37 | Here, we use the top-level 38 | [`XtalConverter`](https://xtal2png.readthedocs.io/en/latest/api/xtal2png.html#xtal2png.core.XtalConverter) 39 | class with and without optional relaxation via 40 | [`m3gnet`](https://github.com/materialsvirtuallab/m3gnet). 41 | 42 | ```python 43 | # example_structures is a list of `pymatgen.core.structure.Structure` objects 44 | >>> from xtal2png import XtalConverter, example_structures 45 | >>> 46 | >>> xc = XtalConverter(relax_on_decode=False) 47 | >>> data = xc.xtal2png(example_structures, show=True, save=True) 48 | >>> decoded_structures = xc.png2xtal(data, save=False) 49 | >>> len(decoded_structures) 50 | 2 51 | 52 | >> xc = XtalConverter(relax_on_decode=True) 53 | >> data = xc.xtal2png(example_structures, show=True, save=True) 54 | >> relaxed_decoded_structures = xc.png2xtal(data, save=False) 55 | >> len(relaxed_decoded_structures) 56 | 2 57 | 58 | ``` 59 | 60 | 61 | 62 | ### Output 63 | 64 | ```python 65 | print(example_structures[0], decoded_structures[0], relaxed_decoded_structures[0]) 66 | ``` 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 91 |
Original
76 | 77 | ```python 78 | Structure Summary 79 | Lattice 80 | abc : 5.033788 11.523021 10.74117 81 | angles : 90.0 90.0 90.0 82 | volume : 623.0356027127609 83 | A : 5.033788 0.0 3.0823061808931787e-16 84 | B : 1.8530431062799525e-15 11.523021 7.055815392078867e-16 85 | C : 0.0 0.0 10.74117 86 | PeriodicSite: Zn2+ (0.9120, 5.7699, 9.1255) [0.1812, 0.5007, 0.8496] 87 | PeriodicSite: Zn2+ (4.1218, 5.7531, 1.6156) [0.8188, 0.4993, 0.1504] 88 | ... 89 | ``` 90 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 115 |
Decoded
99 | 100 | ```python 101 | Structure Summary 102 | Lattice 103 | abc : 5.0250980392156865 11.533333333333331 10.8 104 | angles : 90.0 90.0 90.0 105 | volume : 625.9262117647058 106 | A : 5.0250980392156865 0.0 0.0 107 | B : 0.0 11.533333333333331 0.0 108 | C : 0.0 0.0 10.8 109 | PeriodicSite: Zn (0.9016, 5.7780, 3.8012) [0.1794, 0.5010, 0.3520] 110 | PeriodicSite: Zn (4.1235, 5.7554, 6.9988) [0.8206, 0.4990, 0.6480] 111 | ... 112 | ``` 113 | 114 |
116 | 117 | 118 | 119 | 120 | 121 | 122 | 139 | 140 |
Relaxed Decoded
123 | 124 | ```python 125 | Structure Summary 126 | Lattice 127 | abc : 5.026834307381214 11.578854613685237 10.724087971087924 128 | angles : 90.0 90.0 90.0 129 | volume : 624.1953646135236 130 | A : 5.026834307381214 0.0 0.0 131 | B : 0.0 11.578854613685237 0.0 132 | C : 0.0 0.0 10.724087971087924 133 | PeriodicSite: Zn (0.9050, 5.7978, 3.7547) [0.1800, 0.5007, 0.3501] 134 | PeriodicSite: Zn (4.1218, 5.7810, 6.9693) [0.8200, 0.4993, 0.6499] 135 | ... 136 | ``` 137 | 138 |
141 | 142 | The before and after structures match within an expected tolerance; note the round-off error due to encoding numerical data as RGB images which has a coarse resolution of approximately `1/255 = 0.00392`. Note also that the decoded version lacks charge states. The QR-code-like intermediate PNG image is also provided in original size and a scaled version for a better viewing experience: 143 | | 64x64 pixels | Scaled for Better Viewing ([tool credit](https://lospec.com/pixel-art-scaler/)) | Legend | 144 | | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | 145 | | ![Zn8B8Pb4O24,volume=623,uid=bc2d](https://user-images.githubusercontent.com/45469701/169936372-e14a8bba-698a-4fc9-9d4b-fc5e1de7d67f.png) | | | 146 | 147 | Additional examples can be found in [the docs](https://xtal2png.readthedocs.io/en/latest/examples.html). 148 | 149 | ## Limitations and Design Considerations 150 | 151 | There are some limitations and design considerations for `xtal2png`. Here, we cover round-off error, image dimensions, contextual features, and customization. 152 | 153 | ### Round-off 154 | While the round-off 155 | error is a necessary evil for encoding to a [PNG file format](https://en.wikipedia.org/wiki/Portable_Network_Graphics), the unrounded NumPy arrays 156 | can be used directly instead if supported by the image model of interest via 157 | [`structures_to_arrays`](https://xtal2png.readthedocs.io/en/latest/api/xtal2png.html#xtal2png.core.XtalConverter.structures_to_arrays) and [`arrays_to_structures`](https://xtal2png.readthedocs.io/en/latest/api/xtal2png.html#xtal2png.XtalConverter.arrays_to_structures). 158 | 159 | ### Image dimensions 160 | We choose a 161 | $64\times64$ representation by default which supports up to 52 sites within a unit cell. 162 | The maximum number of sites [`max_sites`](https://xtal2png.readthedocs.io/en/latest/api/xtal2png.html#xtal2png.core.XtalConverter) can be adjusted which changes the size of the 163 | representation. A square representation is used for greater compatibility with the 164 | common limitation of image-based models supporting only square image arrays. The choice 165 | of the default sidelength as a base-2 number (i.e. $2^6$) reflects common conventions of 166 | low-resolution images for image-based machine learning tasks. 167 | 168 | ### Contextual features 169 | While the 170 | distance matrix does not directly contribute to the reconstruction in the current 171 | implementation of `xtal2png`, it serves a number of purposes. First, similar to the unit 172 | cell volume and space group information, it can provide additional guidance to the 173 | algorithm. A corresponding example would be the role of background vs. foreground in 174 | classification of wolves vs. huskies; oftentimes classification algorithms will pay 175 | attention to the background (such as presence of snow) in predicting the animal class. 176 | Likewise, providing contextual information such as volume, space group, and a distance 177 | matrix is additional information that can help the models to capture the essence of 178 | particular crystal structures. In a future implementation, we plan to reconstruct 179 | Euclidean coordinates from the distance matrices and homogenize (e.g. via weighted 180 | averaging) the explicit fractional coordinates with the reconstructed coordinates. 181 | 182 | ### Customization 183 | See the [docs](https://xtal2png.readthedocs.io/en/latest/api/xtal2png.html#xtal2png.core.XtalConverter) for the full list of customizable parameters that `XtalConverter` takes. 184 | 185 | ## Installation 186 | 187 | 205 | 206 | ### PyPI (`pip`) installation 207 | 208 | Create and activate a new `conda` environment named `xtal2png` (`-n`) with `python==3.9.*` or your preferred Python version, then install `xtal2png` via `pip`. 209 | 210 | ```bash 211 | conda create -n xtal2png python==3.9.* 212 | conda activate xtal2png 213 | pip install xtal2png 214 | ``` 215 | 216 | ## Editable installation 217 | 218 | In order to set up the necessary environment: 219 | 220 | 1. clone and enter the repository via: 221 | 222 | ```bash 223 | git clone https://github.com/sparks-baird/xtal2png.git 224 | cd xtal2png 225 | ``` 226 | 227 | 2. create and activate a new conda environment (optional, but recommended) 228 | 229 | ```bash 230 | conda env create --name xtal2png python==3.9.* 231 | conda activate xtal2png 232 | ``` 233 | 234 | 3. perform an editable (`-e`) installation in the current directory (`.`): 235 | 236 | ```bash 237 | pip install -e . 238 | ``` 239 | 240 | > **_NOTE:_** Some changes, e.g. in `setup.cfg`, might require you to run `pip install -e .` again. 241 | 242 | Optional and needed only once after `git clone`: 243 | 244 | 3. install several [pre-commit] git hooks with: 245 | 246 | ```bash 247 | pre-commit install 248 | # You might also want to run `pre-commit autoupdate` 249 | ``` 250 | 251 | and checkout the configuration under `.pre-commit-config.yaml`. 252 | The `-n, --no-verify` flag of `git commit` can be used to deactivate pre-commit hooks temporarily. 253 | 254 | 4. install [nbstripout] git hooks to remove the output cells of committed notebooks with: 255 | 256 | ```bash 257 | nbstripout --install --attributes notebooks/.gitattributes 258 | ``` 259 | 260 | This is useful to avoid large diffs due to plots in your notebooks. 261 | A simple `nbstripout --uninstall` will revert these changes. 262 | 263 | Then take a look into the `scripts` and `notebooks` folders. 264 | 265 | 279 | 280 | ## Command Line Interface (CLI) 281 | 282 | Make sure to install the package first per the installation instructions above. Here is 283 | how to access the help for the CLI and a few examples to get you started. 284 | 285 | ### Help 286 | 287 | You can see the usage information of the `xtal2png` CLI script via: 288 | 289 | ```bash 290 | xtal2png --help 291 | ``` 292 | 293 | > ```bash 294 | >Usage: xtal2png [OPTIONS] 295 | > 296 | > xtal2png command line interface. 297 | > 298 | >Options: 299 | > --version Show version. 300 | > -p, --path PATH Crystallographic information file (CIF) filepath 301 | > (extension must be .cif or .CIF) or path to 302 | > directory containing .cif files or processed PNG 303 | > filepath or path to directory containing processed 304 | > .png files (extension must be .png or .PNG). 305 | > Assumes CIFs if --encode flag is used. Assumes 306 | > PNGs if --decode flag is used. 307 | > -s, --save-dir PATH Encode CIF files as PNG images. 308 | > --encode Encode CIF files as PNG images. 309 | > --decode Decode PNG images to CIF files. 310 | > -v, --verbose TEXT Set loglevel to INFO. 311 | > -vv, --very-verbose TEXT Set loglevel to INFO. 312 | > --help Show this message and exit. 313 | > ``` 314 | 315 | ### Examples 316 | 317 | To encode a single CIF file located at `src/xtal2png/utils/Zn2B2PbO6.cif` as a PNG and save the PNG to the `tmp` directory: 318 | 319 | ```bash 320 | xtal2png --encode --path src/xtal2png/utils/Zn2B2PbO6.cif --save-dir tmp 321 | ``` 322 | 323 | To encode all CIF files contained in the `src/xtal2png/utils` directory as a PNG and 324 | save corresponding PNGs to the `tmp` directory: 325 | 326 | ```bash 327 | xtal2png --encode --path src/xtal2png/utils --save-dir tmp 328 | ``` 329 | 330 | To decode a single structure-encoded PNG file located at 331 | `data/preprocessed/Zn8B8Pb4O24,volume=623,uid=b62a.png` as a CIF file and save the CIF 332 | file to the `tmp` directory: 333 | 334 | ```bash 335 | xtal2png --decode --path data/preprocessed/Zn8B8Pb4O24,volume=623,uid=b62a.png --save-dir tmp 336 | ``` 337 | 338 | To decode all structure-encoded PNG file contained in the `data/preprocessed` directory as CIFs and save the CIFs to the `tmp` directory: 339 | 340 | ```bash 341 | xtal2png --decode --path data/preprocessed --save-dir tmp 342 | ``` 343 | 344 | Note that the save directory (e.g. `tmp`) including any parents (e.g. `ab/cd/tmp`) will 345 | be created automatically if the directory does not already exist. 346 | 347 | ## Project Organization 348 | 349 | ``` 350 | ├── AUTHORS.md <- List of developers and maintainers. 351 | ├── CHANGELOG.md <- Changelog to keep track of new features and fixes. 352 | ├── CONTRIBUTING.md <- Guidelines for contributing to this project. 353 | ├── Dockerfile <- Build a docker container with `docker build .`. 354 | ├── LICENSE.txt <- License as chosen on the command-line. 355 | ├── README.md <- The top-level README for developers. 356 | ├── configs <- Directory for configurations of model & application. 357 | ├── data 358 | │ ├── external <- Data from third party sources. 359 | │ ├── interim <- Intermediate data that has been transformed. 360 | │ ├── preprocessed <- The final, canonical data sets for modeling. 361 | │ └── raw <- The original, immutable data dump. 362 | ├── docs <- Directory for Sphinx documentation in rst or md. 363 | ├── environment.yml <- The conda environment file for reproducibility. 364 | ├── models <- Trained and serialized models, model predictions, 365 | │ or model summaries. 366 | ├── notebooks <- Jupyter notebooks. Naming convention is a number (for 367 | │ ordering), the creator's initials and a description, 368 | │ e.g. `1.0-fw-initial-data-exploration`. 369 | ├── pyproject.toml <- Build configuration. Don't change! Use `pip install -e .` 370 | │ to install for development or to build `tox -e build`. 371 | ├── references <- Data dictionaries, manuals, and all other materials. 372 | ├── reports <- Generated analysis as HTML, PDF, LaTeX, etc. 373 | │ └── figures <- Generated plots and figures for reports. 374 | ├── scripts <- Analysis and production scripts which import the 375 | │ actual PYTHON_PKG, e.g. train_model. 376 | ├── setup.cfg <- Declarative configuration of your project. 377 | ├── setup.py <- [DEPRECATED] Use `python setup.py develop` to install for 378 | │ development or `python setup.py bdist_wheel` to build. 379 | ├── src 380 | │ └── xtal2png <- Actual Python package where the main functionality goes. 381 | ├── tests <- Unit tests which can be run with `pytest`. 382 | ├── .coveragerc <- Configuration for coverage reports of unit tests. 383 | ├── .isort.cfg <- Configuration for git hook that sorts imports. 384 | └── .pre-commit-config.yaml <- Configuration of pre-commit git hooks. 385 | ``` 386 | 387 | 388 | 389 | ## Note on PyScaffold 390 | 391 | This project has been set up using [PyScaffold] 4.2.1 and the [dsproject extension] 0.7.1. 392 | 393 | [conda]: https://docs.conda.io/ 394 | [pre-commit]: https://pre-commit.com/ 395 | [Jupyter]: https://jupyter.org/ 396 | [nbstripout]: https://github.com/kynan/nbstripout 397 | [Google style]: http://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings 398 | [PyScaffold]: https://pyscaffold.org/ 399 | [dsproject extension]: https://github.com/pyscaffold/pyscaffoldext-dsproject 400 | 401 | To create the same starting point for this repository, as of 2022-06-01 on Windows you will need the development versions of PyScaffold and extensions, however this will not be necessary once certain bugfixes have been introduced in the next stable releases: 402 | 403 | ```bash 404 | pip install git+https://github.com/pyscaffold/pyscaffold.git git+https://github.com/pyscaffold/pyscaffoldext-dsproject.git git+https://github.com/pyscaffold/pyscaffoldext-markdown.git 405 | ``` 406 | 407 | The following `pyscaffold` command creates a starting point for this repository: 408 | 409 | ```bash 410 | putup xtal2png --github-actions --markdown --dsproj 411 | ``` 412 | 413 | Alternatively, you can edit a file interactively and update and uncomment relevant lines, which saves some of the additional setup: 414 | 415 | ```bash 416 | putup --interactive xtal2png 417 | ``` 418 | 419 | ## Attributions 420 | 421 | - [@michaeldalverson](https://github.com/michaeldalverson) for iterating through various representations during extensive work with crystal GANs. The base representation for `xtal2png` (see [#output](https://github.com/sparks-baird/xtal2png#output)) closely follows a recent iteration (2022-06-13), taking the first layer ($1\times64\times64$) of the $4\times64\times64$ representation and replacing a buffer column/row of zeros with unit cell volume. 422 | -------------------------------------------------------------------------------- /configs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/configs/.gitignore -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file and .gitignore in sub directories 4 | !.gitignore 5 | !raw 6 | !external 7 | !preprocessed 8 | !interim 9 | -------------------------------------------------------------------------------- /data/external/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | !V2NiSe4.cif 6 | !Zn2B2PbO6.cif 7 | -------------------------------------------------------------------------------- /data/external/V2NiSe4.cif: -------------------------------------------------------------------------------- 1 | # generated using pymatgen 2 | data_V2NiSe4 3 | _symmetry_space_group_name_H-M C2/m 4 | _cell_length_a 12.95611800 5 | _cell_length_b 3.39550400 6 | _cell_length_c 6.19321991 7 | _cell_angle_alpha 90.00000000 8 | _cell_angle_beta 116.76664345 9 | _cell_angle_gamma 90.00000000 10 | _symmetry_Int_Tables_number 12 11 | _chemical_formula_structural V2NiSe4 12 | _chemical_formula_sum 'V4 Ni2 Se8' 13 | _cell_volume 243.26142717 14 | _cell_formula_units_Z 2 15 | loop_ 16 | _symmetry_equiv_pos_site_id 17 | _symmetry_equiv_pos_as_xyz 18 | 1 'x, y, z' 19 | 2 '-x, -y, -z' 20 | 3 '-x, y, -z' 21 | 4 'x, -y, z' 22 | 5 'x+1/2, y+1/2, z' 23 | 6 '-x+1/2, -y+1/2, -z' 24 | 7 '-x+1/2, y+1/2, -z' 25 | 8 'x+1/2, -y+1/2, z' 26 | loop_ 27 | _atom_type_symbol 28 | _atom_type_oxidation_number 29 | V3+ 3.0 30 | Ni2+ 2.0 31 | Se2- -2.0 32 | loop_ 33 | _atom_site_type_symbol 34 | _atom_site_label 35 | _atom_site_symmetry_multiplicity 36 | _atom_site_fract_x 37 | _atom_site_fract_y 38 | _atom_site_fract_z 39 | _atom_site_occupancy 40 | V3+ V0 4 0.24525600 0.50000000 0.68740300 1 41 | Ni2+ Ni1 2 0.00000000 0.00000000 0.00000000 1 42 | Se2- Se2 4 0.10484600 0.00000000 0.44654500 1 43 | Se2- Se3 4 0.13939000 0.50000000 0.97786100 1 44 | -------------------------------------------------------------------------------- /data/external/Zn2B2PbO6.cif: -------------------------------------------------------------------------------- 1 | # generated using pymatgen 2 | data_Zn2B2PbO6 3 | _symmetry_space_group_name_H-M Pccn 4 | _cell_length_a 5.03378800 5 | _cell_length_b 11.52302100 6 | _cell_length_c 10.74117000 7 | _cell_angle_alpha 90.00000000 8 | _cell_angle_beta 90.00000000 9 | _cell_angle_gamma 90.00000000 10 | _symmetry_Int_Tables_number 56 11 | _chemical_formula_structural Zn2B2PbO6 12 | _chemical_formula_sum 'Zn8 B8 Pb4 O24' 13 | _cell_volume 623.03560271 14 | _cell_formula_units_Z 4 15 | loop_ 16 | _symmetry_equiv_pos_site_id 17 | _symmetry_equiv_pos_as_xyz 18 | 1 'x, y, z' 19 | 2 '-x, -y, -z' 20 | 3 '-x+1/2, -y+1/2, z' 21 | 4 'x+1/2, y+1/2, -z' 22 | 5 'x+1/2, -y, -z+1/2' 23 | 6 '-x+1/2, y, z+1/2' 24 | 7 '-x, y+1/2, -z+1/2' 25 | 8 'x, -y+1/2, z+1/2' 26 | loop_ 27 | _atom_type_symbol 28 | _atom_type_oxidation_number 29 | Zn2+ 2.0 30 | B3+ 3.0 31 | Pb2+ 2.0 32 | O2- -2.0 33 | loop_ 34 | _atom_site_type_symbol 35 | _atom_site_label 36 | _atom_site_symmetry_multiplicity 37 | _atom_site_fract_x 38 | _atom_site_fract_y 39 | _atom_site_fract_z 40 | _atom_site_occupancy 41 | Zn2+ Zn0 8 0.18117300 0.50072900 0.84958500 1 42 | B3+ B1 8 0.19372900 0.60171000 0.58405600 1 43 | Pb2+ Pb2 4 0.25000000 0.25000000 0.05941000 1 44 | O2- O3 8 0.07797100 0.12464400 0.91056100 1 45 | O2- O4 8 0.16038400 0.62347500 0.97747400 1 46 | O2- O5 8 0.18185200 0.55318900 0.18686500 1 47 | -------------------------------------------------------------------------------- /data/interim/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | !V2NiSe4.csv 6 | !Zn2B2PbO6.csv 7 | -------------------------------------------------------------------------------- /data/preprocessed/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !examples 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /data/preprocessed/examples/V4Ni2Se8,volume=243,uid=8b92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/data/preprocessed/examples/V4Ni2Se8,volume=243,uid=8b92.png -------------------------------------------------------------------------------- /data/preprocessed/examples/Zn8B8Pb4O24,volume=623,uid=b62a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/data/preprocessed/examples/Zn8B8Pb4O24,volume=623,uid=b62a.png -------------------------------------------------------------------------------- /data/raw/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | AUTODOCDIR = api 11 | 12 | # User-friendly check for sphinx-build 13 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1) 14 | $(error "The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/") 15 | endif 16 | 17 | .PHONY: help clean Makefile 18 | 19 | # Put it first so that "make" without argument is like "make help". 20 | help: 21 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 22 | 23 | clean: 24 | rm -rf $(BUILDDIR)/* $(AUTODOCDIR) 25 | 26 | # Catch-all target: route all unknown targets to Sphinx using the new 27 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 28 | %: Makefile 29 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 30 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # Empty directory 2 | -------------------------------------------------------------------------------- /docs/_static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/docs/_static/favicon-32x32.png -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/authors.md: -------------------------------------------------------------------------------- 1 | ```{include} ../AUTHORS.md 2 | :relative-docs: docs/ 3 | :relative-images: 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CHANGELOG.md 2 | :relative-docs: docs/ 3 | :relative-images: 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # This file is execfile()d with the current directory set to its containing dir. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | # 7 | # All configuration values have a default; values that are commented out 8 | # serve to show the default. 9 | 10 | import os 11 | import sys 12 | import shutil 13 | 14 | import sphinx_rtd_theme # noqa 15 | 16 | # -- Path setup -------------------------------------------------------------- 17 | 18 | __location__ = os.path.dirname(__file__) 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | sys.path.insert(0, os.path.join(__location__, "../src")) 24 | 25 | # -- Run sphinx-apidoc ------------------------------------------------------- 26 | # This hack is necessary since RTD does not issue `sphinx-apidoc` before running 27 | # `sphinx-build -b html . _build/html`. See Issue: 28 | # https://github.com/readthedocs/readthedocs.org/issues/1139 29 | # DON'T FORGET: Check the box "Install your project inside a virtualenv using 30 | # setup.py install" in the RTD Advanced Settings. 31 | # Additionally it helps us to avoid running apidoc manually 32 | 33 | try: # for Sphinx >= 1.7 34 | from sphinx.ext import apidoc 35 | except ImportError: 36 | from sphinx import apidoc 37 | 38 | output_dir = os.path.join(__location__, "api") 39 | module_dir = os.path.join(__location__, "../src/xtal2png") 40 | try: 41 | shutil.rmtree(output_dir) 42 | except FileNotFoundError: 43 | pass 44 | 45 | try: 46 | import sphinx 47 | 48 | cmd_line = f"sphinx-apidoc --implicit-namespaces -f -o {output_dir} {module_dir}" 49 | 50 | args = cmd_line.split(" ") 51 | if tuple(sphinx.__version__.split(".")) >= ("1", "7"): 52 | # This is a rudimentary parse_version to avoid external dependencies 53 | args = args[1:] 54 | 55 | apidoc.main(args) 56 | except Exception as e: 57 | print("Running `sphinx-apidoc` failed!\n{}".format(e)) 58 | 59 | # -- General configuration --------------------------------------------------- 60 | 61 | # If your documentation needs a minimal Sphinx version, state it here. 62 | # needs_sphinx = '1.0' 63 | 64 | # Add any Sphinx extension module names here, as strings. They can be extensions 65 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 66 | extensions = [ 67 | "sphinx.ext.autodoc", 68 | "sphinx.ext.intersphinx", 69 | "sphinx.ext.todo", 70 | "sphinx.ext.autosummary", 71 | "sphinx.ext.viewcode", 72 | "sphinx.ext.coverage", 73 | "sphinx.ext.doctest", 74 | "sphinx.ext.ifconfig", 75 | "sphinx.ext.mathjax", 76 | "sphinx.ext.napoleon", 77 | "sphinx_rtd_theme", 78 | "sphinx_copybutton", 79 | "nbsphinx", 80 | "nbsphinx_link", 81 | ] 82 | 83 | # Add any paths that contain templates here, relative to this directory. 84 | templates_path = ["_templates"] 85 | 86 | 87 | # Enable markdown 88 | extensions.append("myst_parser") 89 | 90 | # Configure MyST-Parser 91 | myst_enable_extensions = [ 92 | "amsmath", 93 | "colon_fence", 94 | "deflist", 95 | "dollarmath", 96 | "html_image", 97 | "linkify", 98 | "replacements", 99 | "smartquotes", 100 | "substitution", 101 | "tasklist", 102 | ] 103 | 104 | # The suffix of source filenames. 105 | source_suffix = [".rst", ".md"] 106 | 107 | # The encoding of source files. 108 | # source_encoding = 'utf-8-sig' 109 | 110 | # The master toctree document. 111 | master_doc = "index" 112 | 113 | # General information about the project. 114 | project = "xtal2png" 115 | copyright = "2022, sgbaird" 116 | 117 | # The version info for the project you're documenting, acts as replacement for 118 | # |version| and |release|, also used in various other places throughout the 119 | # built documents. 120 | # 121 | # version: The short X.Y version. 122 | # release: The full version, including alpha/beta/rc tags. 123 | # If you don’t need the separation provided between version and release, 124 | # just set them both to the same value. 125 | try: 126 | from xtal2png import __version__ as version 127 | except ImportError: 128 | version = "" 129 | 130 | if not version or version.lower() == "unknown": 131 | version = os.getenv("READTHEDOCS_VERSION", "unknown") # automatically set by RTD 132 | 133 | release = version 134 | 135 | # The language for content autogenerated by Sphinx. Refer to documentation 136 | # for a list of supported languages. 137 | # language = None 138 | 139 | # There are two options for replacing |today|: either, you set today to some 140 | # non-false value, then it is used: 141 | # today = '' 142 | # Else, today_fmt is used as the format for a strftime call. 143 | # today_fmt = '%B %d, %Y' 144 | 145 | # List of patterns, relative to source directory, that match files and 146 | # directories to ignore when looking for source files. 147 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv"] 148 | 149 | # The reST default role (used for this markup: `text`) to use for all documents. 150 | # default_role = None 151 | 152 | # If true, '()' will be appended to :func: etc. cross-reference text. 153 | # add_function_parentheses = True 154 | 155 | # If true, the current module name will be prepended to all description 156 | # unit titles (such as .. function::). 157 | # add_module_names = True 158 | 159 | # If true, sectionauthor and moduleauthor directives will be shown in the 160 | # output. They are ignored by default. 161 | # show_authors = False 162 | 163 | # The name of the Pygments (syntax highlighting) style to use. 164 | pygments_style = "sphinx" 165 | 166 | # A list of ignored prefixes for module index sorting. 167 | # modindex_common_prefix = [] 168 | 169 | # If true, keep warnings as "system message" paragraphs in the built documents. 170 | # keep_warnings = False 171 | 172 | # If this is True, todo emits a warning for each TODO entries. The default is False. 173 | todo_emit_warnings = True 174 | 175 | 176 | # -- Options for HTML output ------------------------------------------------- 177 | 178 | # The theme to use for HTML and HTML Help pages. See the documentation for 179 | # a list of builtin themes. 180 | html_theme = "sphinx_rtd_theme" 181 | 182 | # Theme options are theme-specific and customize the look and feel of a theme 183 | # further. For a list of options available for each theme, see the 184 | # documentation. 185 | html_theme_options = {"sidebar_width": "300px", "page_width": "1200px"} 186 | 187 | # Add any paths that contain custom themes here, relative to this directory. 188 | # html_theme_path = [] 189 | 190 | # The name for this set of Sphinx documents. If None, it defaults to 191 | # " v documentation". 192 | # html_title = None 193 | 194 | # A shorter title for the navigation bar. Default is the same as html_title. 195 | # html_short_title = None 196 | 197 | # The name of an image file (relative to this directory) to place at the top 198 | # of the sidebar. 199 | html_logo = "logo.png" 200 | 201 | # The name of an image file (within the static path) to use as favicon of the 202 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 203 | # pixels large. 204 | html_favicon = "_static/favicon.ico" 205 | 206 | # Add any paths that contain custom static files (such as style sheets) here, 207 | # relative to this directory. They are copied after the builtin static files, 208 | # so a file named "default.css" will overwrite the builtin "default.css". 209 | html_static_path = ["_static"] 210 | 211 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 212 | # using the given strftime format. 213 | # html_last_updated_fmt = '%b %d, %Y' 214 | 215 | # If true, SmartyPants will be used to convert quotes and dashes to 216 | # typographically correct entities. 217 | html_use_smartypants = True 218 | 219 | # Custom sidebar templates, maps document names to template names. 220 | # html_sidebars = {} 221 | 222 | # Additional templates that should be rendered to pages, maps page names to 223 | # template names. 224 | # html_additional_pages = {} 225 | 226 | # If false, no module index is generated. 227 | # html_domain_indices = True 228 | 229 | # If false, no index is generated. 230 | # html_use_index = True 231 | 232 | # If true, the index is split into individual pages for each letter. 233 | # html_split_index = False 234 | 235 | # If true, links to the reST sources are added to the pages. 236 | html_show_sourcelink = True 237 | 238 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 239 | # html_show_sphinx = True 240 | 241 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 242 | # html_show_copyright = True 243 | 244 | # If true, an OpenSearch description file will be output, and all pages will 245 | # contain a tag referring to it. The value of this option must be the 246 | # base URL from which the finished HTML is served. 247 | # html_use_opensearch = '' 248 | 249 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 250 | # html_file_suffix = None 251 | 252 | # Output file base name for HTML help builder. 253 | htmlhelp_basename = "xtal2png-doc" 254 | 255 | 256 | # -- Options for LaTeX output ------------------------------------------------ 257 | 258 | latex_elements = { 259 | # The paper size ("letterpaper" or "a4paper"). 260 | # "papersize": "letterpaper", 261 | # The font size ("10pt", "11pt" or "12pt"). 262 | # "pointsize": "10pt", 263 | # Additional stuff for the LaTeX preamble. 264 | # "preamble": "", 265 | } 266 | 267 | # Grouping the document tree into LaTeX files. List of tuples 268 | # (source start file, target name, title, author, documentclass [howto/manual]). 269 | latex_documents = [ 270 | ("index", "user_guide.tex", "xtal2png Documentation", "sgbaird", "manual") 271 | ] 272 | 273 | # The name of an image file (relative to this directory) to place at the top of 274 | # the title page. 275 | # latex_logo = "" 276 | 277 | # For "manual" documents, if this is true, then toplevel headings are parts, 278 | # not chapters. 279 | # latex_use_parts = False 280 | 281 | # If true, show page references after internal links. 282 | # latex_show_pagerefs = False 283 | 284 | # If true, show URL addresses after external links. 285 | # latex_show_urls = False 286 | 287 | # Documents to append as an appendix to all manuals. 288 | # latex_appendices = [] 289 | 290 | # If false, no module index is generated. 291 | # latex_domain_indices = True 292 | 293 | # -- External mapping -------------------------------------------------------- 294 | python_version = ".".join(map(str, sys.version_info[0:2])) 295 | intersphinx_mapping = { 296 | "sphinx": ("https://www.sphinx-doc.org/en/master", None), 297 | "python": ("https://docs.python.org/" + python_version, None), 298 | "matplotlib": ("https://matplotlib.org", None), 299 | "numpy": ("https://numpy.org/doc/stable", None), 300 | "sklearn": ("https://scikit-learn.org/stable", None), 301 | "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), 302 | "scipy": ("https://docs.scipy.org/doc/scipy/reference", None), 303 | "setuptools": ("https://setuptools.pypa.io/en/stable/", None), 304 | "pyscaffold": ("https://pyscaffold.org/en/stable", None), 305 | } 306 | 307 | print(f"loading configurations for {project} {version} ...", file=sys.stderr) 308 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CONTRIBUTING.md 2 | :relative-docs: docs/ 3 | :relative-images: 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | ```{include} ../notebooks/README.md 2 | :relative-docs: docs/ 3 | :relative-images: 4 | ``` 5 | 6 | ```{toctree} 7 | :maxdepth: 2 8 | 9 | notebooks/1.0-xtal2png-tutorial 10 | notebooks/2.0-materials-project-feature-ranges 11 | notebooks/2.0.1-materials-project-feature-ranges-conventional 12 | notebooks/2.1-xtal2png-cnn-classification 13 | notebooks/2.2-xgboost-matbench-benchmark 14 | notebooks/3.0-denoising-diffusion 15 | notebooks/3.1-imagen-pytorch 16 | scripts/denoising_diffusion_pytorch_example 17 | scripts/denoising_diffusion_pytorch_pretrained_sample 18 | scripts/imagen_pytorch_example 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # xtal2png 2 | 3 | Encode/decode a crystal structure to/from a grayscale PNG image for direct use with image-based machine learning models such as [Imagen], [DALLE2], or [Palette].[^1] 4 | 5 | [![Open In 6 | Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/sparks-baird/xtal2png/blob/main/notebooks/1.0-xtal2png-tutorial.ipynb) 7 | [![status](https://joss.theoj.org/papers/0c704f6ae9739c1e97e05ae0ad57aecb/status.svg)](https://joss.theoj.org/papers/0c704f6ae9739c1e97e05ae0ad57aecb) 8 | [![PyPI - 9 | Downloads](https://img.shields.io/pypi/dm/xtal2png)](https://pypi.org/project/xtal2png) 10 | [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/xtal2png?style=flat&color=blue&label=Conda%20Downloads)](https://anaconda.org/conda-forge/xtal2png) 11 | 12 | Star 15 | Follow @sgbaird 18 | Issue 21 | Discuss 22 |

23 | 24 | The latest advances in machine learning are often in natural language such as with long 25 | short-term memory networks (LSTMs) and transformers or image processing such as with 26 | generative adversarial networks (GANs), variational autoencoders (VAEs), and guided 27 | diffusion models; however, transfering these advances to adjacent domains such as 28 | materials informatics often takes years. `xtal2png` encodes and decodes crystal 29 | structures via grayscale PNG images by writing and reading the necessary information for 30 | crystal reconstruction (unit cell, atomic elements, atomic coordinates) as a square 31 | matrix of numbers, respectively. This is akin to making/reading a QR code for crystal 32 | structures, where the `xtal2png` representation is invertible. The ability to feed these 33 | images directly into image-based pipelines allows you, as a materials informatics 34 | practitioner, to get streamlined results for new state-of-the-art image-based machine 35 | learning models applied to crystal structure. 36 | 37 | > Results manuscript coming soon! 38 | 39 | 40 | 41 | ## Contents 42 | 43 | ```{toctree} 44 | :maxdepth: 2 45 | 46 | Overview 47 | Examples 48 | Contributions & Help 49 | License 50 | Authors 51 | Changelog 52 | Module Reference 53 | GitHub Source 54 | ``` 55 | 56 | ## Indices and tables 57 | 58 | * {ref}`genindex` 59 | * {ref}`modindex` 60 | * {ref}`search` 61 | 62 | [Sphinx]: http://www.sphinx-doc.org/ 63 | [Markdown]: https://daringfireball.net/projects/markdown/ 64 | [reStructuredText]: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html 65 | [MyST]: https://myst-parser.readthedocs.io/en/latest/ 66 | [Palette]: https://iterative-refinement.github.io/palette/ 67 | [Janspiry/Palette-Image-to-Image-Diffusion-Models]: https://github.com/Janspiry/Palette-Image-to-Image-Diffusion-Models 68 | [Imagen]: https://imagen.research.google/ 69 | [lucidrains/imagen-pytorch]: https://github.com/lucidrains/imagen-pytorch#usage 70 | [DALLE2]: https://openai.com/dall-e-2/ 71 | [lucidrains/DALLE2-pytorch]: https://github.com/lucidrains/DALLE2-pytorch#unconditional-training 72 | [^1]: For unofficial implementations, see [lucidrains/imagen-pytorch], [lucidrains/DALLE2-pytorch], and [Janspiry/Palette-Image-to-Image-Diffusion-Models], respectively 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ```{literalinclude} ../LICENSE.txt 4 | :language: text 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/docs/logo.png -------------------------------------------------------------------------------- /docs/notebooks/1.0-xtal2png-tutorial.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/1.0-xtal2png-tutorial.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/2.0-materials-project-feature-ranges.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/2.0-materials-project-feature-ranges.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/2.0.1-materials-project-feature-ranges-conventional.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/2.0.1-materials-project-feature-ranges-conventional.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/2.1-xtal2png-cnn-classification.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/2.1-xtal2png-cnn-classification.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/2.2-xgboost-matbench-benchmark.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/2.2-xgboost-matbench-benchmark.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/3.0-denoising-diffusion.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/3.0-denoising-diffusion.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/notebooks/3.1-imagen-pytorch.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../notebooks/3.1-imagen-pytorch.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ```{include} ../README.md 2 | :relative-docs: docs/ 3 | :relative-images: 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ipykernel 2 | myst-parser[linkify] 3 | nbsphinx 4 | nbsphinx-link 5 | sphinx>=3.2.1 6 | sphinx_copybutton 7 | sphinx_rtd_theme 8 | # Requirements file for ReadTheDocs, check .readthedocs.yml. 9 | # To build the module reference correctly, make sure every external package 10 | # under `install_requires` in `setup.cfg` is also listed here! 11 | -------------------------------------------------------------------------------- /docs/scripts/denoising_diffusion_pytorch_example.md: -------------------------------------------------------------------------------- 1 | # Denoising Diffusion PyTorch Example (script) 2 | 3 | ```{literalinclude} ../../scripts/denoising_diffusion_pytorch_example.py 4 | :language: python 5 | :linenos: 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/scripts/denoising_diffusion_pytorch_pretrained_sample.md: -------------------------------------------------------------------------------- 1 | # Denoising Diffusion PyTorch Pretrained Sample (script) 2 | 3 | ```{literalinclude} ../../scripts/denoising_diffusion_pytorch_pretrained_sample.py 4 | :language: python 5 | :linenos: 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/scripts/imagen_pytorch_example.md: -------------------------------------------------------------------------------- 1 | # Imagen PyTorch Example (script) 2 | 3 | ```{literalinclude} ../../scripts/imagen_pytorch_example.py 4 | :language: python 5 | :linenos: 6 | ``` 7 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: xtal2png 2 | channels: 3 | - defaults 4 | - conda-forge 5 | - pytorch 6 | # - fastai 7 | dependencies: 8 | - python>=3.6 9 | - pip 10 | # BASICS 11 | - numpy 12 | - scipy 13 | - pandas 14 | - tqdm 15 | - click 16 | - ipython 17 | - pillow 18 | - pymatgen 19 | # VISUALIZATION 20 | - matplotlib 21 | - ipympl # interactive matplotlib plots 22 | - seaborn 23 | - plotly 24 | - kaleido 25 | # - altair 26 | # - yellowbrick 27 | # ML, STATS & DEEP LEARNING 28 | # - statsmodels 29 | # - scikit-learn 30 | # - sktime 31 | # - tslearn 32 | # - xgboost 33 | # - catboost 34 | # - lightgbm 35 | # - pytorch 36 | # - fastai # activate fastai channel above! 37 | # - tensorflow 38 | # - keras 39 | # - spacy 40 | # OTHER TOOLS 41 | # - optuna 42 | # - dask 43 | # - snakeviz 44 | - pip: 45 | - -e . # install git checkout of xtal2png in editable mode 46 | # add here only pip-packages that are not available in conda/conda-forge! E.g.: 47 | # - icecream 48 | # - jax 49 | # - numpyro 50 | # - funsor 51 | # - neptune-client 52 | # - neptune-contrib 53 | 54 | # DEVELOPMENT ONLY PACKAGES (could also be kept in a separate environment file) 55 | - jupyterlab 56 | - pytest 57 | - pytest-cov 58 | - tox 59 | - pre_commit 60 | - nbdime 61 | - nbstripout 62 | - sphinx 63 | - recommonmark 64 | -------------------------------------------------------------------------------- /equations.nb: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.mathematica *) 2 | 3 | (*** Wolfram Notebook File ***) 4 | (* http://www.wolfram.com/nb *) 5 | 6 | (* CreatedBy='Mathematica 13.0' *) 7 | 8 | (*CacheID: 234*) 9 | (* Internal cache information: 10 | NotebookFileLineBreakTest 11 | NotebookFileLineBreakTest 12 | NotebookDataPosition[ 158, 7] 13 | NotebookDataLength[ 5064, 155] 14 | NotebookOptionsPosition[ 3974, 130] 15 | NotebookOutlinePosition[ 4397, 146] 16 | CellTagsIndexPosition[ 4354, 143] 17 | WindowFrame->Normal*) 18 | 19 | (* Beginning of Notebook Content *) 20 | Notebook[{ 21 | Cell[BoxData[ 22 | RowBox[{"<<", "Notation`"}]], "Input", 23 | CellChangeTimes->{{3.8620958852919607`*^9, 3.862095886265899*^9}}, 24 | CellLabel->"In[1]:=",ExpressionUUID->"538e0298-492a-4626-83ed-9e8fa51a5276"], 25 | 26 | Cell[BoxData[ 27 | RowBox[{"Symbolize", "[", 28 | TemplateBox[{ 29 | SubscriptBox["_", "_"]}, 30 | "NotationTemplateTag"], "]"}]], "Input", 31 | CellChangeTimes->{{3.8620958890856886`*^9, 3.862095889689019*^9}}, 32 | CellLabel->"In[2]:=",ExpressionUUID->"fdff9082-0ddd-45f0-9d73-e939e22f6e23"], 33 | 34 | Cell[BoxData[{ 35 | RowBox[{"X_std", "==", 36 | RowBox[{ 37 | RowBox[{"(", 38 | RowBox[{"X", "-", "data_min"}], ")"}], "/", 39 | RowBox[{"(", 40 | RowBox[{"data_max", "-", "data_min"}], ")"}]}]}], "\n", 41 | RowBox[{"X_scaled", "==", 42 | RowBox[{ 43 | RowBox[{"X_std", "*", 44 | RowBox[{"(", 45 | RowBox[{"feature_max", "-", "feature_min"}], ")"}]}], "+", 46 | "feature_min"}]}]}], "Input", 47 | CellChangeTimes->{{3.862095873697342*^9, 48 | 3.8620958983419266`*^9}},ExpressionUUID->"258ed234-170b-47be-b3c9-\ 49 | a6da6965c89a"], 50 | 51 | Cell[BoxData[ 52 | RowBox[{ 53 | RowBox[{"eqns", "=", 54 | RowBox[{"{", ",", "}"}]}], ";"}]], "Input", 55 | CellChangeTimes->{{3.862095880389358*^9, 56 | 3.8620959988171363`*^9}},ExpressionUUID->"8ba859bf-87b2-40f6-ad49-\ 57 | 3b47b1314234"], 58 | 59 | Cell[CellGroupData[{ 60 | 61 | Cell[BoxData[ 62 | RowBox[{"soln1", "=", 63 | RowBox[{ 64 | RowBox[{"Solve", "[", 65 | RowBox[{ 66 | RowBox[{ 67 | SubscriptBox["X", "scaled"], "==", 68 | RowBox[{ 69 | RowBox[{ 70 | SubscriptBox["X", "std"], 71 | RowBox[{"(", 72 | RowBox[{ 73 | SubscriptBox["feature", "max"], "-", 74 | SubscriptBox["feature", "min"]}], ")"}]}], "+", 75 | SubscriptBox["feature", "min"]}]}], ",", 76 | SubscriptBox["X", "std"]}], "]"}], "[", 77 | RowBox[{"[", "1", "]"}], "]"}]}]], "Input", 78 | CellChangeTimes->{{3.8620959496275835`*^9, 3.8620959938424892`*^9}}, 79 | CellLabel->"In[9]:=",ExpressionUUID->"1561f0d1-7f33-4bbe-8cf9-1a11bbdefdba"], 80 | 81 | Cell[BoxData[ 82 | RowBox[{"{", 83 | RowBox[{ 84 | SubscriptBox["X", "std"], "\[Rule]", 85 | FractionBox[ 86 | RowBox[{ 87 | RowBox[{"-", 88 | SubscriptBox["feature", "min"]}], "+", 89 | SubscriptBox["X", "scaled"]}], 90 | RowBox[{ 91 | SubscriptBox["feature", "max"], "-", 92 | SubscriptBox["feature", "min"]}]]}], "}"}]], "Output", 93 | CellChangeTimes->{{3.862095961935524*^9, 3.8620959942893114`*^9}}, 94 | CellLabel->"Out[9]=",ExpressionUUID->"8db9df8b-753c-41ab-8888-34b49ad91fb4"] 95 | }, Open ]], 96 | 97 | Cell[CellGroupData[{ 98 | 99 | Cell[BoxData[ 100 | RowBox[{"Solve", "[", 101 | RowBox[{ 102 | RowBox[{ 103 | SubscriptBox["X", "std"], "==", 104 | FractionBox[ 105 | RowBox[{"X", "-", 106 | SubscriptBox["data", "min"]}], 107 | RowBox[{ 108 | SubscriptBox["data", "max"], "-", 109 | SubscriptBox["data", "min"]}]]}], ",", "X"}], "]"}]], "Input", 110 | CellChangeTimes->{{3.8620960003169155`*^9, 3.862096005138488*^9}, { 111 | 3.862096159543583*^9, 3.862096163728257*^9}}, 112 | CellLabel->"In[15]:=",ExpressionUUID->"fe4bd5bb-1ab7-4a65-a3aa-bc7c6a0a287f"], 113 | 114 | Cell[BoxData[ 115 | RowBox[{"{", 116 | RowBox[{"{", 117 | RowBox[{"X", "\[Rule]", 118 | RowBox[{ 119 | SubscriptBox["data", "min"], "+", 120 | RowBox[{ 121 | SubscriptBox["data", "max"], " ", 122 | SubscriptBox["X", "std"]}], "-", 123 | RowBox[{ 124 | SubscriptBox["data", "min"], " ", 125 | SubscriptBox["X", "std"]}]}]}], "}"}], "}"}]], "Output", 126 | CellChangeTimes->{ 127 | 3.862096001345128*^9, {3.862096160171989*^9, 3.862096164409914*^9}}, 128 | CellLabel->"Out[15]=",ExpressionUUID->"409ecba4-ae9e-4951-861d-4e1e5d6c6012"] 129 | }, Open ]] 130 | }, 131 | WindowSize->{571.8, 524.4}, 132 | WindowMargins->{{Automatic, 141}, {Automatic, 21.599999999999998`}}, 133 | FrontEndVersion->"13.0 for Microsoft Windows (64-bit) (February 4, 2022)", 134 | StyleDefinitions->"Default.nb", 135 | ExpressionUUID->"e6832f1a-9bcc-4c4b-a872-793bf8ede2df" 136 | ] 137 | (* End of Notebook Content *) 138 | 139 | (* Internal cache information *) 140 | (*CellTagsOutline 141 | CellTagsIndex->{} 142 | *) 143 | (*CellTagsIndex 144 | CellTagsIndex->{} 145 | *) 146 | (*NotebookFileOutline 147 | Notebook[{ 148 | Cell[558, 20, 199, 3, 28, "Input",ExpressionUUID->"538e0298-492a-4626-83ed-9e8fa51a5276"], 149 | Cell[760, 25, 276, 6, 40, "Input",ExpressionUUID->"fdff9082-0ddd-45f0-9d73-e939e22f6e23"], 150 | Cell[1039, 33, 503, 15, 48, "Input",ExpressionUUID->"258ed234-170b-47be-b3c9-a6da6965c89a"], 151 | Cell[1545, 50, 222, 6, 28, "Input",ExpressionUUID->"8ba859bf-87b2-40f6-ad49-3b47b1314234"], 152 | Cell[CellGroupData[{ 153 | Cell[1792, 60, 646, 18, 48, "Input",ExpressionUUID->"1561f0d1-7f33-4bbe-8cf9-1a11bbdefdba"], 154 | Cell[2441, 80, 473, 13, 50, "Output",ExpressionUUID->"8db9df8b-753c-41ab-8888-34b49ad91fb4"] 155 | }, Open ]], 156 | Cell[CellGroupData[{ 157 | Cell[2951, 98, 496, 13, 47, "Input",ExpressionUUID->"fe4bd5bb-1ab7-4a65-a3aa-bc7c6a0a287f"], 158 | Cell[3450, 113, 508, 14, 32, "Output",ExpressionUUID->"409ecba4-ae9e-4951-861d-4e1e5d6c6012"] 159 | }, Open ]] 160 | } 161 | ] 162 | *) 163 | 164 | (* End of internal cache information *) 165 | -------------------------------------------------------------------------------- /models/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /notebooks/1.0-xtal2png-tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "colab_type": "text", 7 | "id": "view-in-github" 8 | }, 9 | "source": [ 10 | "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/sparks-baird/xtal2png/blob/main/notebooks/1.0-xtal2png-tutorial.ipynb)" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "id": "mHJ4l5gKuXx-" 17 | }, 18 | "source": [ 19 | "# Converting interchangeably between crystal structure and a grayscale PNG image (basic `xtal2png` tutorial)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": { 25 | "id": "pjrtpDdbuyqp" 26 | }, 27 | "source": [ 28 | "In this notebook, we will install the `xtal2png` package, encode/decode two example pymatgen `Structure` objects, and show some visualizations of the intermediate PNG representations and before/after crystal structure plots. Finally, we comment on how you can use `xtal2png` with state-of-the-art machine learning image models." 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": { 34 | "id": "In1KcDcVuxw6" 35 | }, 36 | "source": [ 37 | "## Installation" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": { 43 | "id": "93hxUzlfqb4v" 44 | }, 45 | "source": [ 46 | "Install the `xtal2png` package. Optionally install `ase` and `nglview` which can be used to visualize crystal structures. You may need to restart the runtime via `Ctrl+M, .` or `Runtime --> Restart Runtime` (via menubar)." 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": { 53 | "colab": { 54 | "base_uri": "https://localhost:8080/" 55 | }, 56 | "id": "oEFP_2LqUUC5", 57 | "outputId": "f26dfa45-9709-4687-8078-2c7b5af7a2fe" 58 | }, 59 | "outputs": [ 60 | { 61 | "name": "stdout", 62 | "output_type": "stream", 63 | "text": [ 64 | "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", 65 | "Collecting xtal2png\n", 66 | " Downloading xtal2png-0.1.6-py3-none-any.whl (20 kB)\n", 67 | "Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from xtal2png) (7.1.2)\n", 68 | "Collecting pymatgen\n", 69 | " Downloading pymatgen-2022.0.17.tar.gz (40.6 MB)\n", 70 | "\u001b[K |████████████████████████████████| 40.6 MB 2.0 MB/s \n", 71 | "\u001b[?25h Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n", 72 | " Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n", 73 | " Installing backend dependencies ... \u001b[?25l\u001b[?25hdone\n", 74 | " Preparing wheel metadata ... \u001b[?25l\u001b[?25hdone\n", 75 | "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from xtal2png) (1.21.6)\n", 76 | "Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from xtal2png) (4.11.3)\n", 77 | "Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->xtal2png) (4.2.0)\n", 78 | "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->xtal2png) (3.8.0)\n", 79 | "Requirement already satisfied: palettable>=3.1.1 in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (3.3.0)\n", 80 | "Collecting monty>=3.0.2\n", 81 | " Downloading monty-2022.4.26-py3-none-any.whl (65 kB)\n", 82 | "\u001b[K |████████████████████████████████| 65 kB 3.9 MB/s \n", 83 | "\u001b[?25hRequirement already satisfied: plotly>=4.5.0 in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (5.5.0)\n", 84 | "Collecting uncertainties>=3.1.4\n", 85 | " Downloading uncertainties-3.1.6-py2.py3-none-any.whl (98 kB)\n", 86 | "\u001b[K |████████████████████████████████| 98 kB 6.4 MB/s \n", 87 | "\u001b[?25hCollecting ruamel.yaml>=0.15.6\n", 88 | " Downloading ruamel.yaml-0.17.21-py3-none-any.whl (109 kB)\n", 89 | "\u001b[K |████████████████████████████████| 109 kB 50.8 MB/s \n", 90 | "\u001b[?25hCollecting scipy>=1.5.0\n", 91 | " Downloading scipy-1.7.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38.1 MB)\n", 92 | "\u001b[K |████████████████████████████████| 38.1 MB 1.2 MB/s \n", 93 | "\u001b[?25hRequirement already satisfied: matplotlib>=1.5 in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (3.2.2)\n", 94 | "Requirement already satisfied: sympy in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (1.7.1)\n", 95 | "Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (2.23.0)\n", 96 | "Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (1.3.5)\n", 97 | "Requirement already satisfied: tabulate in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (0.8.9)\n", 98 | "Requirement already satisfied: networkx>=2.2 in /usr/local/lib/python3.7/dist-packages (from pymatgen->xtal2png) (2.6.3)\n", 99 | "Collecting spglib>=1.9.9.44\n", 100 | " Downloading spglib-1.16.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (325 kB)\n", 101 | "\u001b[K |████████████████████████████████| 325 kB 64.7 MB/s \n", 102 | "\u001b[?25hRequirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=1.5->pymatgen->xtal2png) (1.4.2)\n", 103 | "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=1.5->pymatgen->xtal2png) (3.0.9)\n", 104 | "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=1.5->pymatgen->xtal2png) (2.8.2)\n", 105 | "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=1.5->pymatgen->xtal2png) (0.11.0)\n", 106 | "Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from plotly>=4.5.0->pymatgen->xtal2png) (1.15.0)\n", 107 | "Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.7/dist-packages (from plotly>=4.5.0->pymatgen->xtal2png) (8.0.1)\n", 108 | "Collecting ruamel.yaml.clib>=0.2.6\n", 109 | " Downloading ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl (546 kB)\n", 110 | "\u001b[K |████████████████████████████████| 546 kB 52.2 MB/s \n", 111 | "\u001b[?25hRequirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from uncertainties>=3.1.4->pymatgen->xtal2png) (0.16.0)\n", 112 | "Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->pymatgen->xtal2png) (2022.1)\n", 113 | "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pymatgen->xtal2png) (2022.5.18.1)\n", 114 | "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pymatgen->xtal2png) (2.10)\n", 115 | "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->pymatgen->xtal2png) (3.0.4)\n", 116 | "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->pymatgen->xtal2png) (1.24.3)\n", 117 | "Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy->pymatgen->xtal2png) (1.2.1)\n", 118 | "Building wheels for collected packages: pymatgen\n" 119 | ] 120 | } 121 | ], 122 | "source": [ 123 | "!pip install xtal2png\n", 124 | "!pip install ase nglview # optional, for visualization of crystal structures" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": { 130 | "id": "5_ur18igvGap" 131 | }, 132 | "source": [ 133 | "## Encode(/decode) two pymatgen `Structure` objects" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": { 139 | "id": "auHOZ-dAhd5t" 140 | }, 141 | "source": [ 142 | "Import a list of two example pymatgen `Structure` objects (these correspond to [mp-560471](https://next-gen.materialsproject.org/materials/mp-560471)/$Zn_2B_2PbO_6$ and [mp-7823](https://next-gen.materialsproject.org/materials/mp-7823)/$V_2NiSe_4$, respectively)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": { 149 | "id": "qDET_Hc6UXP7" 150 | }, 151 | "outputs": [], 152 | "source": [ 153 | "from xtal2png.utils.data import example_structures\n", 154 | "from xtal2png.core import XtalConverter" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": { 160 | "id": "EcxElT0bhyxF" 161 | }, 162 | "source": [ 163 | "Let's take a look at the second `Structure` which has a smaller footprint." 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "metadata": { 170 | "id": "S2I58v39hyNO" 171 | }, 172 | "outputs": [], 173 | "source": [ 174 | "example_structures[1]" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": { 180 | "id": "ulVWYCeoiuTM" 181 | }, 182 | "source": [ 183 | "We will be using the `XtalConverter` class, for which more information including its `__init__` arguments and functions can be displayed via `help(XtalConverter)`. For just the parameters for class instantiation, try `help(XtalConverter.__init__)`. Note that `max_sites` is not tested for values other than `52`." 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": { 190 | "id": "hZ4LtYcJkoOC" 191 | }, 192 | "outputs": [], 193 | "source": [ 194 | "help(XtalConverter.__init__)" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": { 200 | "id": "weBReOogvmeK" 201 | }, 202 | "source": [ 203 | "Let's specify the save directory (`save_dir`) for the PNG files as `\"data\"`, which will be automatically created. In this case, it will be saved to temporary Google Colab storage." 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": { 210 | "id": "B_S0jeVsVfYK" 211 | }, 212 | "outputs": [], 213 | "source": [ 214 | "xc = XtalConverter(save_dir=\"data\") # DFT surrogate relaxation via m3gnet by default\n", 215 | "data = xc.xtal2png(example_structures, save=True)\n", 216 | "relaxed_decoded_structures = xc.png2xtal(data, save=False)" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "We also take a look at the unrelaxed decoded structures." 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "xc = XtalConverter(save_dir=\"data\", relax_on_decode=False)\n", 233 | "data = xc.xtal2png(example_structures, save=True)\n", 234 | "decoded_structures = xc.png2xtal(data, save=False)" 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "metadata": { 240 | "id": "DNL8GcdZwixh" 241 | }, 242 | "source": [ 243 | "## Visualization" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": { 249 | "id": "Cexf9PqBwkMR" 250 | }, 251 | "source": [ 252 | "For visualization, we'll cover two aspects: the structure-encoded PNG images and visualizing before/after crystal structures." 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "metadata": { 258 | "id": "Xm8i5EZ5wsqJ" 259 | }, 260 | "source": [ 261 | "### Structure-encoded PNG images" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "metadata": { 267 | "id": "TQ8jkgYrhcg2" 268 | }, 269 | "source": [ 270 | "Note that images won't show via `im.show()` command _on Google Colab_ even if you specify `xc.xtal2png(..., show=True, ...)`, so for this Colab example we'll open the images ad-hoc based on where they were saved in local Colab storage. We display the images stacked one on top of another using `display(im)` instead of `im.show()`. Note that the filepaths have the chemical formula, `volume`, and a randomly generated `uid` portion to promote uniqueness, especially when dealing with allotropes (same chemical formula, different crystal structure)." 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "metadata": { 277 | "id": "pV0L8N85kTFk" 278 | }, 279 | "outputs": [], 280 | "source": [ 281 | "import glob, os\n", 282 | "from PIL import Image\n", 283 | "for fpath in glob.glob(\"data/*.png\"):\n", 284 | " with Image.open(fpath) as im:\n", 285 | " im = im.resize((64*5, 64*5), Image.BOX)\n", 286 | " print(fpath)\n", 287 | " display(im)" 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": { 293 | "id": "oU6FjfDYnnuI" 294 | }, 295 | "source": [ 296 | "As mentioned in the README, the legend key for these images is as follows:\n", 297 | "\n", 298 | "        " 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": { 304 | "id": "U-rTkYeMxxAQ" 305 | }, 306 | "source": [ 307 | "Also described in the README, the match between the encoded and decoded versions is within an expected tolerance, given that PNG images are represented as discrete RGB values between 0 and 255 (i.e. there is a round-off error).\n", 308 | "\n", 309 | "\n", 310 | "\n", 311 | "\n", 312 | "\n", 313 | "\n", 314 | "\n", 315 | "\n", 316 | "\n", 333 | "\n", 350 | "\n", 367 | "\n", 368 | "
Original Decoded Relaxed Decoded
\n", 317 | "\n", 318 | "```python\n", 319 | "Structure Summary\n", 320 | "Lattice\n", 321 | " abc : 5.033788 11.523021 10.74117\n", 322 | " angles : 90.0 90.0 90.0\n", 323 | " volume : 623.0356027127609\n", 324 | " A : 5.033788 0.0 3.0823061808931787e-16\n", 325 | " B : 1.8530431062799525e-15 11.523021 7.055815392078867e-16\n", 326 | " C : 0.0 0.0 10.74117\n", 327 | "PeriodicSite: Zn2+ (0.9120, 5.7699, 9.1255) [0.1812, 0.5007, 0.8496]\n", 328 | "PeriodicSite: Zn2+ (4.1218, 5.7531, 1.6156) [0.8188, 0.4993, 0.1504]\n", 329 | "...\n", 330 | "```\n", 331 | "\n", 332 | "\n", 334 | "\n", 335 | "```python\n", 336 | "Structure Summary\n", 337 | "Lattice\n", 338 | " abc : 5.0250980392156865 11.533333333333331 10.8\n", 339 | " angles : 90.0 90.0 90.0\n", 340 | " volume : 625.9262117647058\n", 341 | " A : 5.0250980392156865 0.0 0.0\n", 342 | " B : 0.0 11.533333333333331 0.0\n", 343 | " C : 0.0 0.0 10.8\n", 344 | "PeriodicSite: Zn (0.9016, 5.7780, 3.8012) [0.1794, 0.5010, 0.3520]\n", 345 | "PeriodicSite: Zn (4.1235, 5.7554, 6.9988) [0.8206, 0.4990, 0.6480]\n", 346 | "...\n", 347 | "```\n", 348 | "\n", 349 | "\n", 351 | "\n", 352 | "```python\n", 353 | "Structure Summary\n", 354 | "Lattice\n", 355 | " abc : 5.026834307381214 11.578854613685237 10.724087971087924\n", 356 | " angles : 90.0 90.0 90.0\n", 357 | " volume : 624.1953646135236\n", 358 | " A : 5.026834307381214 0.0 0.0\n", 359 | " B : 0.0 11.578854613685237 0.0\n", 360 | " C : 0.0 0.0 10.724087971087924\n", 361 | "PeriodicSite: Zn (0.9050, 5.7978, 3.7547) [0.1800, 0.5007, 0.3501]\n", 362 | "PeriodicSite: Zn (4.1218, 5.7810, 6.9693) [0.8200, 0.4993, 0.6499]\n", 363 | "...\n", 364 | "```\n", 365 | "\n", 366 | "
" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": { 374 | "id": "6K8M_EWewvgK" 375 | }, 376 | "source": [ 377 | "### Before/after Crystal Structure Visualization" 378 | ] 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "metadata": { 383 | "id": "e1TFsM32pwsN" 384 | }, 385 | "source": [ 386 | "To visualize the crystal structures before and after, we can use `nglview` after a bit of Colab finnagling with external ipywidgets." 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": null, 392 | "metadata": { 393 | "id": "VDhN-1cFuGIq" 394 | }, 395 | "outputs": [], 396 | "source": [ 397 | "from google.colab import output\n", 398 | "output.enable_custom_widget_manager()" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": null, 404 | "metadata": { 405 | "id": "kT2kMJkBriRT" 406 | }, 407 | "outputs": [], 408 | "source": [ 409 | "from pymatgen.io.ase import AseAtomsAdaptor\n", 410 | "from ase.visualize import view\n", 411 | "aaa = AseAtomsAdaptor()" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "execution_count": null, 417 | "metadata": { 418 | "id": "40sCJggKsV5E" 419 | }, 420 | "outputs": [], 421 | "source": [ 422 | "[display(view(aaa.get_atoms(s), viewer='ngl')) for s in example_structures]" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": null, 428 | "metadata": { 429 | "id": "FWMOd7gHsTB0" 430 | }, 431 | "outputs": [], 432 | "source": [ 433 | "[display(view(aaa.get_atoms(s), viewer='ngl')) for s in decoded_structures]" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": null, 439 | "metadata": {}, 440 | "outputs": [], 441 | "source": [ 442 | "[display(view(aaa.get_atoms(s), viewer='ngl')) for s in relaxed_decoded_structures]" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "metadata": { 448 | "id": "1u_95y04uIRa" 449 | }, 450 | "source": [ 451 | "Undo the Colab finnagling of external ipywidgets." 452 | ] 453 | }, 454 | { 455 | "cell_type": "code", 456 | "execution_count": null, 457 | "metadata": { 458 | "id": "IVFYjEvUrfCP" 459 | }, 460 | "outputs": [], 461 | "source": [ 462 | "from google.colab import output\n", 463 | "output.disable_custom_widget_manager()" 464 | ] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "metadata": { 469 | "id": "DLAfVjyrw5VK" 470 | }, 471 | "source": [ 472 | "## Final Remarks" 473 | ] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "metadata": { 478 | "id": "7QHtKaklxKip" 479 | }, 480 | "source": [ 481 | "This tool makes it possible to use state-of-the-art image-based machine learning models with minimal \"plumbing\" required. Just follow the normal instructions for custom image datasets. For example, this can be used with [Palette](https://iterative-refinement.github.io/palette/), an image-to-image guided diffusion model by Google, which has an unofficial implementation [here](https://github.com/Janspiry/Palette-Image-to-Image-Diffusion-Models)." 482 | ] 483 | } 484 | ], 485 | "metadata": { 486 | "colab": { 487 | "authorship_tag": "ABX9TyNJG5GbIiRm3b22dp5jDrbI", 488 | "include_colab_link": true, 489 | "name": "xtal2png-tutorial.ipynb", 490 | "provenance": [] 491 | }, 492 | "kernelspec": { 493 | "display_name": "Python 3", 494 | "name": "python3" 495 | }, 496 | "language_info": { 497 | "name": "python" 498 | } 499 | }, 500 | "nbformat": 4, 501 | "nbformat_minor": 0 502 | } 503 | -------------------------------------------------------------------------------- /notebooks/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Various Jupyter notebooks that cover primary usage and peripheral concepts/functionality 4 | of `xtal2png` are included here. Many of these examples can be run on [Google Colab](https://colab.research.google.com/) and 5 | have a corresponding clickable badge at the top of the notebook. For example, here is 6 | the badge for the tutorial notebook: 7 | 8 | Open In Colab 12 | 13 | These resources can also be downloaded directly from GitHub 14 | ([notebooks](https://github.com/sparks-baird/xtal2png/tree/main/notebooks), 15 | [scripts](https://github.com/sparks-baird/xtal2png/tree/main/scripts)). 16 | -------------------------------------------------------------------------------- /notebooks/data/preprocessed/V4Ni2Se8,volume=243,uid=0674.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/notebooks/data/preprocessed/V4Ni2Se8,volume=243,uid=0674.png -------------------------------------------------------------------------------- /notebooks/data/preprocessed/Zn8B8Pb4O24,volume=623,uid=9376.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/notebooks/data/preprocessed/Zn8B8Pb4O24,volume=623,uid=9376.png -------------------------------------------------------------------------------- /notebooks/template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import sys\n", 11 | "import math\n", 12 | "import logging\n", 13 | "from pathlib import Path\n", 14 | "\n", 15 | "import numpy as np\n", 16 | "import scipy as sp\n", 17 | "import sklearn\n", 18 | "import statsmodels.api as sm\n", 19 | "from statsmodels.formula.api import ols\n", 20 | "\n", 21 | "%load_ext autoreload\n", 22 | "%autoreload 2\n", 23 | "\n", 24 | "import matplotlib as mpl\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "%matplotlib inline\n", 27 | "%config InlineBackend.figure_format = 'retina'\n", 28 | "\n", 29 | "import seaborn as sns\n", 30 | "sns.set_context(\"poster\")\n", 31 | "sns.set(rc={\"figure.figsize\": (16, 9.)})\n", 32 | "sns.set_style(\"whitegrid\")\n", 33 | "\n", 34 | "import pandas as pd\n", 35 | "pd.set_option(\"display.max_rows\", 120)\n", 36 | "pd.set_option(\"display.max_columns\", 120)\n", 37 | "\n", 38 | "logging.basicConfig(level=logging.INFO, stream=sys.stdout)" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "from xtal2png import *" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "**PLEASE** save this file right now using the following naming convention: `NUMBER_FOR_SORTING-YOUR_INITIALS-SHORT_DESCRIPTION`, e.g. `1.0-fw-initial-data-exploration`. Use the number to order the file within the directory according to its usage." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [] 63 | } 64 | ], 65 | "metadata": { 66 | "kernelspec": { 67 | "display_name": "Python 3", 68 | "language": "python", 69 | "name": "python3" 70 | }, 71 | "language_info": { 72 | "codemirror_mode": { 73 | "name": "ipython", 74 | "version": 3 75 | }, 76 | "file_extension": ".py", 77 | "mimetype": "text/x-python", 78 | "name": "python", 79 | "nbconvert_exporter": "python", 80 | "pygments_lexer": "ipython3", 81 | "version": "3.7.3" 82 | }, 83 | "pycharm": { 84 | "stem_cell": { 85 | "cell_type": "raw", 86 | "metadata": { 87 | "collapsed": false 88 | }, 89 | "source": [] 90 | } 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 2 95 | } 96 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD! 3 | requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5", "wheel"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | [tool.setuptools_scm] 7 | # For smarter version schemes and other configuration options, 8 | # check out https://github.com/pypa/setuptools_scm 9 | version_scheme = "no-guess-dev" 10 | -------------------------------------------------------------------------------- /references/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/references/.gitignore -------------------------------------------------------------------------------- /reports/figures/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/.gitignore -------------------------------------------------------------------------------- /reports/figures/Zn8B8Pb4O24,volume=623,uid=bc2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/Zn8B8Pb4O24,volume=623,uid=bc2d.png -------------------------------------------------------------------------------- /reports/figures/Zn8B8Pb4O24,volume=623,uid=bc2d_upsampled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/Zn8B8Pb4O24,volume=623,uid=bc2d_upsampled.png -------------------------------------------------------------------------------- /reports/figures/example-and-legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/example-and-legend.png -------------------------------------------------------------------------------- /reports/figures/lattice_a_conventional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/lattice_a_conventional.png -------------------------------------------------------------------------------- /reports/figures/lattice_a_matplotlibified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/lattice_a_matplotlibified.png -------------------------------------------------------------------------------- /reports/figures/lattice_a_plotly_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/lattice_a_plotly_default.png -------------------------------------------------------------------------------- /reports/figures/legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/legend.png -------------------------------------------------------------------------------- /reports/figures/mp-time-split-train-fold-0-forced-equimolar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/mp-time-split-train-fold-0-forced-equimolar.png -------------------------------------------------------------------------------- /reports/figures/original-decoded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/original-decoded.png -------------------------------------------------------------------------------- /reports/figures/xtal2png-docs-qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/xtal2png-docs-qr-code.png -------------------------------------------------------------------------------- /reports/figures/xtal2png-imagen-pytorch-epoch=1999-6x5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/reports/figures/xtal2png-imagen-pytorch-epoch=1999-6x5.png -------------------------------------------------------------------------------- /reports/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'xtal2png: A Python package for representing crystal structure as PNG files' 3 | tags: 4 | - Python 5 | - materials informatics 6 | - crystal structure 7 | - computer vision 8 | - image-based predictions 9 | authors: 10 | - name: Sterling G. Baird 11 | orcid: 0000-0002-4491-6876 12 | equal-contrib: false 13 | corresponding: true 14 | affiliation: "1" # (Multiple affiliations must be quoted) 15 | - name: Kevin M. Jablonka 16 | orcid: 0000-0003-4894-4660 17 | affiliation: "3" 18 | - name: Michael D. Alverson 19 | orcid: 0000-0002-4857-7584 20 | equal-contrib: false 21 | affiliation: "1,2" # (Multiple affiliations must be quoted) 22 | - name: Hasan M. Sayeed 23 | orcid: 0000-0002-6583-7755 24 | equal-contrib: false 25 | affiliation: "1" # (Multiple affiliations must be quoted) 26 | - name: Mohammed Faris Khan 27 | equal-contrib: false 28 | orcid: 0000-0001-7527-6368 29 | affiliation: "1" # (Multiple affiliations must be quoted) 30 | - name: Colton Seegmiller 31 | orcid: 0000-0001-9511-2918 32 | equal-contrib: false 33 | affiliation: "4" # (Multiple affiliations must be quoted) 34 | - name: Berend Smit 35 | orcid: 0000-0003-4653-8562 36 | affiliation: "3" 37 | - name: Taylor D. Sparks 38 | orcid: 0000-0001-8020-7711 39 | equal-contrib: false 40 | affiliation: "1" # (Multiple affiliations must be quoted) 41 | affiliations: 42 | - name: Materials Science & Engineering, University of Utah, USA 43 | index: 1 44 | - name: Computer Science, University of Southern California, USA 45 | index: 2 46 | - name: Laboratory of Molecular Simulation (LSMO), Institut des Sciences et Ingénierie Chimique, École Polytechnique Fédérale de Lausanne, Switzerland 47 | index: 3 48 | - name: Computer Science, Utah Valley University, USA 49 | index: 4 50 | date: 28 July 2022 51 | bibliography: paper.bib 52 | 53 | # # Optional fields if submitting to a AAS journal too, see this blog post: 54 | # # https://blog.joss.theoj.org/2018/12/a-new-collaboration-with-aas-publishing 55 | # aas-doi: 10.3847/xxxxx <- update this with the DOI from AAS once you know it. 56 | # aas-journal: Astrophysical Journal <- The name of the AAS journal. 57 | --- 58 | 59 | # Summary 60 | 61 | The latest advances in machine learning are often in natural language processing such as with long 62 | short-term memory networks (LSTMs) and Transformers, or image processing such as with 63 | generative adversarial networks (GANs), variational autoencoders (VAEs), and guided 64 | diffusion models. `xtal2png` encodes and decodes crystal structures via PNG 65 | images (see e.g. \autoref{fig:64-bit}) by writing and reading the necessary information 66 | for crystal reconstruction (unit cell, atomic elements, atomic coordinates) as a square 67 | matrix of numbers. This is akin to making/reading a QR code for crystal 68 | structures, where the `xtal2png` representation is an invertible representation. The 69 | ability to feed these images directly into image-based pipelines allows you, as a 70 | materials informatics practitioner, to get streamlined results for new state-of-the-art 71 | image-based machine learning models applied to crystal structures. 72 | 73 | ![A real size $64\times64$ pixel `xtal2png` representation of a crystal structure.\label{fig:64-bit}](figures/Zn8B8Pb4O24,volume=623,uid=bc2d.png) 74 | 75 | # Statement of need 76 | 77 | Using a state-of-the-art method in a separate domain with a custom data representation 78 | is often an expensive and drawn-out process. For example, [@vaswaniAttentionAllYou2017] 79 | introduced the revolutionary natural language processing Transformer architecture in 80 | June 2017, yet the application of Transformers to the adjacent domain of materials 81 | informatics (chemical-formula-based predictions) was not publicly realized until late 82 | 2019 [@goodallPredictingMaterialsProperties2019], approximately two-and-a-half years 83 | later, with peer-reviewed publications dating to late 2020 84 | [@goodallPredictingMaterialsProperties2020]. Interestingly, a nearly identical 85 | implementation was being developed concurrently in a different research group with 86 | slightly later public release [@wangCompositionallyrestrictedAttentionbasedNetwork2020] 87 | and publication [@wangCompositionallyRestrictedAttentionbased2021] dates. Another 88 | example of a state-of-the-art algorithm domain transfer is refactoring image-processing 89 | models for crystal structure applications, which was first introduced in a preprint 90 | [@kipfSemisupervisedClassificationGraph2016] and published with application for 91 | materials' property prediction in a peer-reviewed journal over a year later 92 | [@xieCrystalGraphConvolutional2018]. Similarly, VAEs were introduced in 2013 93 | [@kingmaAutoEncodingVariationalBayes2014a] and implemented for molecules in 2016 94 | [@gomez-bombarelliAutomaticChemicalDesign2016], and denoising diffusion probabilistic 95 | models (DDPMs) were introduced in 2015 [@sohl-dicksteinDeepUnsupervisedLearning2015] and 96 | implemented for crystal structures in 2021 [@xieCrystalDiffusionVariational2021]. Here, 97 | we focus on state-of-the-art domain transfer (especially of generative models) from 98 | image processing to crystal structure to enable materials science practitioners to 99 | leverage the most advanced image processing models for materials' property prediction 100 | and inverse design. 101 | 102 | `xtal2png` is a Python package that allows you to convert between a crystal structure 103 | and a PNG image for direct use with image-based machine learning models. Let's take 104 | [Google's image-to-image diffusion model, 105 | Palette](https://iterative-refinement.github.io/palette/) 106 | [@sahariaPaletteImagetoImageDiffusion2022a], which supports unconditional image 107 | generation, conditional inpainting, and conditional image restoration, which are modeling tasks 108 | that can be used in crystal generation, structure prediction, and structure 109 | relaxation, respectively. Rather than dig into the code and spending hours, days, or 110 | weeks modifying, debugging, and playing GitHub phone tag with the developers before you 111 | can (maybe) get preliminary results, `xtal2png` lets you get comparable results using the default parameters, assuming the instructions can be run without 112 | error. While there are other invertible representations for crystal structures 113 | [@xieCrystalDiffusionVariational2022;@renInvertibleCrystallographicRepresentation2022a] 114 | as well as cross-domain conversions such as converting between molecules and strings 115 | [@weiningerSMILESChemicalLanguage1988;@selfies], to our knowledge, this is the first 116 | package that enables conversion between a crystal structure and an image file format. 117 | 118 | ![(a) upscaled example image and (b) legend of the `xtal2png` encoding.\label{fig:example-and-legend}](figures/example-and-legend.png) 119 | 120 | `xtal2png` was designed to be easy to use by both 121 | "[Pythonistas](https://en.wiktionary.org/wiki/Pythonista)" and entry-level coders alike. 122 | `xtal2png` provides a straightforward Python application programming interface (API) and 123 | command-line interface (CLI). `xtal2png` relies on `pymatgen.core.structure.Structure` 124 | [@ongPythonMaterialsGenomics2013] objects for representing crystal structures and also 125 | supports reading crystallographic information files (CIFs) from directories. `xtal2png` 126 | encodes crystallographic information related to the unit cell, crystallographic 127 | symmetry, and atomic elements and coordinates which are each scaled individually 128 | according to the information type. An upscaled version of the PNG image and a legend of 129 | the representation are given in \autoref{fig:example-and-legend}. Due to the encoding of 130 | numerical values as PNG images (allowable values are integers between 0 and 131 | 255), a round-off error is present during a single round of encoding and decoding. 132 | An example comparing an original vs. decoded structure is given in 133 | \autoref{fig:original-decoded}. 134 | 135 | There are some limitations and design considerations for `xtal2png` that are described 136 | in `xtal2png`'s [documentation](https://xtal2png.readthedocs.io/en/latest/index.html) in 137 | the Overview section. 138 | At this time, it is unclear to what extent deviation from the aforementioned design 139 | choices will affect performance. We intend to use hyperparameter optimization to 140 | determine an optimal configuration for crystal structure generation tasks using the 141 | `xtal2png` representation. 142 | 143 | ![(a) Original and (b) `xtal2png` decoded visualizations of 144 | [`mp-560471`](https://materialsproject.org/materials/mp-560471/) / Zn$_2$B$_2$PbO$_6$. Images were generated using [ase visualizations](https://wiki.fysik.dtu.dk/ase/ase/visualize/visualize.html). \label{fig:original-decoded}](figures/original-decoded.png){ width=50% } 145 | 146 | The significance of the representation lies in being able to directly use the PNG 147 | representation with image-based models which often do not directly support custom 148 | dataset types. We expect the use of `xtal2png` as a screening tool for such models to 149 | save significant user time of code refactoring and adaptation during the process of 150 | obtaining preliminary results on a newly released model. After obtaining preliminary 151 | results, you get to decide whether it's worth it to you to take on the 152 | higher-cost/higher-expertise task of modifying the codebase and using a more customized 153 | approach. Or you can stick with the results of xtal2png. It's up to you! 154 | 155 | We plan to apply `xtal2png` to a probabilistic diffusion generative model as a 156 | proof of concept and present our findings in the near future. 157 | 158 | 159 | 160 | 176 | 177 | 190 | 191 | 199 | 200 | # Acknowledgements 201 | 202 | S.G.B. and T.D.S. acknowledge support by the National Science Foundation, USA under 203 | Grant No. DMR-1651668. H.M.S.and T.D.S. acknowledge support by the National Science 204 | Foundation, USA under Grant No. OMA-1936383. C.S. and T.D.S. acknowledge support by the 205 | National Science Foundation, USA under Grant No. DMR-1950589. K.M.J. and B.S. 206 | acknowledge support by the MARVEL National Centre for Competence in Research funded by 207 | the Swiss National Science Foundation (grant agreement ID 51NF40-182892). 208 | 209 | # References 210 | -------------------------------------------------------------------------------- /reports/parameters-notes.txt: -------------------------------------------------------------------------------- 1 | atom_range * 2 float + 1 constraint 2 | a_range * 2 float + 1 constraint 3 | b_range * 2 float + 1 constraint 4 | volume_range * 2 float + 1 constraint 5 | distance_distance_range float * 2 + 1 constraint 6 | symprec * 2 float + 1 constraint 7 | angle_tolerance * 2 float + 1 constraint 8 | 9 | -- 14 float parameters, 7 constraints 10 | 11 | max_sites * 1 integer 12 | channels : categorical/(integer?), 2 options (could have as a somewhat unconstrained integer variable when using e.g. imagen-pytorch) 13 | 14 | -- 2 integer parameters 15 | 16 | relax_on_decode, categorical/boolean, 2 options 17 | volume_mask, space_group_mask, distance_mask, lower_tri_mask - 4 boolean 18 | 19 | 5 boolean parameters 20 | 21 | element_coder, categorical, ~5 options (?) 22 | # fit, categorical/boolean, 2 options 23 | encode_cell_type, categorical, 5 options 24 | decode_cell_type (fix to encode_cell_type) 25 | 26 | 2 categorical parameters, each with ~5 options 27 | 28 | Validity, Uniqueness, Novelty 29 | 30 | 3 objectives 31 | 32 | 14+2+5+2 = 23 33 | 34 | 23 parameters, 7 constraints, and 3 objectives 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | importlib_metadata==4.11.4 2 | kaleido 3 | numpy==1.22.4 4 | pillow==9.3.0 5 | plotly 6 | pymatgen==2022.5.19 7 | -------------------------------------------------------------------------------- /scripts/denoising_diffusion_pytorch_example.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from pathlib import Path 3 | from uuid import uuid4 4 | 5 | from denoising_diffusion_pytorch import GaussianDiffusion, Trainer, Unet 6 | from mp_time_split.core import MPTimeSplit 7 | 8 | from xtal2png.core import XtalConverter 9 | 10 | # import numpy as np 11 | 12 | 13 | mpt = MPTimeSplit() 14 | mpt.load() 15 | 16 | fold = 0 17 | train_inputs, val_inputs, train_outputs, val_outputs = mpt.get_train_and_val_data(fold) 18 | # train_idx = np.random.permutation(len(train_inputs)) 19 | # train_inputs = train_inputs.iloc[train_idx] 20 | # train_outputs = train_outputs.iloc[train_idx] 21 | 22 | channels = 1 23 | data_path = path.join("data", "preprocessed", "mp-time-split", f"fold={fold}") 24 | xc = XtalConverter( 25 | save_dir=data_path, 26 | encode_as_primitive=True, 27 | decode_as_primitive=True, 28 | channels=channels, 29 | ) 30 | xc.xtal2png(train_inputs.tolist()) 31 | 32 | model = Unet(dim=64, dim_mults=(1, 2, 4, 8), channels=channels).cuda() 33 | 34 | diffusion = GaussianDiffusion( 35 | model, channels=channels, image_size=64, timesteps=1000, loss_type="l1" 36 | ).cuda() 37 | 38 | train_batch_size = 32 39 | print("train_batch_size: ", train_batch_size) 40 | 41 | results_folder = path.join( 42 | "data", "interim", "denoising_diffusion_pytorch", f"fold={fold}", str(uuid4())[0:4] 43 | ) 44 | Path(results_folder).mkdir(exist_ok=True, parents=True) 45 | 46 | trainer = Trainer( 47 | diffusion, 48 | data_path, 49 | image_size=64, 50 | train_batch_size=train_batch_size, 51 | train_lr=2e-5, 52 | train_num_steps=700000, # total training steps 53 | gradient_accumulate_every=2, # gradient accumulation steps 54 | ema_decay=0.995, # exponential moving average decay 55 | amp=True, # turn on mixed precision 56 | augment_horizontal_flip=False, 57 | results_folder=results_folder, 58 | ) 59 | 60 | trainer.train() 61 | 62 | sampled_images = diffusion.sample(batch_size=100) 63 | 64 | 1 + 1 65 | -------------------------------------------------------------------------------- /scripts/denoising_diffusion_pytorch_pretrained_sample.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from os import path 4 | from pathlib import Path 5 | 6 | import numpy as np 7 | from denoising_diffusion_pytorch import GaussianDiffusion, Trainer, Unet 8 | from mp_time_split.core import MPTimeSplit 9 | from PIL import Image 10 | from pymatgen.core.composition import Composition 11 | from pymatviz.elements import ptable_heatmap_plotly 12 | 13 | from xtal2png.core import XtalConverter 14 | from xtal2png.utils.data import rgb_scaler 15 | 16 | fold = 0 17 | 18 | model = Unet(dim=64, dim_mults=(1, 2, 4, 8), channels=1).cuda() 19 | 20 | diffusion = GaussianDiffusion( 21 | model, channels=1, image_size=64, timesteps=1000, loss_type="l1" 22 | ).cuda() 23 | 24 | train_batch_size = 32 25 | print("train_batch_size: ", train_batch_size) 26 | 27 | uid = "5eab" 28 | results_folder = path.join( 29 | "data", "interim", "denoising_diffusion_pytorch", f"fold={fold}", uid 30 | ) 31 | Path(results_folder).mkdir(exist_ok=True, parents=True) 32 | 33 | data_path = path.join("data", "preprocessed", "mp-time-split", f"fold={fold}") 34 | 35 | fnames = os.listdir(results_folder) 36 | 37 | # i.e. "model-1.pt" --> "1.pt" --> "1" --> 1 38 | checkpoints = [int(name.split("-")[1].split(".")[0]) for name in fnames] 39 | checkpoint = np.max(checkpoints) 40 | 41 | print(f"checkpoint: {checkpoint}") 42 | 43 | trainer = Trainer( 44 | diffusion, 45 | data_path, 46 | image_size=64, 47 | train_batch_size=train_batch_size, 48 | train_lr=2e-5, 49 | train_num_steps=700000, # total training steps 50 | gradient_accumulate_every=2, # gradient accumulation steps 51 | ema_decay=0.995, # exponential moving average decay 52 | amp=True, # turn on mixed precision 53 | augment_horizontal_flip=False, 54 | results_folder=results_folder, 55 | ) 56 | 57 | trainer.load(checkpoint) 58 | 59 | diffusion = trainer.model 60 | 61 | img_arrays_torch = diffusion.sample(batch_size=16) 62 | unscaled_arrays = np.squeeze(img_arrays_torch.cpu().numpy()) 63 | rgb_arrays = rgb_scaler(unscaled_arrays, data_range=(0, 1)) 64 | 65 | mode = "L" 66 | if mode == "RGB": 67 | rgb_arrays = [arr.transpose(1, 2, 0) for arr in rgb_arrays] 68 | sampled_images = [Image.fromarray(np.uint8(arr), mode) for arr in rgb_arrays] 69 | 70 | gen_path = path.join( 71 | "data", 72 | "preprocessed", 73 | "mp-time-split", 74 | "denoising_diffusion_pytorch", 75 | f"fold={fold}", 76 | uid, 77 | ) 78 | xc = XtalConverter( 79 | save_dir=gen_path, encode_as_primitive=True, decode_as_primitive=True 80 | ) 81 | structures = xc.png2xtal(sampled_images, save=True) 82 | 83 | space_group = [] 84 | W = [] 85 | for s in structures: 86 | try: 87 | space_group.append(s.get_space_group_info(symprec=0.1)[1]) 88 | except Exception as e: 89 | W.append(e) 90 | space_group.append(None) 91 | print(space_group) 92 | 93 | mpt = MPTimeSplit() 94 | mpt.load() 95 | train_inputs, val_inputs, train_outputs, val_outputs = mpt.get_train_and_val_data(fold) 96 | 97 | equimolar_compositions = train_inputs.apply( 98 | lambda s: Composition(re.sub(r"\d", "", s.formula)) 99 | ) 100 | fig = ptable_heatmap_plotly(equimolar_compositions) 101 | fig.show() 102 | 103 | 1 + 1 104 | 105 | # %% Code Graveyard 106 | # compositions = train_inputs.apply(lambda s: s.composition) 107 | # atomic_numbers = train_inputs.apply(lambda s: np.unique(s.atomic_numbers)) 108 | -------------------------------------------------------------------------------- /scripts/imagen_pytorch_example.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from uuid import uuid4 3 | 4 | import torch 5 | from imagen_pytorch import BaseUnet64, Imagen, ImagenTrainer, Unet 6 | from mp_time_split.core import MPTimeSplit 7 | 8 | from xtal2png.core import XtalConverter 9 | 10 | low_mem = False 11 | max_batch_size = 16 12 | 13 | mpt = MPTimeSplit() 14 | mpt.load() 15 | 16 | fold = 0 17 | train_inputs, val_inputs, train_outputs, val_outputs = mpt.get_train_and_val_data(fold) 18 | 19 | xc = XtalConverter(save_dir="tmp", encode_as_primitive=True, decode_as_primitive=True) 20 | arrays, _, _ = xc.structures_to_arrays(train_inputs.tolist(), rgb_scaling=False) 21 | training_images = torch.from_numpy(arrays).float().cuda() 22 | 23 | # unets for unconditional imagen 24 | 25 | unet1 = Unet( 26 | dim=32, 27 | dim_mults=(1, 2, 4), 28 | num_resnet_blocks=3, 29 | layer_attns=(False, True, True), 30 | layer_cross_attns=(False, True, True), 31 | use_linear_attn=True, 32 | ) 33 | 34 | if low_mem: 35 | unet2 = Unet( 36 | dim=32, 37 | dim_mults=(1, 2, 4), 38 | num_resnet_blocks=3, 39 | layer_attns=(False, True, True), 40 | layer_cross_attns=(False, True, True), 41 | use_linear_attn=True, 42 | ) 43 | else: 44 | # unet2 = SRUnet256() 45 | unet2 = BaseUnet64() 46 | 47 | # imagen, which contains the unets above (base unet and super resoluting ones) 48 | 49 | imagen = Imagen( 50 | condition_on_text=False, # this must be set to False for unconditional Imagen 51 | unets=(unet1, unet2), 52 | channels=1, 53 | image_sizes=(32, 64), 54 | timesteps=1000, 55 | ) 56 | 57 | trainer = ImagenTrainer(imagen).cuda() 58 | 59 | # train each unet in concert, or separately (recommended) to completion 60 | 61 | for u in (1, 2): 62 | loss = trainer(training_images, unet_number=u, max_batch_size=max_batch_size) 63 | trainer.update(unet_number=u) 64 | 65 | # do the above for many many many many steps 66 | # now you can sample images unconditionally from the cascading unet(s) 67 | 68 | images = trainer.sample(batch_size=16, return_pil_images=True) # (16, 3, 128, 128) 69 | 70 | results_folder = path.join( 71 | "data", "interim", "imagen-pytorch", f"fold={fold}", str(uuid4())[0:4] + ".pt" 72 | ) 73 | trainer.save(results_folder) 74 | 1 + 1 75 | -------------------------------------------------------------------------------- /scripts/run_grayskull.py: -------------------------------------------------------------------------------- 1 | """Touch up the conda recipe from grayskull using conda-souschef.""" 2 | import os 3 | from copy import copy 4 | from os import getcwd 5 | from os.path import basename, dirname, join, normpath 6 | from pathlib import Path 7 | from shutil import copyfile 8 | from warnings import warn 9 | 10 | import numpy as np 11 | from souschef.recipe import Recipe 12 | 13 | import xtal2png as module 14 | 15 | # from packaging.version import VERSION_PATTERN 16 | 17 | 18 | name, version = module.__name__, module.__version__ 19 | 20 | replace_underscores_with_hyphens = True 21 | 22 | if replace_underscores_with_hyphens: 23 | name = name.replace("_", "-") 24 | 25 | src_dirname = "src" 26 | if basename(normpath(getcwd())) != src_dirname: 27 | warn( 28 | f"`meta.yaml` will be saved to {join(getcwd(), name)} instead of {join(src_dirname, name)}. If this is not the desired behavior, delete {join(getcwd(), name)}, `cd` to {src_dirname}, and rerun." # noqa: E501 29 | ) 30 | 31 | # Regex to match PEP440 compliant version strings 32 | # https://stackoverflow.com/a/38020327/13697228 33 | # _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) 34 | 35 | # if bool(_regex.match(version)): 36 | 37 | version = os.popen("git describe --abbrev=0 --tags").read().replace("\n", "") 38 | 39 | # warn("version is 'unknown', falling back to {version} via git tag") 40 | 41 | os.system(f"grayskull pypi {name}=={version}") 42 | 43 | # Whether to save meta.yaml and LICENSE.txt file to a "scratch" folder for `conda build` 44 | personal_conda_channel = False 45 | 46 | fpath = join(name, "meta.yaml") 47 | 48 | if personal_conda_channel: 49 | fpath2 = join(name, "scratch", "meta.yaml") 50 | Path(dirname(fpath2)).mkdir(exist_ok=True) 51 | 52 | my_recipe = Recipe(load_file=fpath) 53 | 54 | # ensure proper order for conda-forge 55 | keys = list(my_recipe.keys()) 56 | 57 | # https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html 58 | key_order = [ 59 | "package", 60 | "source", 61 | "build", 62 | "requirements", 63 | "test", 64 | "outputs", 65 | "about", 66 | "app", 67 | "extra", 68 | ] 69 | unshared_keys = np.setdiff1d(key_order, keys) 70 | 71 | ordered_keys = copy(key_order) 72 | for key in unshared_keys: 73 | ordered_keys.remove(key) 74 | 75 | for key in ordered_keys: 76 | my_recipe.yaml.move_to_end(key) 77 | 78 | my_recipe["build"].add_section({"noarch": "python"}) 79 | 80 | try: 81 | del my_recipe["build"]["skip"] 82 | except Exception as e: 83 | print(e) 84 | warn("Could not delete build: skip section (probably because it didn't exist)") 85 | 86 | try: 87 | del my_recipe["requirements"]["build"] 88 | except Exception as e: 89 | print(e) 90 | warn("Could not delete build section (probably because it didn't exist)") 91 | 92 | min_py_ver = "3.6" 93 | my_recipe["requirements"]["host"].remove("python") 94 | my_recipe["requirements"]["host"].append(f"python >={min_py_ver}") 95 | 96 | my_recipe["requirements"]["run"].remove("python") 97 | my_recipe["requirements"]["run"].append(f"python >={min_py_ver}") 98 | 99 | # remove the `# [py<38]` selector comment 100 | run_section = my_recipe["requirements"]["run"] 101 | idx = run_section.index("importlib-metadata") 102 | my_recipe["requirements"]["run"].remove("importlib-metadata") 103 | my_recipe["requirements"]["run"].append("importlib-metadata") 104 | 105 | # NOTE: how to add a doc_url? 106 | # my_recipe["about"].insert(0, "key", "value") 107 | 108 | # # sometimes package names differ between PyPI and Anaconda (e.g. `kaleido`) 109 | my_recipe["requirements"]["run"].replace("kaleido", "python-kaleido") 110 | 111 | # # It's better to install some packages either exclusively via Anaconda or 112 | # # via custom PyPI installation instructions (see e.g. the selectable table from: 113 | # # https://pytorch.org/get-started/locally/) 114 | # my_recipe["requirements"]["run"].append("pytorch >=1.9.0") 115 | # my_recipe["requirements"]["run"].append("cudatoolkit <11.4") 116 | 117 | my_recipe.save(fpath) 118 | 119 | if personal_conda_channel: 120 | my_recipe.save(fpath2) 121 | copyfile("LICENSE.txt", join(dirname(fpath2), "LICENSE.txt")) 122 | -------------------------------------------------------------------------------- /scripts/train_model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | import sys 4 | from pathlib import Path 5 | 6 | import click 7 | from IPython.core import ultratb 8 | 9 | import xtal2png 10 | 11 | # fallback to debugger on error 12 | sys.excepthook = ultratb.FormattedTB(mode="Verbose", color_scheme="Linux", call_pdb=1) 13 | # turn UserWarning messages to errors to find the actual cause 14 | # import warnings 15 | # warnings.simplefilter("error") 16 | 17 | _logger = logging.getLogger(__name__) 18 | 19 | 20 | @click.command() 21 | @click.option( 22 | "-c", 23 | "--config", 24 | "cfg_path", 25 | required=True, 26 | type=click.Path(exists=True), 27 | help="path to config file", 28 | ) 29 | @click.option("--quiet", "log_level", flag_value=logging.WARNING, default=True) 30 | @click.option("-v", "--verbose", "log_level", flag_value=logging.INFO) 31 | @click.option("-vv", "--very-verbose", "log_level", flag_value=logging.DEBUG) 32 | @click.version_option(xtal2png.__version__) 33 | def main(cfg_path: Path, log_level: int): 34 | logging.basicConfig( 35 | stream=sys.stdout, 36 | level=log_level, 37 | datefmt="%Y-%m-%d %H:%M", 38 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 39 | ) 40 | # YOUR CODE GOES HERE! Keep the main functionality in src/xtal2png 41 | # est = xtal2png.models.Estimator() 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is used to configure your project. 2 | # Read more about the various options under: 3 | # https://setuptools.pypa.io/en/latest/userguide/declarative_config.html 4 | # https://setuptools.pypa.io/en/latest/references/keywords.html 5 | 6 | [metadata] 7 | name = xtal2png 8 | description = Encode and decode crystal structures via portable networks graphics (PNG) files. 9 | author = sgbaird 10 | author_email = sterling.baird@utah.edu 11 | license = MIT 12 | license_files = LICENSE.txt 13 | long_description = file: README.md 14 | long_description_content_type = text/markdown; charset=UTF-8; variant=GFM 15 | url = https://github.com/sparks-baird/xtal2png/ 16 | # Add here related links, for example: 17 | project_urls = 18 | Documentation = https://xtal2png.readthedocs.io 19 | Source = https://github.com/sparks-baird/xtal2png 20 | # Changelog = https://pyscaffold.org/en/latest/changelog.html 21 | Tracker = https://github.com/sparks-baird/xtal2png/issues 22 | Conda-Forge = https://anaconda.org/conda-forge/xtal2png 23 | Download = https://pypi.org/project/xtal2png/#files 24 | Twitter = https://twitter.com/xtal2png 25 | 26 | # Change if running only on Windows, Mac or Linux (comma-separated) 27 | platforms = any 28 | 29 | # Add here all kinds of additional classifiers as defined under 30 | # https://pypi.org/classifiers/ 31 | classifiers = 32 | Development Status :: 4 - Beta 33 | Programming Language :: Python 34 | 35 | 36 | [options] 37 | zip_safe = False 38 | packages = find_namespace: 39 | include_package_data = True 40 | package_dir = 41 | =src 42 | 43 | # Require a min/specific Python version (comma-separated conditions) 44 | python_requires = >=3.6 45 | 46 | # Add here dependencies of your project (line-separated), e.g. requests>=2.2,<3.0. 47 | # Version specifiers like >=2.2,<3.0 avoid problems due to API changes in 48 | # new major versions. This works if the required packages follow Semantic Versioning. 49 | # For more information, check out https://semver.org/. 50 | install_requires = 51 | importlib-metadata; python_version<"3.8" 52 | numpy 53 | pillow 54 | pymatgen 55 | plotly 56 | kaleido 57 | element-coder 58 | click 59 | 60 | 61 | [options.packages.find] 62 | where = src 63 | exclude = 64 | tests 65 | 66 | [options.extras_require] 67 | # Add here additional requirements for extra features, to install with: 68 | # `pip install xtal2png[PDF]` like: 69 | # PDF = ReportLab; RXP 70 | relax = m3gnet 71 | 72 | # Add here test requirements (semicolon/line-separated) 73 | testing = 74 | setuptools 75 | pytest 76 | pytest-cov 77 | m3gnet 78 | 79 | [options.entry_points] 80 | # Add here console scripts like: 81 | # console_scripts = 82 | # script_name = xtal2png.module:function 83 | # For example: 84 | console_scripts = 85 | xtal2png = xtal2png.cli:cli 86 | # And any other entry points, for example: 87 | # pyscaffold.cli = 88 | # awesome = pyscaffoldext.awesome.extension:AwesomeExtension 89 | 90 | [tool:pytest] 91 | # Specify command line options as you would do when invoking pytest directly. 92 | # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml 93 | # in order to write a coverage file that can be read by Jenkins. 94 | # CAUTION: --cov flags may prohibit setting breakpoints while debugging. 95 | # Comment those flags to avoid this pytest issue. 96 | addopts = 97 | --cov xtal2png --cov-report term-missing 98 | --verbose 99 | --doctest-modules 100 | --doctest-glob "README.md" 101 | --doctest-continue-on-failure 102 | norecursedirs = 103 | dist 104 | build 105 | .tox 106 | testpaths = tests README.md 107 | # Use pytest markers to select/deselect specific tests 108 | # markers = 109 | # slow: mark tests as slow (deselect with '-m "not slow"') 110 | # system: mark end-to-end system tests 111 | 112 | [devpi:upload] 113 | # Options for the devpi: PyPI server and packaging tool 114 | # VCS export must be deactivated since we are using setuptools-scm 115 | no_vcs = 1 116 | formats = bdist_wheel 117 | 118 | [flake8] 119 | # Some sane defaults for the code style checker flake8 120 | max_line_length = 88 121 | extend_ignore = E203, W503 122 | # ^ Black-compatible 123 | # E203 and W503 have edge cases handled by black 124 | exclude = 125 | .tox 126 | build 127 | dist 128 | .eggs 129 | docs/conf.py 130 | 131 | [pyscaffold] 132 | # PyScaffold's parameters when the project was created. 133 | # This will be used when updating. Do not change! 134 | version = 4.2.1 135 | package = xtal2png 136 | extensions = 137 | dsproject 138 | github_actions 139 | markdown 140 | no_skeleton 141 | pre_commit 142 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup file for xtal2png. 3 | Use setup.cfg to configure your project. 4 | 5 | This file was generated with PyScaffold 4.2.1. 6 | PyScaffold helps you to put up the scaffold of your new Python project. 7 | Learn more under: https://pyscaffold.org/ 8 | """ 9 | from setuptools import setup 10 | 11 | if __name__ == "__main__": 12 | try: 13 | setup(use_scm_version={"version_scheme": "no-guess-dev"}) 14 | except: # noqa 15 | print( 16 | "\n\nAn error occurred while building the project, " 17 | "please ensure you have the most updated version of setuptools, " 18 | "setuptools_scm and wheel with:\n" 19 | " pip install -U setuptools setuptools_scm wheel\n\n" 20 | ) 21 | raise 22 | -------------------------------------------------------------------------------- /src/xtal2png/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info[:2] >= (3, 8): 4 | # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8` 5 | from importlib.metadata import PackageNotFoundError, version # pragma: no cover 6 | else: 7 | from importlib_metadata import PackageNotFoundError, version # pragma: no cover 8 | 9 | try: 10 | # Change here if project is renamed and does not equal the package name 11 | dist_name = __name__ 12 | __version__ = version(dist_name) 13 | except PackageNotFoundError: # pragma: no cover 14 | __version__ = "unknown" 15 | finally: 16 | del version, PackageNotFoundError 17 | 18 | # Public API 19 | 20 | from xtal2png.core import XtalConverter 21 | from xtal2png.utils.data import dummy_structures, example_structures 22 | 23 | __all__ = ["XtalConverter", "dummy_structures", "example_structures"] 24 | -------------------------------------------------------------------------------- /src/xtal2png/cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from glob import glob 4 | 5 | import click 6 | from click._compat import get_text_stderr 7 | from click.exceptions import UsageError 8 | from click.utils import echo 9 | 10 | from xtal2png import __version__ 11 | from xtal2png.core import XtalConverter, _logger, setup_logging 12 | 13 | 14 | def _show_usage_error(self, file=None): 15 | if file is None: 16 | file = get_text_stderr() 17 | color = None 18 | 19 | echo("Error: %s" % self.format_message(), file=file, color=color) 20 | if self.ctx is not None: 21 | color = self.ctx.color 22 | echo("\n\n" + self.ctx.get_help() + "\n", file=file, color=color) 23 | 24 | 25 | UsageError.show = _show_usage_error # type: ignore 26 | 27 | 28 | def check_save_dir(save_dir): 29 | if save_dir is None: 30 | raise UsageError("Please specify a path to a directory to save the PNG files.") 31 | 32 | 33 | def check_path(path, extension): 34 | if path is None: 35 | raise UsageError( 36 | f"Please specify a path to a {extension} file or " 37 | f"directory containing {extension} files." 38 | ) 39 | 40 | 41 | def check_files(path, extension): 42 | if os.path.isdir(path): 43 | files = glob(os.path.join(path, f"*.{extension}")) 44 | if not files: 45 | raise UsageError(f"No {extension.upper()} files found in directory: {path}") 46 | elif os.path.isfile(path): 47 | if not path.endswith(f".{extension}"): 48 | raise UsageError(f"File must have .{extension} extension: {path}") 49 | files = [path] 50 | 51 | return files 52 | 53 | 54 | @click.command("cli") 55 | @click.option("--version", is_flag=True, help="Show version.") 56 | @click.option( 57 | "--path", 58 | "-p", 59 | type=click.Path( 60 | exists=True, 61 | dir_okay=True, 62 | file_okay=True, 63 | readable=True, 64 | ), 65 | help="Crystallographic information file (CIF) filepath " 66 | " (extension must be .cif or .CIF)" 67 | " or path to directory containing .cif files or processed PNG filepath" 68 | " or path to directory containing processed .png files " 69 | "(extension must be .png or .PNG). " 70 | "Assumes CIFs if --encode flag is used. Assumes PNGs if --decode flag is used.", 71 | ) 72 | @click.option( 73 | "--save-dir", 74 | "-s", 75 | type=click.Path(exists=False), 76 | help="Encode CIF files as PNG images.", 77 | ) 78 | @click.option( 79 | "--encode", "runtype", flag_value="encode", help="Encode CIF files as PNG images." 80 | ) 81 | @click.option( 82 | "--decode", "runtype", flag_value="decode", help="Decode PNG images to CIF files." 83 | ) 84 | @click.option("--verbose", "-v", help="Set loglevel to INFO.") 85 | @click.option("--very-verbose", "-vv", help="Set loglevel to INFO.") 86 | @click.option( 87 | "--max-sites", 88 | "-ms", 89 | type=int, 90 | default=52, 91 | help="Maximum number of sites to accomodate in encoding, by default 52", 92 | ) 93 | @click.pass_context 94 | def cli(ctx, version, path, save_dir, runtype, verbose, very_verbose, max_sites): 95 | """ 96 | xtal2png command line interface. 97 | """ 98 | if version: 99 | click.echo("xtal2png version: {}".format(__version__)) 100 | return 101 | if verbose: 102 | setup_logging(loglevel=logging.INFO) 103 | if very_verbose: 104 | setup_logging(loglevel=logging.DEBUG) 105 | 106 | if not runtype and (path or save_dir): 107 | raise UsageError("Please specify --encode or --decode.") 108 | 109 | _logger.debug("Beginning conversion to PNG format") 110 | 111 | if runtype == "encode": 112 | check_path(path, "CIF") 113 | check_save_dir(save_dir) 114 | 115 | files = check_files(path, "cif") 116 | 117 | xc = XtalConverter(save_dir=save_dir, max_sites=max_sites) 118 | xc.xtal2png(files, save=True) 119 | return 120 | 121 | elif runtype == "decode": 122 | check_path(path, "PNG") 123 | check_save_dir(save_dir) 124 | 125 | files = check_files(path, "png") 126 | 127 | xc = XtalConverter(save_dir=save_dir, max_sites=max_sites) 128 | xc.png2xtal(files, save=True) 129 | return 130 | 131 | click.echo(ctx.get_help()) 132 | 133 | 134 | if __name__ == "__main__": 135 | cli() 136 | -------------------------------------------------------------------------------- /src/xtal2png/utils/README.md: -------------------------------------------------------------------------------- 1 | ### CIF References 2 | - [Zn2B2PbO6.cif](Zn2B2PbO6.cif): 3 | 4 | Data retrieved from the Materials Project for Zn2B2PbO6 ([mp-560471](https://next-gen.materialsproject.org/materials/mp-560471)) from database version v2021.11.10. 5 | 6 | - [V2NiSe4.cif](V2NiSe4.cif) 7 | 8 | Data retrieved from the Materials Project for V2NiSe4 ([mp-7823](https://next-gen.materialsproject.org/materials/mp-7823)) from database version v2021.11.10. 9 | -------------------------------------------------------------------------------- /src/xtal2png/utils/V2NiSe4.cif: -------------------------------------------------------------------------------- 1 | # generated using pymatgen 2 | data_V2NiSe4 3 | _symmetry_space_group_name_H-M C2/m 4 | _cell_length_a 12.95611800 5 | _cell_length_b 3.39550400 6 | _cell_length_c 6.19321991 7 | _cell_angle_alpha 90.00000000 8 | _cell_angle_beta 116.76664345 9 | _cell_angle_gamma 90.00000000 10 | _symmetry_Int_Tables_number 12 11 | _chemical_formula_structural V2NiSe4 12 | _chemical_formula_sum 'V4 Ni2 Se8' 13 | _cell_volume 243.26142717 14 | _cell_formula_units_Z 2 15 | loop_ 16 | _symmetry_equiv_pos_site_id 17 | _symmetry_equiv_pos_as_xyz 18 | 1 'x, y, z' 19 | 2 '-x, -y, -z' 20 | 3 '-x, y, -z' 21 | 4 'x, -y, z' 22 | 5 'x+1/2, y+1/2, z' 23 | 6 '-x+1/2, -y+1/2, -z' 24 | 7 '-x+1/2, y+1/2, -z' 25 | 8 'x+1/2, -y+1/2, z' 26 | loop_ 27 | _atom_type_symbol 28 | _atom_type_oxidation_number 29 | V3+ 3.0 30 | Ni2+ 2.0 31 | Se2- -2.0 32 | loop_ 33 | _atom_site_type_symbol 34 | _atom_site_label 35 | _atom_site_symmetry_multiplicity 36 | _atom_site_fract_x 37 | _atom_site_fract_y 38 | _atom_site_fract_z 39 | _atom_site_occupancy 40 | V3+ V0 4 0.24525600 0.50000000 0.68740300 1 41 | Ni2+ Ni1 2 0.00000000 0.00000000 0.00000000 1 42 | Se2- Se2 4 0.10484600 0.00000000 0.44654500 1 43 | Se2- Se3 4 0.13939000 0.50000000 0.97786100 1 44 | -------------------------------------------------------------------------------- /src/xtal2png/utils/Zn2B2PbO6.cif: -------------------------------------------------------------------------------- 1 | # generated using pymatgen 2 | data_Zn2B2PbO6 3 | _symmetry_space_group_name_H-M Pccn 4 | _cell_length_a 5.03378800 5 | _cell_length_b 11.52302100 6 | _cell_length_c 10.74117000 7 | _cell_angle_alpha 90.00000000 8 | _cell_angle_beta 90.00000000 9 | _cell_angle_gamma 90.00000000 10 | _symmetry_Int_Tables_number 56 11 | _chemical_formula_structural Zn2B2PbO6 12 | _chemical_formula_sum 'Zn8 B8 Pb4 O24' 13 | _cell_volume 623.03560271 14 | _cell_formula_units_Z 4 15 | loop_ 16 | _symmetry_equiv_pos_site_id 17 | _symmetry_equiv_pos_as_xyz 18 | 1 'x, y, z' 19 | 2 '-x, -y, -z' 20 | 3 '-x+1/2, -y+1/2, z' 21 | 4 'x+1/2, y+1/2, -z' 22 | 5 'x+1/2, -y, -z+1/2' 23 | 6 '-x+1/2, y, z+1/2' 24 | 7 '-x, y+1/2, -z+1/2' 25 | 8 'x, -y+1/2, z+1/2' 26 | loop_ 27 | _atom_type_symbol 28 | _atom_type_oxidation_number 29 | Zn2+ 2.0 30 | B3+ 3.0 31 | Pb2+ 2.0 32 | O2- -2.0 33 | loop_ 34 | _atom_site_type_symbol 35 | _atom_site_label 36 | _atom_site_symmetry_multiplicity 37 | _atom_site_fract_x 38 | _atom_site_fract_y 39 | _atom_site_fract_z 40 | _atom_site_occupancy 41 | Zn2+ Zn0 8 0.18117300 0.50072900 0.84958500 1 42 | B3+ B1 8 0.19372900 0.60171000 0.58405600 1 43 | Pb2+ Pb2 4 0.25000000 0.25000000 0.05941000 1 44 | O2- O3 8 0.07797100 0.12464400 0.91056100 1 45 | O2- O4 8 0.16038400 0.62347500 0.97747400 1 46 | O2- O5 8 0.18185200 0.55318900 0.18686500 1 47 | -------------------------------------------------------------------------------- /src/xtal2png/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/src/xtal2png/utils/__init__.py -------------------------------------------------------------------------------- /src/xtal2png/utils/data.py: -------------------------------------------------------------------------------- 1 | from importlib.resources import read_text 2 | from typing import Optional, Sequence 3 | from warnings import warn 4 | 5 | import numpy as np 6 | from numpy.testing import assert_allclose, assert_equal 7 | from numpy.typing import ArrayLike 8 | from pymatgen.analysis.structure_matcher import ElementComparator, StructureMatcher 9 | from pymatgen.core.lattice import Lattice 10 | from pymatgen.core.structure import Structure 11 | from pymatgen.symmetry.analyzer import SpacegroupAnalyzer 12 | 13 | coords = [[0, 0, 0], [0.75, 0.5, 0.75]] 14 | lattice = Lattice.from_parameters(a=3.84, b=3.84, c=3.84, alpha=120, beta=90, gamma=60) 15 | dummy_structures = [ 16 | Structure(lattice, ["Si", "Si"], coords), 17 | Structure(lattice, ["Ni", "Ni"], coords), 18 | ] 19 | 20 | EXAMPLE_CIFS = ["Zn2B2PbO6.cif", "V2NiSe4.cif"] 21 | example_structures = [] 22 | for cif in EXAMPLE_CIFS: 23 | cif_str = read_text("xtal2png.utils", cif) 24 | example_structures.append(Structure.from_str(cif_str, "cif")) 25 | 26 | 27 | # ToDo: potentially expose tolerance options 28 | def _get_space_group(s: Structure) -> int: 29 | """Get space group from structure. 30 | See issue https://github.com/sparks-baird/xtal2png/issues/184 31 | """ 32 | try: 33 | return int(np.round(s.get_space_group_info()[1])) 34 | except TypeError: 35 | # 0 should be fine as it is not taken 36 | return 0 37 | 38 | 39 | def element_wise_scaler( 40 | X: ArrayLike, 41 | feature_range: Optional[Sequence] = None, 42 | data_range: Optional[Sequence] = None, 43 | ): 44 | """Scale parameters according to a prespecified min and max (``data_range``). 45 | 46 | ``feature_range`` is preserved from MinMaxScaler 47 | 48 | See Also 49 | -------- 50 | sklearn.preprocessing.MinMaxScaler : Scale each feature to a given range. 51 | 52 | Parameters 53 | ---------- 54 | X : ArrayLike 55 | Features to be scaled element-wise. 56 | feature_range : Sequence 57 | The scaled values will span the range of ``feature_range`` 58 | data_range : Sequence 59 | Expected bounds for the data, e.g. 0 to 117 for periodic elements 60 | 61 | Returns 62 | ------- 63 | X_scaled 64 | Element-wise scaled values. 65 | 66 | Examples 67 | -------- 68 | >>> element_wise_scaler([[1, 2], [3, 4]], feature_range=[1, 4], data_range=[0, 8]) 69 | array([[1.375, 1.75 ], 70 | [2.125, 2.5 ]]) 71 | """ 72 | if not isinstance(X, np.ndarray): 73 | X = np.array(X) 74 | if data_range is None: 75 | data_range = [np.min(X), np.max(X)] 76 | if feature_range is None: 77 | feature_range = [np.min(X), np.max(X)] 78 | 79 | data_min, data_max = data_range 80 | feature_min, feature_max = feature_range 81 | # following modified from: 82 | # https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html 83 | X_std = (X - data_min) / (data_max - data_min) 84 | X_scaled = X_std * (feature_max - feature_min) + feature_min 85 | return X_scaled 86 | 87 | 88 | def element_wise_unscaler( 89 | X_scaled: ArrayLike, 90 | feature_range: Sequence, 91 | data_range: Sequence, 92 | ): 93 | """Scale parameters according to a prespecified min and max (``data_range``). 94 | 95 | ``feature_range`` is preserved from MinMaxScaler 96 | 97 | See Also 98 | -------- 99 | sklearn.preprocessing.MinMaxScaler : Scale each feature to a given range. 100 | 101 | Parameters 102 | ---------- 103 | X : ArrayLike 104 | Element-wise scaled values. 105 | feature_range : Sequence 106 | The scaled values will span the range of ``feature_range`` 107 | data_range : Sequence 108 | Expected bounds for the data, e.g. 0 to 117 for periodic elements 109 | 110 | Returns 111 | ------- 112 | X 113 | Element-wise unscaled values. 114 | 115 | Examples 116 | -------- 117 | >>> element_wise_unscaler( 118 | ... [[1.375, 1.75], [2.125, 2.5]], feature_range=[1, 4], data_range=[0, 8] 119 | ... ) 120 | array([[1., 2.], 121 | [3., 4.]]) 122 | 123 | """ 124 | if not isinstance(X_scaled, np.ndarray): 125 | X_scaled = np.array(X_scaled) 126 | 127 | data_min, data_max = data_range 128 | feature_min, feature_max = feature_range 129 | # following modified from: 130 | # https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html 131 | 132 | # inverse transform, checked against Mathematica 133 | X_std = (X_scaled - feature_min) / (feature_max - feature_min) 134 | X = data_min + (data_max - data_min) * X_std 135 | return X 136 | 137 | 138 | def rgb_scaler( 139 | X: ArrayLike, 140 | data_range: Optional[Sequence] = None, 141 | ): 142 | """Scale parameters according to RGB scale (0 to 255). 143 | 144 | ``feature_range`` is fixed to [0, 255], ``data_range`` is either specified 145 | 146 | See Also 147 | -------- 148 | sklearn.preprocessing.MinMaxScaler : Scale each feature to a given range. 149 | 150 | Parameters 151 | ---------- 152 | X : ArrayLike 153 | Features to be scaled element-wise. 154 | data_range : Optional[Sequence] 155 | Range to use in place of np.min(X) and np.max(X) as in ``MinMaxScaler``. 156 | 157 | Returns 158 | ------- 159 | X_scaled 160 | Element-wise scaled values. 161 | 162 | Examples 163 | -------- 164 | >>> rgb_scaler([[1, 2], [3, 4]], data_range=[0, 8]) 165 | array([[ 32, 64], 166 | [ 96, 128]], dtype=uint8) 167 | """ 168 | rgb_range = [0, 255] 169 | X_scaled = element_wise_scaler(X, data_range=data_range, feature_range=rgb_range) 170 | X_scaled = np.round(X_scaled).astype(int) 171 | return X_scaled 172 | 173 | 174 | def rgb_unscaler( 175 | X: ArrayLike, 176 | data_range: Sequence, 177 | ): 178 | """Unscale parameters from their RGB scale (0 to 255). 179 | 180 | ``feature_range`` is fixed to [0, 255], ``data_range`` is either specified or 181 | calculated based on min and max. 182 | 183 | See Also 184 | -------- 185 | sklearn.preprocessing.MinMaxScaler : Scale each feature to a given range. 186 | 187 | Parameters 188 | ---------- 189 | X : ArrayLike 190 | Element-wise scaled values. 191 | data_range : Optional[Sequence] 192 | Range to use in place of np.min(X) and np.max(X) as in ``class:MinMaxScaler``. 193 | 194 | Returns 195 | ------- 196 | X 197 | Unscaled features. 198 | 199 | Examples 200 | -------- 201 | >>> rgb_unscaler([[32, 64], [96, 128]], data_range=[0, 8]) 202 | array([[1, 2], 203 | [3, 4]]) 204 | """ 205 | rgb_range = [0, 255] 206 | X_scaled = element_wise_unscaler(X, data_range=data_range, feature_range=rgb_range) 207 | return X_scaled 208 | 209 | 210 | def get_image_mode(d: np.ndarray) -> str: 211 | """Get the image mode (i.e. "RGB" vs. grayscale ("L")) for an image array. 212 | 213 | Parameters 214 | ---------- 215 | d : np.ndarray 216 | A NumPy array with 3 dimensions, where the first dimension corresponds to the 217 | of image channels and the second and third dimensions correspond to the height and 218 | width of the image. 219 | 220 | Returns 221 | ------- 222 | mode : str 223 | "RGB" for 3-channel images and "L" for grayscale images. 224 | 225 | Raises 226 | ------ 227 | ValueError 228 | "expected an array with 3 dimensions, received {d.ndim} dims" 229 | ValueError 230 | "Expected a single-channel or 3-channel array, but received a {d.ndim}-channel 231 | array." 232 | 233 | Examples 234 | -------- 235 | >>> d = np.zeros((1, 64, 64), dtype=np.uint8) # grayscale image 236 | >>> mode = get_image_mode(d) 237 | "L" 238 | """ 239 | if d.ndim != 3: 240 | raise ValueError("expected an array with 3 dimensions, received {d.ndim} dims") 241 | if d.shape[0] == 3: 242 | mode = "RGB" 243 | elif d.shape[0] == 1: 244 | mode = "L" 245 | else: 246 | raise ValueError( 247 | f"Expected a single-channel or 3-channel array, but received a {d.ndim}-channel array." # noqa: E501 248 | ) 249 | 250 | return mode 251 | 252 | 253 | def unit_cell_converter( 254 | s: Structure, cell_type: Optional[str] = None, symprec=0.1, angle_tolerance=5.0 255 | ): 256 | """Convert from the original unit cell type to another unit cell via pymatgen. 257 | 258 | Parameters 259 | ---------- 260 | s : Structure 261 | a pymatgen Structure. 262 | cell_type : Optional[str], optional 263 | The cell type as a str or None if leaving the structure as-is. Possible options 264 | are "primitive_standard", "conventional_standard", "refined", "reduced", and 265 | None. By default None 266 | 267 | Returns 268 | ------- 269 | s : Structure 270 | The converted Structure. 271 | 272 | Raises 273 | ------ 274 | ValueError 275 | "Expected one of 'primitive_standard', 'conventional_standard', 'refined', 276 | 'reduced' or None, got {cell_type}" 277 | 278 | Examples 279 | -------- 280 | >>> s = unit_cell_converter(s, cell_type="reduced") 281 | """ 282 | spa = SpacegroupAnalyzer( 283 | s, 284 | symprec=symprec, 285 | angle_tolerance=angle_tolerance, 286 | ) 287 | if cell_type == "primitive_standard": 288 | s = spa.get_primitive_standard_structure() 289 | elif cell_type == "conventional_standard": 290 | s = spa.get_conventional_standard_structure() 291 | elif cell_type == "refined": 292 | s = spa.get_refined_structure() 293 | elif cell_type == "reduced": 294 | s = s.get_reduced_structure() 295 | elif cell_type is not None: 296 | raise ValueError( 297 | f"Expected one of 'primitive_standard', 'conventional_standard', 'refined', 'reduced' or None, got {cell_type}" # noqa: E501 298 | ) 299 | return s 300 | 301 | 302 | RGB_TOL = 1 / 255 # should this be 256? 303 | RGB_LOOSE_TOL = 1.5 / 255 304 | 305 | 306 | def assert_structures_approximate_match( 307 | example_structures, structures, tol_multiplier=1.0 308 | ): 309 | for i, (s, structure) in enumerate(zip(example_structures, structures)): 310 | dummy_matcher = StructureMatcher() 311 | ltol = dummy_matcher.ltol * tol_multiplier 312 | stol = dummy_matcher.stol * tol_multiplier 313 | angle_tol = dummy_matcher.angle_tol * tol_multiplier 314 | sm = StructureMatcher( 315 | ltol=ltol, 316 | stol=stol, 317 | angle_tol=angle_tol, 318 | comparator=ElementComparator(), 319 | ) 320 | is_match = sm.fit(s, structure) 321 | if not is_match: 322 | warn( 323 | f"{i}-th original and decoded structures do not match according to StructureMatcher(comparator=ElementComparator()).fit(s, structure).\n\nOriginal (s): {s}\n\nDecoded (structure): {structure}" # noqa: E501 324 | ) 325 | 326 | spa = SpacegroupAnalyzer(s, symprec=0.1, angle_tolerance=5.0) 327 | s = spa.get_refined_structure() 328 | spa = SpacegroupAnalyzer(structure, symprec=0.1, angle_tolerance=5.0) 329 | structure = spa.get_refined_structure() 330 | 331 | sm = StructureMatcher(primitive_cell=False, comparator=ElementComparator()) 332 | s2 = sm.get_s2_like_s1(s, structure) 333 | 334 | a_check = s._lattice.a 335 | b_check = s._lattice.b 336 | c_check = s._lattice.c 337 | angles_check = s._lattice.angles 338 | atomic_numbers_check = s.atomic_numbers 339 | frac_coords_check = s.frac_coords 340 | space_group_check = _get_space_group(s) 341 | 342 | latt_a = s2._lattice.a 343 | latt_b = s2._lattice.b 344 | latt_c = s2._lattice.c 345 | angles = s2._lattice.angles 346 | atomic_numbers = s2.atomic_numbers 347 | frac_coords = s2.frac_coords 348 | space_group = _get_space_group(s) 349 | 350 | assert_allclose( 351 | a_check, 352 | latt_a, 353 | rtol=RGB_LOOSE_TOL * tol_multiplier, 354 | err_msg="lattice parameter length `a` not all close", 355 | ) 356 | 357 | assert_allclose( 358 | b_check, 359 | latt_b, 360 | rtol=RGB_LOOSE_TOL * tol_multiplier, 361 | err_msg="lattice parameter length `b` not all close", 362 | ) 363 | 364 | assert_allclose( 365 | c_check, 366 | latt_c, 367 | rtol=RGB_LOOSE_TOL * 2 * tol_multiplier, 368 | err_msg="lattice parameter length `c` not all close", 369 | ) 370 | 371 | assert_allclose( 372 | angles_check, 373 | angles, 374 | rtol=RGB_LOOSE_TOL * tol_multiplier, 375 | err_msg="lattice parameter angles not all close", 376 | ) 377 | 378 | assert_allclose( 379 | atomic_numbers_check, 380 | atomic_numbers, 381 | rtol=RGB_LOOSE_TOL * tol_multiplier, 382 | err_msg="atomic numbers not all close", 383 | ) 384 | 385 | # use atol since frac_coords values are between 0 and 1 386 | assert_allclose( 387 | frac_coords_check, 388 | frac_coords, 389 | atol=RGB_TOL * tol_multiplier, 390 | err_msg="atomic numbers not all close", 391 | ) 392 | 393 | assert_equal( 394 | space_group_check, 395 | space_group, 396 | err_msg=f"space groups do not match. Original: {space_group_check}. Decoded: {space_group}.", # noqa: E501 397 | ) 398 | -------------------------------------------------------------------------------- /src/xtal2png/utils/mp_cifs.bib: -------------------------------------------------------------------------------- 1 | @article{osti_1271500, 2 | title = {Materials Data on Zn2B2PbO6 by Materials Project}, 3 | author = {The Materials Project}, 4 | abstractNote = {Zn2B2PbO6 crystallizes in the orthorhombic Pccn space group. The structure is three-dimensional. Zn2+ is bonded to four O2- atoms to form corner-sharing ZnO4 tetrahedra. There is three shorter (1.97 Å) and one longer (2.00 Å) Zn–O bond length. B3+ is bonded in a trigonal planar geometry to three O2- atoms. There is one shorter (1.38 Å) and two longer (1.39 Å) B–O bond length. Pb2+ is bonded in a rectangular see-saw-like geometry to four O2- atoms. There are two shorter (2.32 Å) and two longer (2.56 Å) Pb–O bond lengths. There are three inequivalent O2- sites. In the first O2- site, O2- is bonded in a 2-coordinate geometry to one Zn2+, one B3+, and one Pb2+ atom. In the second O2- site, O2- is bonded in a trigonal planar geometry to two equivalent Zn2+ and one B3+ atom. In the third O2- site, O2- is bonded in a distorted trigonal planar geometry to one Zn2+, one B3+, and one Pb2+ atom.}, 5 | doi = {10.17188/1271500}, 6 | journal = {}, 7 | number = , 8 | volume = , 9 | place = {United States}, 10 | year = {2020}, 11 | month = {5} 12 | } 13 | 14 | @article{osti_1307629, 15 | title = {Materials Data on V2NiSe4 by Materials Project}, 16 | author = {The Materials Project}, 17 | abstractNote = {V2NiSe4 crystallizes in the monoclinic C2/m space group. The structure is three-dimensional. V3+ is bonded to six Se2- atoms to form distorted VSe6 octahedra that share corners with six equivalent NiSe6 octahedra, edges with six equivalent VSe6 octahedra, and a faceface with one NiSe6 octahedra. The corner-sharing octahedra tilt angles range from 51–56°. There are a spread of V–Se bond distances ranging from 2.43–2.71 Å. Ni2+ is bonded to six Se2- atoms to form NiSe6 octahedra that share corners with twelve equivalent VSe6 octahedra, edges with two equivalent NiSe6 octahedra, and faces with two equivalent VSe6 octahedra. The corner-sharing octahedra tilt angles range from 51–56°. There are two shorter (2.47 Å) and four longer (2.53 Å) Ni–Se bond lengths. There are two inequivalent Se2- sites. In the first Se2- site, Se2- is bonded in a 4-coordinate geometry to three equivalent V3+ and one Ni2+ atom. In the second Se2- site, Se2- is bonded in a 5-coordinate geometry to three equivalent V3+ and two equivalent Ni2+ atoms.}, 18 | doi = {10.17188/1307629}, 19 | journal = {}, 20 | number = , 21 | volume = , 22 | place = {United States}, 23 | year = {2020}, 24 | month = {7} 25 | } 26 | -------------------------------------------------------------------------------- /src/xtal2png/utils/plotting.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import plotly.graph_objs as go 4 | from plotly import offline 5 | 6 | 7 | def matplotlibify( 8 | fig: go.Figure, 9 | size: int = 24, 10 | width_inches: Union[float, int] = 3.5, 11 | height_inches: Union[float, int] = 3.5, 12 | dpi: int = 142, 13 | return_scale: bool = False, 14 | ) -> go.Figure: 15 | """Make plotly figures look more like matplotlib for academic publishing. 16 | 17 | modified from: https://medium.com/swlh/fa56ddd97539 18 | 19 | Parameters 20 | ---------- 21 | fig : go.Figure 22 | Plotly figure to be matplotlibified 23 | size : int, optional 24 | Font size for layout and axes, by default 24 25 | width_inches : Union[float, int], optional 26 | Width of matplotlib figure in inches, by default 3.5 27 | height_inches : Union[float, int], optional 28 | Height of matplotlib figure in Inches, by default 3.5 29 | dpi : int, optional 30 | Dots per inch (resolution) of matplotlib figure, by default 142. Leave as 31 | default unless you're willing to verify nothing strange happens with the output. 32 | return_scale : bool, optional 33 | If true, then return `scale` which is a quantity that helps with creating a 34 | high-resolution image at the specified absolute width and height in inches. 35 | More specifically: 36 | >>> width_default_px = fig.layout.width 37 | >>> targ_dpi = 300 38 | >>> scale = width_inches / (width_default_px / dpi) * (targ_dpi / dpi) 39 | Feel free to ignore this parameter. 40 | 41 | Returns 42 | ------- 43 | fig : go.Figure 44 | The matplotlibified plotly figure. 45 | 46 | Examples 47 | -------- 48 | >>> import plotly.express as px 49 | >>> df = px.data.tips() 50 | >>> fig = px.histogram(df, x="day") 51 | >>> fig.show() 52 | >>> fig = matplotlibify(fig, size=24, width_inches=3.5, height_inches=3.5, dpi=142) 53 | >>> fig.show() 54 | 55 | Note the difference between 56 | https://user-images.githubusercontent.com/45469701/171044741-35591a2c-dede-4df1-ae47-597bbfdb89cf.png # noqa: E501 57 | and 58 | https://user-images.githubusercontent.com/45469701/171044746-84a0deb0-1e15-40bf-a459-a5a7d3425b20.png, # noqa: E501 59 | which are both static exports of interactive plotly figures. 60 | """ 61 | font_dict = dict(family="Arial", size=size, color="black") 62 | 63 | # app = QApplication(sys.argv) 64 | # screen = app.screens()[0] 65 | # dpi = screen.physicalDotsPerInch() 66 | # app.quit() 67 | 68 | fig.update_layout( 69 | font=font_dict, 70 | plot_bgcolor="white", 71 | width=width_inches * dpi, 72 | height=height_inches * dpi, 73 | margin=dict(r=40, t=20, b=10), 74 | ) 75 | 76 | fig.update_yaxes( 77 | showline=True, # add line at x=0 78 | linecolor="black", # line color 79 | linewidth=2.4, # line size 80 | ticks="inside", # ticks outside axis 81 | tickfont=font_dict, # tick label font 82 | mirror="allticks", # add ticks to top/right axes 83 | tickwidth=2.4, # tick width 84 | tickcolor="black", # tick color 85 | ) 86 | 87 | fig.update_xaxes( 88 | showline=True, 89 | showticklabels=True, 90 | linecolor="black", 91 | linewidth=2.4, 92 | ticks="inside", 93 | tickfont=font_dict, 94 | mirror="allticks", 95 | tickwidth=2.4, 96 | tickcolor="black", 97 | ) 98 | fig.update(layout_coloraxis_showscale=False) 99 | 100 | width_default_px = fig.layout.width 101 | targ_dpi = 300 102 | scale = width_inches / (width_default_px / dpi) * (targ_dpi / dpi) 103 | 104 | if return_scale: 105 | return fig, scale 106 | else: 107 | return fig 108 | 109 | 110 | def plot_and_save(fig_path, fig, mpl_kwargs={}, show=False, update_legend=False): 111 | if show: 112 | offline.plot(fig) 113 | fig.write_html(fig_path + ".html") 114 | fig.to_json(fig_path + ".json") 115 | if update_legend: 116 | fig.update_layout( 117 | legend=dict( 118 | font=dict(size=16), 119 | yanchor="bottom", 120 | y=0.99, 121 | xanchor="right", 122 | x=0.99, 123 | bgcolor="rgba(0,0,0,0)", 124 | # orientation="h", 125 | ) 126 | ) 127 | fig = matplotlibify(fig, **mpl_kwargs) 128 | fig.write_image(fig_path + ".png") 129 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparks-baird/xtal2png/b34d0a2b85c7da717020e5ee013f05726eaca254/tests/__init__.py -------------------------------------------------------------------------------- /tests/cli_test.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from click.testing import CliRunner 4 | 5 | from xtal2png.cli import cli 6 | 7 | THIS_DIR = path.dirname(path.abspath(__file__)) 8 | 9 | 10 | def test_encode_single(): 11 | fpath = path.abspath(path.join("src", "xtal2png", "utils", "Zn2B2PbO6.cif")) 12 | 13 | args = ["--encode", "--path", fpath, "--save-dir", "tmp"] 14 | 15 | runner = CliRunner() 16 | result = runner.invoke(cli, args) 17 | assert result.exit_code == 0 18 | 19 | 20 | def test_encode_dir(): 21 | fpath = path.abspath(path.join("src", "xtal2png", "utils")) 22 | args = [ 23 | "--encode", 24 | "--path", 25 | fpath, 26 | "--save-dir", 27 | path.join("tmp", "tmp"), 28 | "--max-sites", 29 | "1000", 30 | ] 31 | runner = CliRunner() 32 | result = runner.invoke(cli, args) 33 | assert result.exit_code == 0 34 | 35 | 36 | def test_decode_single(): 37 | fpath = path.join( 38 | "data", "preprocessed", "examples", "Zn8B8Pb4O24,volume=623,uid=b62a.png" 39 | ) 40 | args = ["--decode", "--path", fpath, "--save-dir", "tmp"] 41 | runner = CliRunner() 42 | result = runner.invoke(cli, args) 43 | assert result.exit_code == 0 44 | 45 | 46 | def test_decode_dir(): 47 | fpath = path.join("data", "preprocessed", "examples") 48 | args = ["--decode", "--path", fpath, "--save-dir", "tmp"] 49 | runner = CliRunner() 50 | result = runner.invoke(cli, args) 51 | assert result.exit_code == 0 52 | 53 | 54 | def test_disordered_structure(): 55 | fpath = path.join(THIS_DIR, "test_files", "disordered_structure.cif") 56 | args = ["--encode", "--path", fpath, "--save-dir", "tmp"] 57 | runner = CliRunner() 58 | result = runner.invoke(cli, args, catch_exceptions=True) 59 | assert result.exit_code == 1 60 | assert isinstance(result.exception, ValueError) 61 | assert "xtal2png does not support disordered structures." in str(result.exception) 62 | 63 | 64 | if __name__ == "__main__": 65 | test_encode_single() 66 | test_encode_dir() 67 | test_decode_single() 68 | test_decode_dir() 69 | test_disordered_structure() 70 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dummy conftest.py for xtal2png. 3 | 4 | If you don't know what this is for, just leave it empty. 5 | Read more about conftest.py under: 6 | - https://docs.pytest.org/en/stable/fixture.html 7 | - https://docs.pytest.org/en/stable/writing_plugins.html 8 | """ 9 | import os 10 | 11 | import pytest 12 | from pymatgen.core import Structure 13 | 14 | THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def get_disordered_structure(): 19 | return Structure.from_file( 20 | os.path.join(THIS_DIR, "test_files", "disordered_structure.cif") 21 | ) 22 | -------------------------------------------------------------------------------- /tests/customization_test.py: -------------------------------------------------------------------------------- 1 | """Test customization via XtalConverter kwargs such as 3-channels or max_sites.""" 2 | 3 | 4 | import numpy as np 5 | from pymatgen.symmetry.analyzer import SpacegroupAnalyzer 6 | 7 | from xtal2png.core import XtalConverter 8 | from xtal2png.utils.data import ( 9 | assert_structures_approximate_match, 10 | dummy_structures, 11 | example_structures, 12 | rgb_unscaler, 13 | ) 14 | 15 | 16 | def test_png2xtal_three_channels(): 17 | xc = XtalConverter(relax_on_decode=False, channels=3) 18 | imgs = xc.xtal2png(example_structures, show=False, save=False) 19 | img_shape = np.asarray(imgs[0]).shape 20 | if img_shape != (64, 64, 3): 21 | raise ValueError(f"Expected image shape: (3, 64, 64), received: {img_shape}") 22 | decoded_structures = xc.png2xtal(imgs) 23 | assert_structures_approximate_match( 24 | example_structures, decoded_structures, tol_multiplier=2.0 25 | ) 26 | 27 | 28 | def test_primitive_encoding(): 29 | xc = XtalConverter( 30 | symprec=0.1, 31 | angle_tolerance=5.0, 32 | encode_cell_type="primitive_standard", 33 | decode_cell_type=None, 34 | relax_on_decode=False, 35 | ) 36 | input_structures = [ 37 | SpacegroupAnalyzer( 38 | s, symprec=0.1, angle_tolerance=5.0 39 | ).get_conventional_standard_structure() 40 | for s in example_structures 41 | ] 42 | data, id_data, id_mapper = xc.structures_to_arrays(input_structures) 43 | decoded_structures = xc.arrays_to_structures(data, id_data, id_mapper) 44 | assert_structures_approximate_match( 45 | example_structures, decoded_structures, tol_multiplier=2.0 46 | ) 47 | return decoded_structures 48 | 49 | 50 | def test_primitive_decoding(): 51 | xc = XtalConverter( 52 | symprec=0.1, 53 | angle_tolerance=5.0, 54 | encode_cell_type=None, 55 | decode_cell_type="primitive_standard", 56 | relax_on_decode=False, 57 | ) 58 | input_structures = [ 59 | SpacegroupAnalyzer( 60 | s, symprec=0.1, angle_tolerance=5.0 61 | ).get_conventional_standard_structure() 62 | for s in example_structures 63 | ] 64 | data, id_data, id_mapper = xc.structures_to_arrays(input_structures) 65 | decoded_structures = xc.arrays_to_structures(data, id_data, id_mapper) 66 | # decoded has to be conventional too for compatibility with `get_s1_like_s2` 67 | decoded_structures = [ 68 | SpacegroupAnalyzer( 69 | s, symprec=0.1, angle_tolerance=5.0 70 | ).get_conventional_standard_structure() 71 | for s in decoded_structures 72 | ] 73 | assert_structures_approximate_match( 74 | example_structures, decoded_structures, tol_multiplier=2.0 75 | ) 76 | return decoded_structures 77 | 78 | 79 | def test_relax_on_decode(): 80 | xc = XtalConverter(relax_on_decode=True) 81 | imgs = xc.xtal2png(example_structures, show=False, save=False) 82 | decoded_structures = xc.png2xtal(imgs) 83 | assert_structures_approximate_match( 84 | example_structures, decoded_structures, tol_multiplier=4.0 85 | ) 86 | return decoded_structures 87 | 88 | 89 | def test_max_sites(): 90 | max_sites = 10 91 | xc = XtalConverter(max_sites=max_sites) 92 | imgs = xc.xtal2png(dummy_structures) 93 | width, height = imgs[0].size 94 | if width != max_sites + 12: 95 | raise ValueError( 96 | f"Image width is not correct. Got {width}, expected {max_sites + 12}" 97 | ) 98 | if height != max_sites + 12: 99 | raise ValueError( 100 | f"Image height is not correct. Got {height}, expected {max_sites + 12}" 101 | ) 102 | decoded_structures = xc.png2xtal(imgs) 103 | assert_structures_approximate_match(dummy_structures, decoded_structures) 104 | return decoded_structures 105 | 106 | 107 | def test_distance_mask(): 108 | xc = XtalConverter(mask_types=["distance"]) 109 | imgs = xc.xtal2png(example_structures) 110 | if not np.all(xc.data[xc.id_data == xc.id_mapper["distance"]] == 0): 111 | raise ValueError("Distance mask not applied correctly (id_mapper)") 112 | 113 | if not np.all(xc.data[:, :, 12:, 12:] == 0): 114 | raise ValueError("Distance mask not applied correctly (hardcoded)") 115 | 116 | return imgs 117 | 118 | 119 | def test_lower_tri_mask(): 120 | xc = XtalConverter(mask_types=["lower_tri"]) 121 | imgs = xc.xtal2png(example_structures) 122 | for d in xc.data: 123 | for sq in d: 124 | if not np.all(sq[np.tril_indices(sq.shape[0])] == 0): 125 | raise ValueError("Lower triangle mask not applied correctly") 126 | 127 | return imgs 128 | 129 | 130 | def test_mask_error(): 131 | xc = XtalConverter(mask_types=["num_sites"]) 132 | imgs = xc.xtal2png(example_structures) 133 | 134 | decoded_structures = xc.png2xtal(imgs) 135 | 136 | for s in decoded_structures: 137 | if s.num_sites > 0: 138 | raise ValueError("Num sites mask should have wiped out atomic sites.") 139 | 140 | 141 | def test_png2xtal_element_coder(): 142 | xc = XtalConverter(element_encoding="mod_pettifor", relax_on_decode=False) 143 | imgs = xc.xtal2png(example_structures) 144 | decoded_structures = xc.png2xtal(imgs) 145 | assert_structures_approximate_match( 146 | example_structures, decoded_structures, tol_multiplier=2.0 147 | ) 148 | 149 | 150 | def test_num_sites_mask(): 151 | xc = XtalConverter(relax_on_decode=False) 152 | data, id_data, id_mapper = xc.structures_to_arrays(example_structures) 153 | decoded_structures = xc.arrays_to_structures(data, id_data, id_mapper) 154 | 155 | num_sites = [d[idd == id_mapper["num_sites"]] for d, idd in zip(data, id_data)] 156 | num_sites = [ns[np.where(ns > 0)] for ns in num_sites] 157 | num_sites = rgb_unscaler(num_sites, xc.num_sites_range) 158 | num_sites = [np.round(np.mean(ns)).astype(int) for ns in num_sites] 159 | 160 | for i, (ns, s, ds) in enumerate( 161 | zip(num_sites, example_structures, decoded_structures) 162 | ): 163 | if ns != s.num_sites: 164 | raise ValueError( 165 | f"Num sites not encoded correctly for entry {i}. Received {ns}. Expected: {s.num_sites}" # noqa: E501 166 | ) 167 | if ds.num_sites != s.num_sites: 168 | raise ValueError( 169 | f"Num sites not decoded correctly for entry {i}. Received {ds.num_sites}. Expected: {s.num_sites}" # noqa: E501 170 | ) 171 | 172 | for ns, d in zip(num_sites, data): 173 | mask = np.zeros(d.shape, dtype=bool) 174 | mx = mask.shape[1] 175 | ms = xc.max_sites 176 | filler_dim = mx - ms # i.e. 12 177 | # apply mask to bottom and RHS blocks 178 | mask[:, :, filler_dim + ns :] = True 179 | mask[:, filler_dim + ns :, :] = True 180 | if not np.all(d[mask] == 0): 181 | raise ValueError( 182 | f"Num sites mask not applied correctly. Received: {d}. Expected all zeros for bottom and RHS (last {ns} entries of each)." # noqa: E501 183 | ) 184 | -------------------------------------------------------------------------------- /tests/decoding_test.py: -------------------------------------------------------------------------------- 1 | """Test decoding functionality using default kwargs and assert matches.""" 2 | 3 | 4 | from os import path 5 | 6 | from PIL import Image 7 | 8 | from xtal2png.core import XtalConverter 9 | from xtal2png.utils.data import assert_structures_approximate_match, example_structures 10 | 11 | 12 | def test_arrays_to_structures(): 13 | xc = XtalConverter() 14 | data, id_data, id_mapper = xc.structures_to_arrays(example_structures) 15 | structures = xc.arrays_to_structures(data, id_data, id_mapper) 16 | assert_structures_approximate_match( 17 | example_structures, structures, tol_multiplier=2.0 18 | ) 19 | return structures 20 | 21 | 22 | def test_arrays_to_structures_zero_one(): 23 | xc = XtalConverter() 24 | data, id_data, id_mapper = xc.structures_to_arrays( 25 | example_structures, rgb_scaling=False 26 | ) 27 | structures = xc.arrays_to_structures(data, id_data, id_mapper, rgb_scaling=False) 28 | assert_structures_approximate_match( 29 | example_structures, structures, tol_multiplier=2.0 30 | ) 31 | return structures 32 | 33 | 34 | def test_arrays_to_structures_single(): 35 | xc = XtalConverter() 36 | data, id_data, id_mapper = xc.structures_to_arrays([example_structures[0]]) 37 | structures = xc.arrays_to_structures(data, id_data, id_mapper) 38 | assert_structures_approximate_match( 39 | [example_structures[0]], structures, tol_multiplier=2.0 40 | ) 41 | return structures 42 | 43 | 44 | def test_png2xtal(): 45 | xc = XtalConverter() 46 | imgs = xc.xtal2png(example_structures, show=True, save=True) 47 | decoded_structures = xc.png2xtal(imgs) 48 | assert_structures_approximate_match( 49 | example_structures, decoded_structures, tol_multiplier=2.0 50 | ) 51 | 52 | 53 | def test_png2xtal_single(): 54 | xc = XtalConverter() 55 | imgs = xc.xtal2png([example_structures[0]], show=True, save=True) 56 | decoded_structures = xc.png2xtal(imgs, save=False) 57 | assert_structures_approximate_match( 58 | [example_structures[0]], decoded_structures, tol_multiplier=2.0 59 | ) 60 | return decoded_structures 61 | 62 | 63 | def test_png2xtal_rgb_image(): 64 | xc = XtalConverter() 65 | imgs = xc.xtal2png(example_structures, show=False, save=False) 66 | imgs = [img.convert("RGB") for img in imgs] 67 | decoded_structures = xc.png2xtal(imgs) 68 | assert_structures_approximate_match( 69 | example_structures, decoded_structures, tol_multiplier=2.0 70 | ) 71 | return decoded_structures 72 | 73 | 74 | def test_png2xtal_from_saved_images(): 75 | xc = XtalConverter() 76 | xc.xtal2png(example_structures, show=False, save=True) 77 | fpaths = [path.join(xc.save_dir, savename + ".png") for savename in xc.savenames] 78 | saved_imgs = [Image.open(fpath) for fpath in fpaths] 79 | 80 | decoded_structures = xc.png2xtal(saved_imgs) 81 | assert_structures_approximate_match( 82 | example_structures, decoded_structures, tol_multiplier=2.0 83 | ) 84 | -------------------------------------------------------------------------------- /tests/encoding_test.py: -------------------------------------------------------------------------------- 1 | """Test encoding functionality using default kwargs.""" 2 | 3 | 4 | import numpy as np 5 | import plotly.express as px 6 | import pytest 7 | 8 | from xtal2png.core import XtalConverter 9 | from xtal2png.utils.data import example_structures 10 | from xtal2png.utils.plotting import plot_and_save 11 | 12 | 13 | def test_structures_to_arrays(): 14 | xc = XtalConverter(relax_on_decode=False) 15 | data, _, _ = xc.structures_to_arrays(example_structures) 16 | return data 17 | 18 | 19 | def test_structures_to_arrays_single(): 20 | xc = XtalConverter(relax_on_decode=False) 21 | data, _, _ = xc.structures_to_arrays([example_structures[0]]) 22 | return data 23 | 24 | 25 | def test_xtal2png(): 26 | xc = XtalConverter(relax_on_decode=False) 27 | imgs = xc.xtal2png(example_structures, show=False, save=True) 28 | return imgs 29 | 30 | 31 | def test_xtal2png_single(): 32 | xc = XtalConverter(relax_on_decode=False) 33 | imgs = xc.xtal2png([example_structures[0]], show=False, save=True) 34 | return imgs 35 | 36 | 37 | def test_xtal2png_three_channels(): 38 | xc = XtalConverter(relax_on_decode=False, channels=3) 39 | imgs = xc.xtal2png(example_structures, show=False, save=False) 40 | return imgs 41 | 42 | 43 | def test_plot_and_save(): 44 | df = px.data.tips() 45 | fig = px.histogram(df, x="day") 46 | plot_and_save("reports/figures/tmp", fig, mpl_kwargs={}) 47 | 48 | 49 | def test_structures_to_arrays_zero_one(): 50 | xc = XtalConverter(relax_on_decode=False) 51 | data, _, _ = xc.structures_to_arrays(example_structures, rgb_scaling=False) 52 | 53 | if np.min(data) < 0.0: 54 | raise ValueError( 55 | f"minimum is less than 0 when rgb_output=False: {np.min(data)}" 56 | ) 57 | if np.max(data) > 1.0: 58 | raise ValueError( 59 | f"maximum is greater than 1 when rgb_output=False: {np.max(data)}" 60 | ) 61 | return data 62 | 63 | 64 | def test_disordered_fail(get_disordered_structure): 65 | xc = XtalConverter(relax_on_decode=False) 66 | with pytest.raises(ValueError): 67 | xc.structures_to_arrays([get_disordered_structure]) 68 | 69 | 70 | if __name__ == "__main__": 71 | test_xtal2png_three_channels() 72 | imgs = test_xtal2png() 73 | imgs = test_xtal2png_single() 74 | test_xtal2png_single() 75 | test_structures_to_arrays_single() 76 | test_disordered_fail() 77 | -------------------------------------------------------------------------------- /tests/methods_test.py: -------------------------------------------------------------------------------- 1 | """Test fitting, scaling, and any other methods (if applicable).""" 2 | 3 | 4 | from numpy.testing import assert_allclose, assert_array_equal, assert_equal 5 | 6 | from xtal2png.core import XtalConverter 7 | from xtal2png.utils.data import ( 8 | RGB_TOL, 9 | dummy_structures, 10 | element_wise_scaler, 11 | element_wise_unscaler, 12 | example_structures, 13 | rgb_scaler, 14 | rgb_unscaler, 15 | ) 16 | 17 | 18 | def test_fit(): 19 | xc = XtalConverter(relax_on_decode=False) 20 | xc.fit(example_structures + dummy_structures) 21 | assert_array_equal((5, 82), xc._atom_range) 22 | assert_allclose((3.84, 12.718448099999998), xc.a_range) 23 | assert_allclose((3.395504, 11.292530369999998), xc.b_range) 24 | assert_allclose((3.84, 10.6047314973), xc.c_range) 25 | assert_array_equal((0.0, 180.0), xc.angles_range) 26 | assert_allclose((12, 227), xc.space_group_range) 27 | assert_allclose((2, 44), xc.num_sites_range) 28 | assert_allclose((1.383037596160554, 7.8291318247510695), xc.distance_range) 29 | assert_equal(44, xc.num_sites) 30 | 31 | 32 | def test_element_wise_scaler_unscaler(): 33 | check_input = [[1, 2], [3, 4]] 34 | feature_range = [1, 4] 35 | data_range = [0, 8] 36 | check_output = [[1.375, 1.75], [2.125, 2.5]] 37 | scaled = element_wise_scaler( 38 | check_input, feature_range=feature_range, data_range=data_range 39 | ) 40 | unscaled = element_wise_unscaler( 41 | check_output, feature_range=feature_range, data_range=data_range 42 | ) 43 | assert_allclose(check_input, unscaled) 44 | assert_allclose(check_output, scaled) 45 | 46 | 47 | def test_rgb_scaler_unscaler(): 48 | check_input = [[1, 2], [3, 4]] 49 | check_unscaled = [[1.00392157, 2.00784314], [3.01176471, 4.01568627]] 50 | data_range = [0, 8] 51 | check_output = [[32, 64], [96, 128]] 52 | scaled = rgb_scaler(check_input, data_range=data_range) 53 | unscaled = rgb_unscaler(check_output, data_range=data_range) 54 | # NOTE: rtol = 1/255 seems to be an exact tolerance, maybe loosen slightly 55 | assert_allclose( 56 | check_input, 57 | unscaled, 58 | rtol=RGB_TOL, 59 | err_msg=f"rgb_unscaler values not within {RGB_TOL} of original", 60 | ) 61 | assert_allclose(check_unscaled, unscaled) 62 | assert_allclose(check_output, scaled) 63 | -------------------------------------------------------------------------------- /tests/test_files/disordered_structure.cif: -------------------------------------------------------------------------------- 1 | #====================================================================== 2 | 3 | # CRYSTAL DATA 4 | 5 | #---------------------------------------------------------------------- 6 | 7 | data_VESTA_phase_1 8 | 9 | 10 | _chemical_name_common '' 11 | _cell_length_a 21.1337(11) 12 | _cell_length_b 26.6440(13) 13 | _cell_length_c 4.01415(18) 14 | _cell_angle_alpha 90 15 | _cell_angle_beta 90 16 | _cell_angle_gamma 90 17 | _space_group_name_H-M_alt 'P b a 2' 18 | _space_group_IT_number 32 19 | 20 | loop_ 21 | _space_group_symop_operation_xyz 22 | 'x, y, z' 23 | '-x, -y, z' 24 | 'x+1/2, -y+1/2, z' 25 | '-x+1/2, y+1/2, z' 26 | 27 | loop_ 28 | _atom_site_label 29 | _atom_site_occupancy 30 | _atom_site_fract_x 31 | _atom_site_fract_y 32 | _atom_site_fract_z 33 | _atom_site_adp_type 34 | _atom_site_U_iso_or_equiv 35 | _atom_site_type_symbol 36 | Mo1 0.7400 0.000000 0.000000 0.500000 Uiso 0.029400 Mo 37 | V1 0.2600 0.000000 0.000000 0.500000 Uiso 0.029400 V 38 | V2 0.6200 0.000000 0.500000 0.644(13) Uiso 0.029400 V 39 | V3 0.4200 0.1151(9) 0.2298(7) 0.474(9) Uiso 0.029400 V 40 | Mo4 1.0 0.1769(7) 0.4768(5) 0.548(9) Uiso 0.029400 Mo 41 | Mo5 1.0 0.2107(7) 0.3422(5) 0.669(9) Uiso 0.029400 Mo 42 | Mo6 1.0 0.2773(7) 0.2095(5) 0.641(9) Uiso 0.029400 Mo 43 | Mo7 0.6800 0.3809(8) 0.1015(7) 0.508(8) Uiso 0.029400 Mo 44 | V7 0.3200 0.3809(8) 0.1015(7) 0.508(8) Uiso 0.029400 V 45 | Mo8 1.0 0.4608(7) 0.2258(7) 0.654(9) Uiso 0.029400 Mo 46 | Nb1 1.0 0.3601(7) 0.3194(5) 0.510(8) Uiso 0.029400 Nb 47 | Mo9 1.0 -0.0018(8) 0.1341(5) 0.672(8) Uiso 0.029400 Mo 48 | Mo10 1.0 0.3450(7) 0.4375(5) 0.658(8) Uiso 0.029400 Mo 49 | Te1 0.7400 0.5485(8) 0.1030(5) 0.565(10) Uiso 0.032000 Te 50 | Te2 0.2000 0.672(4) 0.416(3) 0.416(17) Uiso 0.032000 Te 51 | O1 1.0 0.000000 0.000000 0.060(12) Uiso 0.000700 O 52 | O2 1.0 0.000000 0.500000 0.069(12) Uiso 0.000700 O 53 | O3 1.0 0.1187(8) 0.2284(7) 0.074(10) Uiso 0.000700 O 54 | O4 1.0 0.1812(7) 0.4772(7) 0.092(10) Uiso 0.000700 O 55 | O5 1.0 0.2155(7) 0.3397(7) 0.077(11) Uiso 0.000700 O 56 | O6 1.0 0.2824(9) 0.2175(7) 0.131(9) Uiso 0.000700 O 57 | O7 1.0 0.3893(9) 0.1048(7) 0.092(10) Uiso 0.000700 O 58 | O8 1.0 0.4507(8) 0.2283(8) 0.065(10) Uiso 0.000700 O 59 | O9 1.0 0.3504(8) 0.3184(7) 0.055(10) Uiso 0.000700 O 60 | O10 1.0 0.0073(9) 0.1416(7) 0.098(10) Uiso 0.000700 O 61 | O11 1.0 0.3452(9) 0.4395(7) 0.109(10) Uiso 0.000700 O 62 | O12 0.7400 0.5413(10) 0.1186(9) 0.034(10) Uiso 0.000700 O 63 | O13 1.0 0.5130(8) 0.4276(7) 0.556(10) Uiso 0.000700 O 64 | O14 1.0 0.5732(8) 0.3339(7) 0.591(10) Uiso 0.000700 O 65 | O15 1.0 0.0387(8) 0.2687(7) 0.625(9) Uiso 0.000700 O 66 | O16 1.0 0.5793(9) 0.0314(7) 0.573(10) Uiso 0.000700 O 67 | O17 1.0 0.6962(8) 0.2970(7) 0.592(11) Uiso 0.000700 O 68 | O18 1.0 0.7792(9) 0.2122(7) 0.576(11) Uiso 0.000700 O 69 | O19 1.0 0.6655(9) 0.0956(7) 0.554(11) Uiso 0.000700 O 70 | O20 1.0 0.9601(9) 0.4290(7) 0.577(11) Uiso 0.000700 O 71 | O21 1.0 0.8116(8) 0.3561(7) 0.535(10) Uiso 0.000700 O 72 | O22 1.0 0.7965(9) 0.1250(7) 0.579(11) Uiso 0.000700 O 73 | O23 1.0 0.7655(8) 0.0254(7) 0.585(11) Uiso 0.000700 O 74 | O24 1.0 0.8664(8) 0.2553(7) 0.579(11) Uiso 0.000700 O 75 | O25 1.0 0.9045(8) 0.1180(7) 0.581(10) Uiso 0.000700 O 76 | O26 1.0 0.9066(9) 0.0132(7) 0.510(9) Uiso 0.000700 O 77 | O27 1.0 0.8372(8) 0.4536(7) 0.649(8) Uiso 0.000700 O 78 | O28 1.0 0.9401(8) 0.3419(7) 0.584(11) Uiso 0.000700 O 79 | O29 1.0 0.9517(8) 0.1995(7) 0.563(10) Uiso 0.000700 O 80 | O30 1.0 0.1532(9) 0.3040(7) 0.605(10) Uiso 0.000700 O 81 | O31 0.2000 0.682(5) 0.433(4) -0.05(3) Uiso 0.000700 O 82 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox configuration file 2 | # Read more under https://tox.wiki/ 3 | # THIS SCRIPT IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! 4 | 5 | [tox] 6 | minversion = 3.24 7 | envlist = default 8 | isolated_build = True 9 | 10 | 11 | [testenv] 12 | description = Invoke pytest to run automated tests 13 | setenv = 14 | TOXINIDIR = {toxinidir} 15 | passenv = 16 | HOME 17 | extras = 18 | testing 19 | commands = 20 | pytest {posargs} 21 | 22 | 23 | # # To run `tox -e lint` you need to make sure you have a 24 | # # `.pre-commit-config.yaml` file. See https://pre-commit.com 25 | # [testenv:lint] 26 | # description = Perform static analysis and style checks 27 | # skip_install = True 28 | # deps = pre-commit 29 | # passenv = 30 | # HOMEPATH 31 | # PROGRAMDATA 32 | # commands = 33 | # pre-commit run --all-files {posargs:--show-diff-on-failure} 34 | 35 | 36 | [testenv:{build,clean}] 37 | description = 38 | build: Build the package in isolation according to PEP517, see https://github.com/pypa/build 39 | clean: Remove old distribution files and temporary build artifacts (./build and ./dist) 40 | # https://setuptools.pypa.io/en/stable/build_meta.html#how-to-use-it 41 | skip_install = True 42 | changedir = {toxinidir} 43 | deps = 44 | build: build[virtualenv] 45 | commands = 46 | clean: python -c 'import shutil; [shutil.rmtree(p, True) for p in ("build", "dist", "docs/_build")]' 47 | clean: python -c 'import pathlib, shutil; [shutil.rmtree(p, True) for p in pathlib.Path("src").glob("*.egg-info")]' 48 | build: python -m build {posargs} 49 | 50 | 51 | [testenv:{docs,doctests,linkcheck}] 52 | description = 53 | docs: Invoke sphinx-build to build the docs 54 | doctests: Invoke sphinx-build to run doctests 55 | linkcheck: Check for broken links in the documentation 56 | setenv = 57 | DOCSDIR = {toxinidir}/docs 58 | BUILDDIR = {toxinidir}/docs/_build 59 | docs: BUILD = html 60 | doctests: BUILD = doctest 61 | linkcheck: BUILD = linkcheck 62 | deps = 63 | -r {toxinidir}/docs/requirements.txt 64 | # ^ requirements.txt shared with Read The Docs 65 | commands = 66 | sphinx-build --color -b {env:BUILD} -d "{env:BUILDDIR}/doctrees" "{env:DOCSDIR}" "{env:BUILDDIR}/{env:BUILD}" {posargs} 67 | doctest: python -m doctest README.md 68 | 69 | 70 | [testenv:publish] 71 | description = 72 | Publish the package you have been developing to a package index server. 73 | By default, it uses testpypi. If you really want to publish your package 74 | to be publicly accessible in PyPI, use the `-- --repository pypi` option. 75 | skip_install = True 76 | changedir = {toxinidir} 77 | passenv = 78 | # See: https://twine.readthedocs.io/en/latest/ 79 | TWINE_USERNAME 80 | TWINE_PASSWORD 81 | TWINE_REPOSITORY 82 | deps = twine 83 | commands = 84 | python -m twine check dist/* 85 | python -m twine upload {posargs:--repository {env:TWINE_REPOSITORY:testpypi}} dist/* 86 | --------------------------------------------------------------------------------