├── .codacy.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── ADMIN.rst ├── CHANGES.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── README.md ├── docs ├── Makefile ├── logo_mdgo.svg ├── make.bat ├── mdgo-white.svg └── source │ ├── _static │ ├── logo_mdgo.png │ └── mdgo-white.png │ ├── aqueous.rst │ ├── change_log.rst │ ├── conf.py │ ├── index.rst │ ├── introduction.rst │ ├── latest_changes.rst │ ├── mdgo.conductivity.rst │ ├── mdgo.coordination.rst │ ├── mdgo.core.rst │ ├── mdgo.forcefield.rst │ ├── mdgo.mdgopackmol.rst │ ├── mdgo.msd.rst │ ├── mdgo.residence_time.rst │ ├── mdgo.rst │ ├── mdgo.util.rst │ ├── mdgo.volume.rst │ ├── modules.rst │ └── rtd_requirements.txt ├── mdgo ├── __init__.py ├── conductivity.py ├── coordination.py ├── core │ ├── __init__.py │ ├── analysis.py │ └── run.py ├── forcefield │ ├── __init__.py │ ├── aqueous.py │ ├── charge.py │ ├── crawler.py │ ├── data │ │ ├── ion_lj_params.json │ │ └── water │ │ │ ├── water_opc.lmp │ │ │ ├── water_opc3.lmp │ │ │ ├── water_spc.lmp │ │ │ ├── water_spce.lmp │ │ │ ├── water_tip3p_ew.lmp │ │ │ ├── water_tip3p_fb.lmp │ │ │ ├── water_tip4p_2005.lmp │ │ │ ├── water_tip4p_ew.lmp │ │ │ └── water_tip4p_fb.lmp │ ├── maestro.py │ └── pubchem.py ├── msd.py ├── residence_time.py ├── templates │ ├── mae_cmd_assignbond.txt │ └── mae_cmd_noassignbond.txt └── util │ ├── __init__.py │ ├── coord.py │ ├── dict_utils.py │ ├── num.py │ ├── packmol.py │ ├── reformat.py │ └── volume.py ├── pylintrc ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── tasks.py └── tests ├── __init__.py ├── packmol ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md └── packmol ├── test_conductivity.py ├── test_coordination.py ├── test_core.py ├── test_files ├── C.lmp ├── CCOC(=O)OC.lmp ├── DEC.xyz ├── EC.xyz ├── EMC.gro ├── EMC.itp ├── EMC.lmp ├── EMC.lmp.xyz ├── EMC.pdb ├── EMC.xyz ├── LiPF6.xyz ├── LiTFSi.xyz ├── PF6.xyz ├── TFSI.xyz ├── gen2_light │ ├── gen2_mdgo.data │ └── gen2_mdgo_unwrapped_nvt_main.dcd └── subdir with spaces │ └── EMC.xyz ├── test_forcefield.py ├── test_mdgopackmol.py ├── test_msd.py ├── test_residence_time.py ├── test_util.py └── test_volume.py /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - tests/** 4 | - docs*/** -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | Include a summary of major changes in bullet points: 4 | 5 | * Feature 1 6 | * Feature 2 7 | * Fix 1 8 | * Fix 2 9 | 10 | ## Additional dependencies introduced (if any) 11 | 12 | * List all new dependencies needed and justify why. While adding dependencies that bring 13 | significantly useful functionality is perfectly fine, adding ones that 14 | add trivial functionality, e.g., to use one single easily implementable 15 | function, is frowned upon. Provide a justification why that dependency is needed. 16 | Especially frowned upon are circular dependencies, e.g., depending on derivative 17 | modules of pymatgen such as custodian or Fireworks. 18 | 19 | ## TODO (if any) 20 | 21 | If this is a work-in-progress, write something about what else needs 22 | to be done 23 | 24 | * Feature 1 supports A, but not B. 25 | 26 | ## Checklist 27 | 28 | Work-in-progress pull requests are encouraged. Please put [WIP] 29 | in the pull request title. 30 | 31 | Before a pull request can be merged, the following items must be checked: 32 | 33 | - [ ] Code is in the [standard Python style](https://www.python.org/dev/peps/pep-0008/). The easiest way to handle this 34 | is to run the following in the **correct sequence** on your local machine. Start with running 35 | [black](https://black.readthedocs.io/en/stable/index.html) on your new code. This will automatically reformat 36 | your code to PEP8 conventions and removes most issues. Then run 37 | [pycodestyle](https://pycodestyle.readthedocs.io/en/latest/), followed by 38 | [flake8](http://flake8.pycqa.org/en/latest/). 39 | - [ ] Docstrings have been added in the [Google docstring format](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). 40 | Run [pydocstyle](http://www.pydocstyle.org/en/2.1.1/index.html) on your code. 41 | - [ ] Type annotations are **highly** encouraged. Run [mypy](http://mypy-lang.org/) to type check your code. 42 | - [ ] Tests have been added for any new functionality or bug fixes. 43 | - [ ] All linting and tests pass. 44 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | run-linters: 15 | name: Run linters 16 | runs-on: ubuntu-latest 17 | strategy: 18 | max-parallel: 1 19 | matrix: 20 | python-version: ['3.10'] 21 | steps: 22 | - name: Check out Git repository 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | pip install --upgrade ruff mypy 33 | 34 | - name: ruff 35 | run: | 36 | ruff --version 37 | ruff . 38 | ruff format --check . 39 | 40 | - name: mypy 41 | run: | 42 | mypy --version 43 | rm -rf .mypy_cache 44 | mypy ${{ github.event.repository.name }} 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [ created ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | test: 10 | 11 | strategy: 12 | max-parallel: 20 13 | matrix: 14 | os: [ macos-latest ] 15 | python-version: [ 3.8, 3.9 ] 16 | # This distribution of tests is designed to ensure an approximately even time to finish for parallel jobs. 17 | pkg: 18 | - tests 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - uses: actions/cache@v2 29 | if: startsWith(runner.os, 'macOS') 30 | with: 31 | path: ~/Library/Caches/pip 32 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 33 | restore-keys: | 34 | ${{ runner.os }}-pip- 35 | - uses: actions/cache@v2 36 | if: startsWith(runner.os, 'Windows') 37 | with: 38 | path: ~\AppData\Local\pip\Cache 39 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 40 | restore-keys: | 41 | ${{ runner.os }}-pip- 42 | - name: Install dependencies 43 | run: | 44 | pip install --upgrade pip wheel 45 | pip install -r requirements.txt 46 | pip install -r requirements-dev.txt 47 | pip install -e . 48 | - name: Prepare Packmol 49 | run: | 50 | echo $HOME 51 | echo "$HOME/work/mdgo/mdgo/tests/packmol" >> $GITHUB_PATH 52 | - name: Prepare Selenium 53 | # https://github.com/marketplace/actions/setup-chromedriver 54 | uses: nanasess/setup-chromedriver@master 55 | - name: Start XVFB 56 | run: | 57 | export DISPLAY=:99 58 | chromedriver --url-base=/wd/hub & 59 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional 60 | - name: pytest ${{ matrix.pkg }} 61 | run: | 62 | pytest ${{ matrix.pkg }} 63 | 64 | release: 65 | needs: test 66 | strategy: 67 | max-parallel: 2 68 | matrix: 69 | os: [ macos-latest ] 70 | python-version: [ 3.8 ] 71 | runs-on: ${{ matrix.os }} 72 | 73 | steps: 74 | - uses: actions/checkout@v2 75 | - name: Set up Python ${{ matrix.python-version }} 76 | uses: actions/setup-python@v2 77 | with: 78 | python-version: ${{ matrix.python-version }} 79 | - uses: actions/cache@v2 80 | if: startsWith(runner.os, 'macOS') 81 | with: 82 | path: ~/Library/Caches/pip 83 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 84 | restore-keys: | 85 | ${{ runner.os }}-pip- 86 | - name: Install dependencies 87 | run: | 88 | pip install --upgrade pip wheel 89 | pip install -r requirements.txt 90 | pip install -e . 91 | - name: Release 92 | env: 93 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 94 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 95 | run: | 96 | pip install setuptools wheel twine 97 | python setup.py sdist bdist_wheel 98 | twine upload dist/*.whl 99 | twine upload --skip-existing dist/*.tar.gz -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | test: 15 | 16 | strategy: 17 | max-parallel: 20 18 | matrix: 19 | os: [ macos-latest ] 20 | python-version: [ '3.9', '3.10' ] 21 | # This distribution of tests is designed to ensure an approximately even time to finish for parallel jobs. 22 | pkg: 23 | - tests --ignore=tests/test_mdgopackmol.py 24 | 25 | runs-on: ${{ matrix.os }} 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | - uses: actions/cache@v4 34 | if: startsWith(runner.os, 'macOS') 35 | with: 36 | path: ~/Library/Caches/pip 37 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pip- 40 | - uses: actions/cache@v4 41 | if: startsWith(runner.os, 'Windows') 42 | with: 43 | path: ~\AppData\Local\pip\Cache 44 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 45 | restore-keys: | 46 | ${{ runner.os }}-pip- 47 | - name: Install dependencies 48 | run: | 49 | pip install --upgrade pip wheel 50 | pip install -r requirements.txt 51 | pip install -r requirements-dev.txt 52 | pip install -e . 53 | - name: Prepare Packmol 54 | run: | 55 | echo $HOME 56 | echo "$HOME/work/mdgo/mdgo/tests/packmol" >> $GITHUB_PATH 57 | - name: Prepare Selenium 58 | # https://github.com/marketplace/actions/setup-chromedriver 59 | uses: nanasess/setup-chromedriver@master 60 | - name: Start XVFB 61 | run: | 62 | export DISPLAY=:99 63 | chromedriver --url-base=/wd/hub & 64 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional 65 | - name: pytest ${{ matrix.pkg }} 66 | run: | 67 | pytest ${{ matrix.pkg }} 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # drafted examples 10 | mdgo_example/ 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | *.ipynb 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # pytype static type analyzer 139 | .pytype/ 140 | 141 | # Cython debug symbols 142 | cython_debug/ 143 | 144 | #IDE metadata 145 | **.idea/** 146 | 147 | # mac os 148 | .DS_Store 149 | .vscode/settings.json 150 | -------------------------------------------------------------------------------- /ADMIN.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This documentation provides a guide for mdgo administrators. The following 5 | assumes you are using miniconda or Anaconda. 6 | 7 | Releases 8 | ======== 9 | 10 | The general procedure to releasing mdgo comprises the following steps: 11 | 12 | 1. Wait for all unittests to pass on CircleCI. 13 | 2. Update and edit change log. 14 | 3. Release PyPI versions + doc. 15 | 4. Release conda versions. 16 | 17 | Initial setup 18 | ------------- 19 | 20 | Install some conda tools first:: 21 | 22 | conda install --yes conda-build anaconda-client 23 | conda config --add channels matsci 24 | 25 | Mdgo uses `invoke `_ to automate releases. You will 26 | also need sphinx. Install these using:: 27 | 28 | pip install --upgrade invoke sphinx 29 | 30 | For 2018, we will release both py27 and py37 versions of mdgo. Create 31 | environments for py37 using conda:: 32 | 33 | conda create --yes -n py37 python=3.7 34 | 35 | For each env, install required packages followed by dev install for 36 | mdgo:: 37 | 38 | conda activate py37 39 | pip install -r requirements.txt 40 | pip install -r requirements-optional.txt 41 | python setup.py develop 42 | 43 | Add your PyPI username and password and GITHUB_RELEASE_TOKEN into your 44 | environment:: 45 | 46 | export TWINE_USERNAME=PYPIUSERNAME 47 | export TWINE_PASSWORD=PYPIPASSWORD 48 | export GITHUB_RELEASES_TOKEN=TOKEN_YOU_GET_FROM_GITHUB 49 | 50 | You may want to add these to your .bash_profile to avoid having to type these 51 | each time. 52 | 53 | Machine-specific issues 54 | ~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | The above instructions are general, but there are some known issues that are 57 | machine-specific: 58 | 59 | * It can be useful to `pip install --upgrade pip twine setuptools` (this may 60 | be necessary if there are authentication errors when connecting to PyPI). 61 | 62 | Doing the release 63 | ----------------- 64 | 65 | Ensure appropriate environment variables are set including `DISCOURSE_API_USERNAME`, 66 | `DISCOURSE_API_KEY` and `GITHUB_RELEASES_TOKEN`. 67 | 68 | First update the change log. The autogenerated change log is simply a list of 69 | commit messages since the last version. Make sure to edit the log for brevity 70 | and to attribute significant features to appropriate developers:: 71 | 72 | conda activate py37 73 | invoke update-changelog 74 | 75 | Then, do the release with the following sequence of commands (you can put them 76 | in a bash script in your PATH somewhere):: 77 | 78 | conda activate py37 79 | invoke release --nodoc 80 | invoke update-doc 81 | conda deactivate 82 | python setup.py develop 83 | 84 | Double check that the releases are properly done on Pypi. If you are releasing 85 | on a Mac, you should see a mdgo.version.tar.gz and two wheels (Py37 and 86 | P). There will be a py37 wheel for Windows that is generated by Appveyor. 87 | 88 | Materials.sh 89 | ------------ 90 | 91 | Fork and clone the `materials.sh `_. 92 | This repo contains the conda skeletons to build the conda versions for various 93 | matsci codes on the Anaconda `matsci channel `_. 94 | 95 | The first time this is run, you may need to `pip install beautifulsoup4`. 96 | 97 | If you doing this for the first time, make sure conda-build and anaconda-client 98 | are installed:: 99 | 100 | conda install --yes conda-build anaconda-client 101 | 102 | Update the mdgo meta.yaml:: 103 | 104 | invoke update-pypi mdgo 105 | 106 | Build the mac versions manually:: 107 | 108 | invoke build-conda mdgo 109 | 110 | Commit and push to repo, which will build the Linux and Windows versions. 111 | 112 | Check that the `matsci channel `_ versions are 113 | properly updated. -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ========== 3 | 4 | v0.3.1 5 | ------ 6 | 7 | v0.3.0 8 | ------ 9 | 10 | v0.2.4 11 | ------ 12 | 13 | 14 | v0.2.3 15 | ------ 16 | 17 | 18 | v0.2.2 19 | ------ 20 | 21 | * Packmol: overhaul and add tests by @rkingsbury in https://github.com/HT-MD/mdgo/pull/17 22 | * Overhaul forcefield parameters for ions by @rkingsbury in https://github.com/HT-MD/mdgo/pull/15 23 | * Add a Codacy badge to README.md by @codacy-badger in https://github.com/HT-MD/mdgo/pull/19 24 | 25 | * @codacy-badger made their first contribution in https://github.com/HT-MD/mdgo/pull/19 26 | 27 | **Full Changelog**: https://github.com/HT-MD/mdgo/compare/v0.2.1...v0.2.2 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at conduct@materialsproject.org. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident to the extent possible by law and institutional policy. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at https://www.contributor-covenant.org/version/1/3/0/code-of-conduct.html 47 | 48 | [homepage]: https://www.contributor-covenant.org 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Collaborative Github Workflow 2 | ============================= 3 | 4 | For developers interested in expanding mdgo for their own purposes, we 5 | recommend forking mdgo directly from the 6 | `mdgo GitHub repo`_. Here's a typical workflow (adapted from 7 | http://www.eqqon.com/index.php/Collaborative_Github_Workflow): 8 | 9 | .. note:: 10 | 11 | Ignore the Github fork queue. Let the maintainer of mdgo worry about 12 | the fork queue. 13 | 14 | 1. Create a free GitHub account (if you don't already have one) and perform the 15 | necessary setup (e.g., install SSH keys etc.). 16 | 2. Fork the mdgo GitHub repo, i.e., go to the main 17 | `mdgo GitHub repo`_ and click fork to create a copy of the mdgo code 18 | base on your own Github account. 19 | 3. Install git on your local machine (if you don't already have it). 20 | 4. Clone *your forked repo* to your local machine. You will work mostly with 21 | your local repo and only publish changes when they are ready to be merged: 22 | 23 | :: 24 | 25 | git clone git@github.com:YOURNAME/mdgo.git 26 | 27 | Note that the entire Github repo is fairly large because of the presence of 28 | test files, but these are absolutely necessary for rigorous testing of the 29 | code. 30 | 5. It is highly recommended you install all the optional dependencies as well. 31 | 6. Code (see `Coding Guidelines`_). Commit early and commit often. Keep your 32 | code up to date. You need to add the main repository to the list of your 33 | remotes. Let's name the upstream repo as mdmain. 34 | 35 | :: 36 | 37 | git remote add mdmain git://github.com/HT-MD/mdgo.git 38 | 39 | Make sure your repository is clean (no uncommitted changes) and is currently 40 | on the main branch. If not, commit or stash any changes and switch to the 41 | main. 42 | 43 | :: 44 | 45 | git checkout main 46 | 47 | Then you can pull all the new commits from the main line 48 | 49 | :: 50 | 51 | git pull mdmain main 52 | 53 | Remember, pull is a combination of the commands fetch and merge, so there may 54 | be merge conflicts to be manually resolved. 55 | 56 | 7. Publish your contributions. Assuming that you now have a couple of commits 57 | that you would like to contribute to the main repository. Please follow the 58 | following steps: 59 | 60 | a. If your change is based on a relatively old state of the main repository, 61 | then you should probably bring your repository up-to-date first to see if 62 | the change is not creating any merge conflicts. 63 | b. Check that everything compiles cleanly and passes all tests. 64 | The mdgo repo comes with a complete set of tests for all modules. If 65 | you have written new modules or methods, you must write tests for the new 66 | code as well (see `Coding Guidelines`_). Install and run pytest in your 67 | local repo directory and fix all errors before continuing further. 68 | c. If everything is ok, publish the commits to your github repository. 69 | 70 | :: 71 | 72 | git push origin main 73 | 74 | 8. Now that your commit is published, it doesn't mean that it has already been 75 | merged into the main repository. You should issue a merge request to 76 | mdgo maintainers. They will pull your commits and run their own tests 77 | before releasing. 78 | 79 | "Work-in-progress" pull requests are encouraged, especially if this is your 80 | first time contributing to mdgo, and the maintainers will be happy to 81 | help or provide code review as necessary. Put "[WIP]" in the title of your 82 | pull request to indicate it's not ready to be merged. 83 | 84 | Coding Guidelines 85 | ================= 86 | 87 | Given that mdgo is intended to be long-term code base, we adopt very strict 88 | quality control and coding guidelines for all contributions to mdgo. The 89 | following must be satisfied for your contributions to be accepted into mdgo. 90 | 91 | 1. **Unit tests** are required for all new modules and methods. The only way to 92 | minimize code regression is to ensure that all code are well-tested. If the 93 | maintainer cannot test your code, the contribution will be rejected. 94 | 2. **Python PEP 8** `code style `_. 95 | We allow a few exceptions when they are well-justified (e.g., Element's 96 | atomic number is given a variable name of capital Z, in line with accepted 97 | scientific convention), but generally, PEP 8 must be observed. Code style 98 | will be automatically checked for all PRs and must pass before any PR is merged. 99 | To aid you, you can copy the example pre-commit hook into your .git/hooks 100 | directly. This will automatically run pycodestyle and other linting services 101 | prior to any commits. At the very least, copy pre-commit to .git/hooks/pre-push. 102 | 3. **Python 3**. We only support Python 3.7+. 103 | 4. **Documentation** required for all modules, classes and methods. In 104 | particular, the method doc strings should make clear the arguments expected 105 | and the return values. For complex algorithms (e.g., an Ewald summation), a 106 | summary of the algorithm should be provided, and preferably with a link to a 107 | publication outlining the method in detail. 108 | 5. **IDE**. We highly recommend the use of Pycharm. You should also set up 109 | pycodestyle and turn those on within the IDE setup. This will warn of any 110 | issues with coding styles. Many code style errors can be done by simply 111 | selecting the entire code and using the Code->Reformat Code within Pycharm. 112 | 113 | For the above, if in doubt, please refer to the core classes in mdgo for 114 | examples of what is expected. 115 | 116 | 117 | .. _`mdgo GitHub repo`: https://github.com/HT-MD/mdgo 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![MDGO](https://github.com/HT-MD/mdgo/blob/main/docs/logo_mdgo.svg) 2 | 3 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/mdgo?style=plastic) 4 | 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a97ce4eb53404e58b89bf41e0c3a3ee6)](https://app.codacy.com/gh/HT-MD/mdgo?utm_source=github.com&utm_medium=referral&utm_content=HT-MD/mdgo&utm_campaign=Badge_Grade_Settings) 6 | 7 | [![docs](https://readthedocs.org/projects/mdgo/badge/?version=latest&style=plastic)](https://mdgo.readthedocs.io/) ![Linting](https://github.com/HT-MD/mdgo/actions/workflows/lint.yml/badge.svg) ![Test](https://github.com/HT-MD/mdgo/actions/workflows/test.yml/badge.svg) 8 | 9 | An all-in-one code base for the classical molecualr dynamics (MD) simulation setup and results analysis. 10 | 11 | # 1. Installation 12 | 13 | ## 1.1 Installing from PyPI 14 | 15 | To install the latest release version of mdgo: 16 | 17 | `pip install mdgo` 18 | 19 | ## 1.2 Installing from source code 20 | 21 | Mdgo requires numpy, pandas, matplotlib, scipy, tqdm, statsmodels, pymatgen>=2022.0.9, pubchempy, selenium, MDAnalysis (>=2.0.0) and their dependencies. 22 | 23 | ### Getting Source Code 24 | 25 | If not available already, use the following steps. 26 | 27 | 1. Install [git](http://git-scm.com), if not already packaged with your system. 28 | 29 | 2. Download the mdgo source code using the command: 30 | 31 | `https://github.com/HouGroup/mdgo.git` 32 | 33 | ### Installation 34 | 35 | 1. Navigate to mdgo root directory: 36 | 37 | `cd mdgo` 38 | 39 | 2. Install the code, using the command: 40 | 41 | `pip install .` 42 | 43 | 3. The latest version MDAnalysis (>=2.0.0) is recommended, you may download the source code of the latest MDAnalysis from github and install using pip to replace an existing version. 44 | 45 | ### Installation in development mode 46 | 47 | 1. Navigate to mdgo root directory: 48 | 49 | `cd mdgo` 50 | 51 | 2. Install the code in "editable" mode, using the command:: 52 | 53 | `pip install -e .` 54 | 55 | 3. The latest version MDAnalysis (>=2.0.0) is recommended, you may download the source code of the latest MDAnalysis from github and install using pip to replace an existing version. 56 | 57 | # 2. Features 58 | 59 | 1. Retrieving compound structure and information from PubChem 60 | - Supported searching text: 61 | - cid, name, smiles, inchi, inchikey or formula 62 | - Supported output format: 63 | - smiles code, PDB, XML, ASNT/B, JSON, SDF, CSV, PNG, TXT 64 | 2. Retrieving water and ion models 65 | - Supported water models: 66 | - SCP, SPC/E, TIP3P_EW, TIP4P_EW, TIP4P_2005 67 | - Supported ion models: 68 | - alkali, ammonium, and halide monovalent ions by Jensen and Jorgensen 69 | - alkali and halide monovalent ions by Joung and Cheatham 70 | - alkali and alkaline-earth metal cations by Åqvist 71 | 3. Write OPLS-AA forcefield file from LigParGen 72 | - Supported input format: 73 | - mol/pdb 74 | - SMILES code 75 | - Supported output format: 76 | - LAMMPS(.lmp) 77 | - GROMACS(.gro, .itp) 78 | 4. Write OPLS-AA forcefield file from Maestro 79 | - Supported input format: 80 | - Any [format that Maestro support] 81 | - Supported output format: 82 | - LAMMPS(.lmp) 83 | - Others pending\... 84 | 5. Packmol wrapper 85 | - Supported input format: 86 | - xyz 87 | - Others pending\... 88 | 6. Basic simulation properties 89 | - Initial box dimension 90 | - Equilibrium box dimension 91 | - Salt concentration 92 | 7. Conductivity analysis 93 | - Green--Kubo conductivity 94 | - Nernst--Einstein conductivity 95 | 8. Coordination analysis 96 | - The distribution of the coordination number of single species 97 | - The integral of radial distribution function (The average 98 | coordination numbers of multiple species) 99 | - Solvation structure write out 100 | - Population of solvent separated ion pairs (SSIP), contact ion 101 | pairs (CIP), and aggregates (AGG) 102 | - The trajectory (distance) of cation and coordinating species as 103 | a function of time 104 | - The hopping frequency of cation between binding sites 105 | - The distribution heat map of cation around binding sites 106 | - The averaged nearest neighbor distance of a species 107 | 9. Diffusion analysis 108 | - The mean square displacement of all species 109 | - The mean square displacement of coordinated species and 110 | uncoordinated species, separately 111 | - Self-diffusion coefficients 112 | 10. Residence time analysis 113 | - The residence time of all species 114 | 115 | # 3. Citation 116 | 117 | When using mdgo in published work, please cite the following paper: 118 | 119 | * Hou, T.; Fong, K. D.; Wang, J.; Persson, K. A. The solvation structure, transport properties and reduction behavior of carbonate-based electrolytes of lithium-ion batteries. *Chem. Sci.* **2021**, *12*, 14740-14751. [[doi](https://doi.org/10.1039/D1SC04265C)] 120 | 121 | [format that Maestro support]: https://www.schrodinger.com/kb/1278 122 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal 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 = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/logo_mdgo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/mdgo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/source/_static/logo_mdgo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/docs/source/_static/logo_mdgo.png -------------------------------------------------------------------------------- /docs/source/_static/mdgo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/docs/source/_static/mdgo-white.png -------------------------------------------------------------------------------- /docs/source/aqueous.rst: -------------------------------------------------------------------------------- 1 | 2 | ================================ 3 | Force Fields for Aqueous Systems 4 | ================================ 5 | 6 | The Aqueous module provides tools for setting up molecular dynamics simulations 7 | involving water and ions. 8 | 9 | Water Models 10 | ============ 11 | 12 | `mdgo` contains parameters for several popular water models. This section lists 13 | a brief description and literature reference to the available models. 14 | 15 | SPC 16 | --- 17 | 18 | TIP3P-EW 19 | -------- 20 | 21 | TIP3P-FB 22 | -------- 23 | 24 | Wang, L., Martinez, T. J., Pande, V.S., Building Force Fields: An Automatic, Systematic, 25 | and Reproducible Approach. J. Phys. Chem. Lett. 2014, 5, 11, 1885–1891. 26 | https://pubs.acs.org/doi/abs/10.1021/jz500737m 27 | 28 | Parameters are given in Supporting Table 1. Note that the epsilon for Oxygen must be converted 29 | from kJ/mol to kcal/mol. 30 | 31 | TIP4P-EW 32 | -------- 33 | 34 | [Vega & de Miguel, J Chem Phys 126:154707 (2007), Vega et al, Faraday Discuss 141:251 (2009)]. 35 | 36 | TIP4P-FB 37 | -------- 38 | 39 | Wang, L., Martinez, T. J., Pande, V.S., Building Force Fields: An Automatic, Systematic, 40 | and Reproducible Approach. J. Phys. Chem. Lett. 2014, 5, 11, 1885–1891. 41 | https://pubs.acs.org/doi/abs/10.1021/jz500737m 42 | 43 | Parameters are given in Supporting Table 1. Note that the epsilon for oxygen must be converted 44 | from kJ/mol to kcal/mol. 45 | 46 | TIP4P-2005 47 | ---------- 48 | 49 | Abascal & Vega, J Chem Phys 123:234505 (2005) 50 | 51 | OPC 52 | ---- 53 | 54 | Izadi, Anandakrishnan, and Onufriev, Building Water Models: A Different Approach. 55 | J. Phys. Chem. Lett. 2014, 5, 21, 3863–3871 https://doi.org/10.1021/jz501780a 56 | 57 | Parameters are given in Table 2. Note that the epsilon for oxygen must be converted 58 | from kJ/mol to kcal/mol. 59 | 60 | OPC3 61 | ---- 62 | 63 | Izadi and Onufriev, Accuracy limit of rigid 3-point water models 64 | J. Chemical Physics 145, 074501, 2016. https://doi.org/10.1063/1.4960175 65 | 66 | Parameters are given in Table II. Note that the epsilon for oxygen must be converted 67 | from kJ/mol to kcal/mol. 68 | 69 | 70 | Ion Parameter Sets 71 | ================== 72 | 73 | ``mdgo`` contains a compilation of several sets of Lennard Jones 74 | parameters for ions in water. All values are reported as :math:`\sigma_i` 75 | and :math:`\epsilon_i` in the equation 76 | 77 | .. math:: 78 | 79 | E = 4 \\epsilon_i \\left[ \\left( \\frac{\sigma_i}{r} \\right)^{12} - \\left( \\frac{\sigma_i}{r} \\right)^{6} \\right] 80 | 81 | Values of :math:`\sigma_i` and :math:`\epsilon_i` are given in Angstrom 82 | and kcal/mol, respectively, corresponding to the ‘real’ units system in 83 | LAMMPS. 84 | 85 | Aqvist (aq) 86 | ----------- 87 | 88 | Aqvist, J. Ion-Water Interaction Potentials Derived from Free Energy 89 | Perturbation Slmulations J. Phys. Chem. 1990, 94, 8021– 8024. 90 | https://pubs.acs.org/doi/10.1021/j100384a009 91 | 92 | Values were parameterized to the SPC water model and are reported in 93 | Table I and II as :math:`A_i` and :math:`B_i` coefficients in the 94 | following form of the Lennard-Jones potential: 95 | 96 | .. math:: 97 | 98 | 99 | E = \left[ \left( \frac{A_i^2}{r} \right)^{12} - \left( \frac{B_i^2}{r} \right)^{6} \right] 100 | 101 | This parameter set is a work in progress! 102 | 103 | Jensen and Jorgensen (jj) 104 | ------------------------- 105 | 106 | Jensen, K. P. and Jorgensen, W. L., Halide, Ammonium, and Alkali Metal 107 | Ion Parameters for Modeling Aqueous Solutions. J. Chem. Theory Comput. 108 | 2006, 2, 6, 1499–1509. https://pubs.acs.org/doi/abs/10.1021/ct600252r 109 | 110 | Values were parameterized to the TIP4P water model using geometric 111 | combining rules and are reported directly as sigma_i and epsilon_i in 112 | Table 2. 113 | 114 | Joung-Cheatham (jc) 115 | ------------------- 116 | 117 | Joung, and Thomas E. Cheatham, Thomas E. III, Determination of Alkali 118 | and Halide Monovalent Ion Parameters for Use in Explicitly Solvated 119 | Biomolecular Simulations. J. Phys. Chem. B 2008, 112, 30, 9020–9041. 120 | https://pubs.acs.org/doi/10.1021/jp8001614 121 | 122 | Values were parameterized for the SPC/E, TIP3P, and TIP4P_EW water 123 | models using Lorentz-Berthelot combining rules (LAMMPS: ‘arithmetic’) 124 | and are reported in Table 5 as :math:`R_{min}`/2 and epsilon_i. R_min/2 125 | values are converted to :math:`\sigma_i` values using 126 | :math:`\sigma_i = R_{min}/2 * 2^(5/6)` 127 | 128 | Li and Merz group (lm) 129 | ---------------------- 130 | 131 | Sengupta et al. Parameterization of Monovalent Ions for the OPC3, OPC, 132 | TIP3P-FB, and TIP4P-FB Water Models. J. Chem. Information Modeling 133 | 61(2), 2021. https://pubs.acs.org/doi/10.1021/acs.jcim.0c01390 134 | 135 | Li et al. Systematic Parametrization of Divalent Metal Ions for the 136 | OPC3, OPC, TIP3P-FB, and TIP4P-FB Water Models. J. Chem. Theory and 137 | Computation 16(7), 2020. 138 | https://pubs.acs.org/doi/10.1021/acs.jctc.0c00194 139 | 140 | Li et al. Parametrization of Trivalent and Tetravalent Metal Ions for 141 | the OPC3, OPC, TIP3P-FB, and TIP4P-FB Water Models. J. Chem. Theory and 142 | Computation 17(4), 2021. 143 | https://pubs.acs.org/doi/10.1021/acs.jctc.0c01320 144 | 145 | Values were parameterized for the OPC, OPC3, TIP3P-FB, and TIP4P-FB 146 | water models using Lorentz-Berthelot combining rules (LAMMPS: 147 | ‘arithmetic’) and are reported in Table 3 as :math:`R_{min}`/2 and 148 | epsilon_i. R_min/2 values are converted to :math:`\sigma_i` values using 149 | :math:`\sigma_i = R_{min}/2 * 2^(5/6)`. This set of values is optimized 150 | for reproducing ion-oxygen distance. An alternate set of values optimized for 151 | hydration free energies is available in the original papers. 152 | 153 | -------------------------------------------------------------------------------- /docs/source/change_log.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Change log 3 | ============ 4 | 5 | v0.2.4 6 | ------ 7 | 8 | 9 | v0.2.3 10 | ------ 11 | 12 | 13 | v0.2.2 14 | ------ 15 | 16 | * Packmol: overhaul and add tests by @rkingsbury in https://github.com/HT-MD/mdgo/pull/17 17 | * Overhaul forcefield parameters for ions by @rkingsbury in https://github.com/HT-MD/mdgo/pull/15 18 | * Add a Codacy badge to README.md by @codacy-badger in https://github.com/HT-MD/mdgo/pull/19 19 | 20 | * @codacy-badger made their first contribution in https://github.com/HT-MD/mdgo/pull/19 21 | 22 | **Full Changelog**: https://github.com/HT-MD/mdgo/compare/v0.2.1...v0.2.2 -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | """Configuration file for the Sphinx documentation builder.""" 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 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | from __future__ import annotations 14 | 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath("../../mdgo")) 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = "mdgo" 23 | copyright = "2021, Tingzheng Hou" 24 | author = "Tingzheng Hou" 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = "0.1.0" 28 | 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | 36 | extensions = [ 37 | "sphinx.ext.autodoc", 38 | "sphinx.ext.intersphinx", 39 | "sphinx.ext.doctest", 40 | "sphinx.ext.autosummary", 41 | "sphinx.ext.mathjax", 42 | "sphinx.ext.viewcode", 43 | "sphinx.ext.napoleon", 44 | "sphinx.ext.todo", 45 | "sphinx_rtd_theme", 46 | "sphinx_autodoc_typehints", 47 | ] 48 | 49 | source_suffix = [".rst"] 50 | autodoc_member_order = "bysource" 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ["_templates"] 54 | 55 | # List of patterns, relative to source directory, that match files and 56 | # directories to ignore when looking for source files. 57 | # This pattern also affects html_static_path and html_extra_path. 58 | exclude_patterns: list[str] = [] 59 | 60 | 61 | # -- Options for HTML output ------------------------------------------------- 62 | 63 | # The theme to use for HTML and HTML Help pages. See the documentation for 64 | # a list of builtin themes. 65 | # 66 | 67 | html_theme = "sphinx_rtd_theme" 68 | 69 | # Add any paths that contain custom static files (such as style sheets) here, 70 | # relative to this directory. They are copied after the builtin static files, 71 | # so a file named "default.css" will overwrite the builtin "default.css". 72 | html_static_path = ["_static"] 73 | html_logo = "_static/mdgo-white.png" 74 | html_theme_options = { 75 | "logo_only": True, 76 | "display_version": False, 77 | } 78 | 79 | autodoc_typehints = "description" 80 | 81 | autodoc_mock_imports = [ 82 | "typing_extensions", 83 | "numpy", 84 | "pandas", 85 | "matplotlib", 86 | "scipy", 87 | "tqdm", 88 | "monty", 89 | "pymatgen", 90 | "statsmodels", 91 | "pubchempy", 92 | "MDAnalysis", 93 | "selenium", 94 | "matplotlib.pyplot", 95 | "MDAnalysis.lib", 96 | "MDAnalysis.lib.distances", 97 | "MDAnalysis.analysis", 98 | "MDAnalysis.analysis.msd", 99 | "MDAnalysis.analysis.distances", 100 | "pymatgen.io", 101 | "pymatgen.io.lammps", 102 | "pymatgen.io.lammps.data", 103 | "statsmodels.tsa", 104 | "statsmodels.tsa.stattools", 105 | "scipy.signal", 106 | "scipy.optimize", 107 | "selenium.common", 108 | "selenium.common.exceptions", 109 | "selenium.webdriver", 110 | "selenium.webdriver.support", 111 | "selenium.webdriver.support.ui", 112 | "selenium.webdriver.common", 113 | "selenium.webdriver.common.by", 114 | ] 115 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | Mdgo documentation 3 | ============================================== 4 | 5 | .. toctree:: 6 | :hidden: 7 | 8 | introduction 9 | modules 10 | change_log 11 | 12 | .. include:: introduction.rst -------------------------------------------------------------------------------- /docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Introduction 3 | ============ 4 | 5 | Welcome to the documentation site for mdgo! Mdgo is an python toolkit for classical molecualr dynamics (MD) simulation setup and results analysis, especially for electrolyte systems. The purpose of making this package is for supporting a high-throughput workflow for screening novel electrolytes for battery use. Currently, the package is under active development. 6 | 7 | .. contents:: 8 | :local: 9 | 10 | Features 11 | ======== 12 | 13 | Please see :any:`modules ` for detailed documentation of the code. 14 | 15 | #. Retriving compound structure and information from PubChem 16 | 17 | * Supported searching text: 18 | 19 | * cid, name, smiles, inchi, inchikey or formula 20 | 21 | * Supported output format: 22 | 23 | * smiles code, PDB, XML, ASNT/B, JSON, SDF, CSV, PNG, TXT 24 | 25 | #. Retrieving water and ion models 26 | 27 | * Supported water models: 28 | 29 | * SCP, SPC/E, TIP3P_EW, TIP4P_EW, TIP4P_2005 30 | 31 | * Supported ion models: 32 | 33 | * alkali, ammonium, and halide monovalent ions by Jensen and Jorgensen 34 | 35 | * alkali and halide monovalent ions by Joung and Cheatham 36 | 37 | * alkali and alkaline-earth metal cations by Åqvist 38 | 39 | #. Write OPLS-AA forcefield file from LigParGen 40 | 41 | * Supported input format: 42 | 43 | * mol/pdb 44 | 45 | * SMILES code 46 | 47 | * Supported output format: 48 | 49 | * LAMMPS(.lmp) 50 | 51 | * GROMACS(.gro, .itp) 52 | 53 | #. Write OPLS-AA forcefield file from Maestro 54 | 55 | * Supported input format: 56 | 57 | * Any `format that Maestro support `_ 58 | 59 | * Supported output format: 60 | 61 | * LAMMPS(.lmp) 62 | 63 | * Others pending... 64 | 65 | #. Packmol wrapper 66 | 67 | * Supported input format: 68 | 69 | * xyz 70 | 71 | * Others pending... 72 | 73 | #. Basic simulation properties 74 | 75 | * Initial box dimension 76 | 77 | * Equilibrium box dimension 78 | 79 | * Salt concentration 80 | 81 | #. Conductivity analysis 82 | 83 | * Green–Kubo conductivity 84 | 85 | * Nernst–Einstein conductivity 86 | 87 | #. Coordination analysis 88 | 89 | * The distribution of the coordination number of single species 90 | 91 | * The integral of radial distribution function (The average coordination numbers of multiple species) 92 | 93 | * Solvation structure write out 94 | 95 | * Population of solvent separated ion pairs (SSIP), contact ion pairs (CIP), and aggregates (AGG) 96 | 97 | * The trajectory (distance) of cation and coordinating species as a function of time 98 | 99 | * The hopping frequency of cation between binding sites 100 | 101 | * The distribution heat map of cation around binding sites 102 | 103 | * The averaged nearest neighbor distance of a species 104 | 105 | #. Diffusion analysis 106 | 107 | * The mean square displacement of all species 108 | 109 | * The mean square displacement of coordinated species and uncoordinated species, separately 110 | 111 | * Self-diffusion coefficients 112 | 113 | #. Residence time analysis 114 | 115 | * The residence time of all species 116 | 117 | 118 | 119 | Installation 120 | ============ 121 | 122 | Installing from PyPI 123 | -------------------- 124 | 125 | To install the latest release version of mdgo:: 126 | 127 | pip install mdgo 128 | 129 | Installing from Source 130 | ---------------------- 131 | 132 | Mdgo requires numpy, pandas, matplotlib, scipy, tqdm, statsmodels, pymatgen>=2022.0.9, pubchempy, selenium, MDAnalysis (version 2.0.0-dev0 prefered) and their dependencies. 133 | 134 | Getting source code 135 | ^^^^^^^^^^^^^^^^^^^ 136 | 137 | If not available already, use the following steps. 138 | 139 | 1. Install `git `_ if not already packaged with your system. 140 | 141 | 2. Download the mdgo source code using the command:: 142 | 143 | git clone https://github.com/htz1992213/mdgo.git 144 | 145 | Installation from source 146 | ^^^^^^^^^^^^^^^^^^^^^^^^ 147 | 1. Navigate to mdgo root directory:: 148 | 149 | cd mdgo 150 | 151 | 2. Install the code, using the command:: 152 | 153 | pip install . 154 | 155 | 3. The latest version MDAnalysis==2.0.0.dev0 is recommended, you may download the source code of the latest MDAnalysis from github and install using pip to replace an existing version. 156 | 157 | Installation in development mode 158 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 159 | 1. Navigate to mdgo root directory:: 160 | 161 | cd mdgo 162 | 163 | 2. Install the code in "editable" mode, using the command:: 164 | 165 | pip install -e . 166 | 167 | 3. The latest version MDAnalysis==2.0.0.dev0 is recommended, you may download the source code of the latest MDAnalysis from github and install using pip to replace an existing version. 168 | 169 | Contributing 170 | ============ 171 | 172 | Reporting bugs 173 | -------------- 174 | 175 | Please report any bugs and issues at mdgo's 176 | `Github Issues page `_. 177 | 178 | Developing new functionality 179 | ---------------------------- 180 | 181 | You may submit new code/bugfixes by sending a pull request to the mdgo's github repository. 182 | 183 | How to cite mdgo 184 | ================ 185 | 186 | pending... 187 | 188 | License 189 | ======= 190 | 191 | Mdgo is released under the MIT License. The terms of the license are as 192 | follows: 193 | 194 | .. literalinclude:: ../../LICENSE.rst 195 | 196 | About the Team 197 | ============== 198 | 199 | Tingzheng Hou started mdgo in 2020 under the supervision of Prof. Kristin Persson at University of California, berkeley. 200 | 201 | Copyright Policy 202 | ================ 203 | 204 | The following banner should be used in any source code file 205 | to indicate the copyright and license terms:: 206 | 207 | # Copyright (c) Tingzheng Hou. 208 | # Distributed under the terms of the MIT License. 209 | 210 | Indices and tables 211 | ================== 212 | 213 | * :ref:`genindex` 214 | * :ref:`modindex` 215 | * :ref:`search` 216 | -------------------------------------------------------------------------------- /docs/source/latest_changes.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/docs/source/latest_changes.rst -------------------------------------------------------------------------------- /docs/source/mdgo.conductivity.rst: -------------------------------------------------------------------------------- 1 | mdgo.conductivity module 2 | ======================== 3 | 4 | .. automodule:: mdgo.conductivity 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.coordination.rst: -------------------------------------------------------------------------------- 1 | mdgo.coordination module 2 | ======================== 3 | 4 | .. automodule:: mdgo.coordination 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.core.rst: -------------------------------------------------------------------------------- 1 | mdgo.core module 2 | ================ 3 | 4 | .. automodule:: mdgo.core 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.forcefield.rst: -------------------------------------------------------------------------------- 1 | mdgo.forcefield module 2 | ====================== 3 | 4 | .. automodule:: mdgo.forcefield 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.mdgopackmol.rst: -------------------------------------------------------------------------------- 1 | mdgo.mdgopackmol module 2 | ======================= 3 | 4 | .. automodule:: mdgo.mdgopackmol 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.msd.rst: -------------------------------------------------------------------------------- 1 | mdgo.msd module 2 | =============== 3 | 4 | .. automodule:: mdgo.msd 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.residence_time.rst: -------------------------------------------------------------------------------- 1 | mdgo.residence\_time module 2 | =========================== 3 | 4 | .. automodule:: mdgo.residence_time 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.rst: -------------------------------------------------------------------------------- 1 | mdgo package 2 | ============ 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | mdgo.conductivity 11 | mdgo.coordination 12 | mdgo.core 13 | mdgo.forcefield 14 | mdgo.mdgopackmol 15 | mdgo.msd 16 | mdgo.residence_time 17 | mdgo.volume 18 | mdgo.util 19 | 20 | Module contents 21 | --------------- 22 | 23 | .. automodule:: mdgo 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | -------------------------------------------------------------------------------- /docs/source/mdgo.util.rst: -------------------------------------------------------------------------------- 1 | mdgo.util module 2 | ================ 3 | 4 | .. automodule:: mdgo.util 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/mdgo.volume.rst: -------------------------------------------------------------------------------- 1 | mdgo.volume module 2 | ================== 3 | 4 | .. automodule:: mdgo.volume 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | .. _my-section: 2 | 3 | ==== 4 | mdgo 5 | ==== 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | mdgo 11 | -------------------------------------------------------------------------------- /docs/source/rtd_requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-autodoc-typehints 2 | -------------------------------------------------------------------------------- /mdgo/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """This package contains core modules and classes molecular dynamics simulation setup and analysis.""" 5 | 6 | from __future__ import annotations 7 | 8 | __author__ = "Mdgo Development Team" 9 | __email__ = "tingzheng_hou@berkeley.edu" 10 | __maintainer__ = "Tingzheng Hou" 11 | __maintainer_email__ = "tingzheng_hou@berkeley.edu" 12 | __version__ = "0.3.1" 13 | -------------------------------------------------------------------------------- /mdgo/conductivity.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """This module implements functions to calculate the ionic conductivity.""" 5 | 6 | from __future__ import annotations 7 | 8 | from typing import TYPE_CHECKING 9 | 10 | import numpy as np 11 | from scipy import stats 12 | from tqdm.auto import tqdm 13 | 14 | from mdgo.msd import msd_fft 15 | 16 | if TYPE_CHECKING: 17 | from MDAnalysis import AtomGroup, Universe 18 | 19 | __author__ = "Kara Fong, Tingzheng Hou" 20 | __version__ = "0.3.0" 21 | __maintainer__ = "Tingzheng Hou" 22 | __email__ = "tingzheng_hou@berkeley.edu" 23 | __date__ = "Jul 19, 2021" 24 | 25 | 26 | def calc_cond_msd( 27 | u: Universe, 28 | anions: AtomGroup, 29 | cations: AtomGroup, 30 | run_start: int, 31 | cation_charge: float = 1, 32 | anion_charge: float = -1, 33 | ) -> np.ndarray: 34 | """Calculates the conductivity "mean square displacement" over time. 35 | 36 | Note: 37 | Coordinates must be unwrapped (in dcd file when creating MDAnalysis Universe) 38 | Ions selections may consist of only one atom per ion, or include all of the atoms 39 | in the ion. The ion AtomGroups may consist of multiple types of cations/anions. 40 | 41 | Args: 42 | u: MDAnalysis universe 43 | anions: MDAnalysis AtomGroup containing all anions 44 | cations: MDAnalysis AtomGroup containing all cations 45 | run_start: index of trajectory from which to start analysis 46 | cation_charge: net charge of cation 47 | anion_charge: net charge of anion 48 | 49 | Returns a numpy.array containing conductivity "MSD" over time 50 | """ 51 | # convert AtomGroup into list of molecules 52 | cation_list = cations.split("residue") 53 | anion_list = anions.split("residue") 54 | # compute sum over all charges and positions 55 | qr = [] 56 | for _ts in tqdm(u.trajectory[run_start:]): 57 | qr_temp = np.zeros(3) 58 | for anion in anion_list: 59 | qr_temp += anion.center_of_mass() * anion_charge 60 | for cation in cation_list: 61 | qr_temp += cation.center_of_mass() * cation_charge 62 | qr.append(qr_temp) 63 | return msd_fft(np.array(qr)) 64 | 65 | 66 | def get_beta( 67 | msd: np.ndarray, 68 | time_array: np.ndarray, 69 | start: int, 70 | end: int, 71 | ) -> tuple: 72 | """Fits the MSD to the form t^(beta) and returns beta. beta = 1 corresponds 73 | to the diffusive regime. 74 | 75 | Args: 76 | msd: mean squared displacement 77 | time_array: times at which position data was collected in the simulation 78 | start: index at which to start fitting linear regime of the MSD 79 | end: index at which to end fitting linear regime of the MSD 80 | 81 | Returns beta (int) and the range of beta values within the region 82 | """ 83 | msd_slope = np.gradient(np.log(msd[start:end]), np.log(time_array[start:end])) 84 | beta = np.mean(np.array(msd_slope)) 85 | beta_range = np.max(msd_slope) - np.min(msd_slope) 86 | return beta, beta_range 87 | 88 | 89 | def choose_msd_fitting_region( 90 | msd: np.ndarray, 91 | time_array: np.ndarray, 92 | ) -> tuple: 93 | """Chooses the optimal fitting regime for a mean-squared displacement. 94 | The MSD should be of the form t^(beta), where beta = 1 corresponds 95 | to the diffusive regime; as a rule of thumb, the MSD should exhibit this 96 | linear behavior for at least a decade of time. Finds the region of the 97 | MSD with the beta value closest to 1. 98 | 99 | Note: 100 | If a beta value greater than 0.9 cannot be found, returns a warning 101 | that the computed conductivity may not be reliable, and that longer 102 | simulations or more replicates are necessary. 103 | 104 | Args: 105 | msd: mean squared displacement 106 | time_array: times at which position data was collected in the simulation 107 | 108 | Returns at tuple with the start of the fitting regime (int), end of the 109 | fitting regime (int), and the beta value of the fitting regime (float). 110 | """ 111 | beta_best = 0 # region with greatest linearity (beta = 1) 112 | # choose fitting regions to check 113 | for i in np.logspace(np.log10(2), np.log10(len(time_array) / 10), 10): # try 10 regions 114 | start = int(i) 115 | end = int(i * 10) # fit over one decade 116 | beta, beta_range = get_beta(msd, time_array, start, end) 117 | slope_tolerance = 2 # acceptable level of noise in beta values 118 | # check if beta in this region is better than regions tested so far 119 | if (np.abs(beta - 1) < np.abs(beta_best - 1) and beta_range < slope_tolerance) or beta_best == 0: 120 | beta_best = beta 121 | start_final = start 122 | end_final = end 123 | if beta_best < 0.9: 124 | print(f"WARNING: MSD is not sufficiently linear (beta = {beta_best}). Consider running simulations longer.") 125 | return start_final, end_final, beta_best 126 | 127 | 128 | def conductivity_calculator( 129 | time_array: np.ndarray, 130 | cond_array: np.ndarray, 131 | v: float, 132 | name: str, 133 | start: int, 134 | end: int, 135 | T: float, 136 | units: str = "real", 137 | ) -> float: 138 | """Calculates the overall conductivity of the system. 139 | 140 | Args: 141 | time_array: times at which position data was collected in the simulation 142 | cond_array: conductivity "mean squared displacement" 143 | v: simulation volume (Angstroms^3) 144 | name: system name 145 | start: index at which to start fitting linear regime of the MSD 146 | end: index at which to end fitting linear regime of the MSD 147 | T: temperature 148 | units: unit system (currently 'real' and 'lj' are supported) 149 | 150 | Returns the overall ionic conductivity (float) 151 | """ 152 | # Unit conversions 153 | if units == "real": 154 | A2cm = 1e-8 # Angstroms to cm 155 | ps2s = 1e-12 # picoseconds to seconds 156 | e2c = 1.60217662e-19 # elementary charge to Coulomb 157 | kb = 1.38064852e-23 # Boltzmann Constant, J/K 158 | convert = e2c * e2c / ps2s / A2cm * 1000 159 | cond_units = "mS/cm" 160 | elif units == "lj": 161 | kb = 1 162 | convert = 1 163 | cond_units = "q^2/(tau sigma epsilon)" 164 | else: 165 | raise ValueError("units selection not supported") 166 | 167 | slope, _, _, _, _ = stats.linregress(time_array[start:end], cond_array[start:end]) 168 | cond = slope / 6 / kb / T / v * convert 169 | 170 | print("Conductivity of " + name + ": " + str(cond) + " " + cond_units) 171 | 172 | return cond 173 | -------------------------------------------------------------------------------- /mdgo/core/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | This package contains core modules and classes for molecular dynamics 6 | setup, run and analysis. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | __author__ = "Tingzheng Hou" 12 | __version__ = "0.3.1" 13 | __maintainer__ = "Tingzheng Hou" 14 | __email__ = "tingzheng_hou@berkeley.edu" 15 | __date__ = "Dec 19, 2023" 16 | 17 | 18 | from .analysis import MdRun 19 | from .run import MdJob 20 | -------------------------------------------------------------------------------- /mdgo/core/run.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """This module implements a core class MdRun for molecular dynamics job setup.""" 5 | from __future__ import annotations 6 | 7 | 8 | class MdJob: 9 | """A core class for MD results analysis.""" 10 | 11 | def __init__(self, name): 12 | """Base constructor.""" 13 | self.name = name 14 | 15 | @classmethod 16 | def from_dict(cls): 17 | """ 18 | Constructor. 19 | 20 | Returns: 21 | name: The name of the class 22 | 23 | """ 24 | return cls("name") 25 | 26 | @classmethod 27 | def from_recipe(cls): 28 | """ 29 | Constructor. 30 | 31 | Returns: 32 | name: The name of the class 33 | """ 34 | return cls("name") 35 | -------------------------------------------------------------------------------- /mdgo/forcefield/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | This package contains modules and classes for retrieving, generating and 6 | modifying MD force filed data. 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | __author__ = "Tingzheng Hou, Ryan Kingsbury" 12 | __version__ = "0.3.1" 13 | __maintainer__ = "Tingzheng Hou, Ryan Kingsbury" 14 | __email__ = "tingzheng_hou@berkeley.edu" 15 | __date__ = "Dec 19, 2023" 16 | 17 | 18 | from .aqueous import Aqueous, IonLJData 19 | from .charge import ChargeWriter 20 | from .crawler import FFcrawler 21 | from .maestro import MaestroRunner 22 | from .pubchem import PubChemRunner 23 | -------------------------------------------------------------------------------- /mdgo/forcefield/charge.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """A class for writing, overwriting, scaling charges of a LammpsData object.""" 5 | 6 | from __future__ import annotations 7 | 8 | import numpy as np 9 | from pymatgen.io.lammps.data import LammpsData 10 | 11 | 12 | class ChargeWriter: 13 | """ 14 | A class for write, overwrite, scale charges of a LammpsData object. 15 | TODO: Auto determine number of significant figures of charges 16 | TODO: write to obj or write separate charge file 17 | TODO: Read LammpsData or path. 18 | 19 | Args: 20 | data: The provided LammpsData obj. 21 | precision: Number of significant figures. 22 | """ 23 | 24 | def __init__(self, data: LammpsData, precision: int = 10): 25 | """Base constructor.""" 26 | self.data = data 27 | self.precision = precision 28 | 29 | def scale(self, factor: float) -> LammpsData: 30 | """ 31 | Scales the charge in of the in self.data and returns a new one. TODO: check if non-destructive. 32 | 33 | Args: 34 | factor: The charge scaling factor 35 | 36 | Returns: 37 | A recreated LammpsData obj 38 | """ 39 | items = {} 40 | items["box"] = self.data.box 41 | items["masses"] = self.data.masses 42 | atoms = self.data.atoms.copy(deep=True) 43 | atoms["q"] = atoms["q"] * factor 44 | assert np.around(atoms.q.sum(), decimals=self.precision) == np.around( 45 | self.data.atoms.q.sum() * factor, decimals=self.precision 46 | ) 47 | digit_count = 0 48 | for q in atoms["q"]: 49 | rounded = self.count_significant_figures(q) 50 | if rounded > digit_count: 51 | digit_count = rounded 52 | print("No. of significant figures to output for charges: ", digit_count) 53 | items["atoms"] = atoms 54 | items["atom_style"] = self.data.atom_style 55 | items["velocities"] = self.data.velocities 56 | items["force_field"] = self.data.force_field 57 | items["topology"] = self.data.topology 58 | return LammpsData(**items) 59 | 60 | def count_significant_figures(self, number: float) -> int: 61 | """ 62 | Count significant figures in a float. 63 | 64 | Args: 65 | number: The number to count. 66 | 67 | Returns: 68 | The number of significant figures. 69 | """ 70 | number_str = repr(float(number)) 71 | tokens = number_str.split(".") 72 | if len(tokens) > 2: 73 | raise ValueError(f"Invalid number '{number}' only 1 decimal allowed") 74 | if len(tokens) == 2: 75 | decimal_num = tokens[1][: self.precision].rstrip("0") 76 | return len(decimal_num) 77 | return 0 78 | -------------------------------------------------------------------------------- /mdgo/forcefield/crawler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | This module implements two core class FFcrawler for generating 6 | LAMMPS/GROMACS data files from molecule structure using 7 | the LigParGen web server. 8 | 9 | For using the FFcrawler class: 10 | 11 | * Download the ChromeDriver executable that 12 | matches your Chrome version via https://chromedriver.chromium.org/downloads 13 | """ 14 | 15 | from __future__ import annotations 16 | 17 | import os 18 | import shutil 19 | import time 20 | 21 | from pymatgen.io.lammps.data import LammpsData 22 | from selenium import webdriver 23 | from selenium.common.exceptions import TimeoutException, WebDriverException 24 | from selenium.webdriver.common.by import By 25 | from selenium.webdriver.support import expected_conditions as EC 26 | from selenium.webdriver.support.ui import WebDriverWait 27 | 28 | from mdgo.util.dict_utils import lmp_mass_to_name 29 | 30 | 31 | class FFcrawler: 32 | """ 33 | Web scrapper that can automatically upload structure to the LigParGen 34 | server and download LAMMPS/GROMACS data file. 35 | 36 | Args: 37 | write_dir: Directory for writing output. 38 | chromedriver_dir: Directory to the ChromeDriver executable. 39 | headless: Whether to run Chrome in headless (silent) mode. 40 | Default to True. 41 | xyz: Whether to write the structure in the LigParGen 42 | generated data file as .xyz. Default to False. This is useful 43 | because the order and the name of the atoms could be 44 | different from the initial input.) 45 | gromacs: Whether to save GROMACS format data files. 46 | Default to False. 47 | 48 | Examples: 49 | >>> lpg = FFcrawler('/path/to/work/dir', '/path/to/chromedriver') 50 | >>> lpg.data_from_pdb("/path/to/pdb") 51 | """ 52 | 53 | def __init__( 54 | self, 55 | write_dir: str, 56 | chromedriver_dir: str | None = None, 57 | headless: bool = True, 58 | xyz: bool = False, 59 | gromacs: bool = False, 60 | ): 61 | """Base constructor.""" 62 | self.write_dir = write_dir 63 | self.xyz = xyz 64 | self.gromacs = gromacs 65 | self.preferences = { 66 | "download.default_directory": write_dir, 67 | "safebrowsing.enabled": "false", 68 | "profile.managed_default_content_settings.images": 2, 69 | } 70 | self.options = webdriver.ChromeOptions() 71 | self.server = webdriver.ChromeService(chromedriver_dir) 72 | self.options.add_argument( 73 | 'user-agent="Mozilla/5.0 ' 74 | "(Macintosh; Intel Mac OS X 10_14_6) " 75 | "AppleWebKit/537.36 (KHTML, like Gecko) " 76 | 'Chrome/88.0.4324.146 Safari/537.36"' 77 | ) 78 | self.options.add_argument("--window-size=1920,1080") 79 | self.options.add_argument("ignore-certificate-errors") 80 | if headless: 81 | self.options.add_argument("--headless") 82 | self.options.add_experimental_option("prefs", self.preferences) 83 | self.options.add_experimental_option("excludeSwitches", ["enable-automation"]) 84 | if chromedriver_dir is None: 85 | self.web = webdriver.Chrome(options=self.options) 86 | else: 87 | self.web = webdriver.Chrome(service=self.server, options=self.options) 88 | self.wait = WebDriverWait(self.web, 10) 89 | self.web.get("http://traken.chem.yale.edu/ligpargen/") 90 | time.sleep(1) 91 | print("LigParGen server connected.") 92 | 93 | def quit(self): 94 | """Method for quiting ChromeDriver.""" 95 | self.web.quit() 96 | 97 | def data_from_pdb(self, pdb_dir: str): 98 | """ 99 | Use the LigParGen server to generate a LAMMPS data file from a pdb file. 100 | Write out a LAMMPS data file. 101 | 102 | Args: 103 | pdb_dir: The path to the input pdb structure file. 104 | """ 105 | self.web.get("http://traken.chem.yale.edu/ligpargen/") 106 | upload_xpath = '//*[@id="exampleMOLFile"]' 107 | time.sleep(1) 108 | self.wait.until(EC.presence_of_element_located((By.XPATH, upload_xpath))) 109 | upload = self.web.find_element(By.XPATH, upload_xpath) 110 | try: 111 | upload.send_keys(pdb_dir) 112 | submit = self.web.find_element(By.XPATH, "/html/body/div[2]/div/div[2]/form/button[1]") 113 | submit.click() 114 | pdb_filename = os.path.basename(pdb_dir) 115 | self.download_data(os.path.splitext(pdb_filename)[0] + ".lmp") 116 | except TimeoutException: 117 | print("Timeout! Web server no response for 10s, file download failed!") 118 | except WebDriverException as e: 119 | print(e) 120 | finally: 121 | self.quit() 122 | 123 | def data_from_smiles(self, smiles_code): 124 | """ 125 | Use the LigParGen server to generate a LAMMPS data file from a SMILES code. 126 | Write out a LAMMPS data file. 127 | 128 | Args: 129 | smiles_code: The SMILES code for the LigParGen input. 130 | """ 131 | self.web.get("http://traken.chem.yale.edu/ligpargen/") 132 | time.sleep(1) 133 | smile = self.web.find_element(By.XPATH, '//*[@id="smiles"]') 134 | smile.send_keys(smiles_code) 135 | submit = self.web.find_element(By.XPATH, "/html/body/div[2]/div/div[2]/form/button[1]") 136 | submit.click() 137 | try: 138 | self.download_data(smiles_code + ".lmp") 139 | except TimeoutException: 140 | print("Timeout! Web server no response for 10s, file download failed!") 141 | finally: 142 | self.quit() 143 | 144 | def download_data(self, lmp_name: str): 145 | """ 146 | Helper function that download and write out the LAMMPS data file. 147 | 148 | Args: 149 | lmp_name: Name of the LAMMPS data file. 150 | """ 151 | print("Structure info uploaded. Rendering force field...") 152 | lmp_xpath = "/html/body/div[2]/div[2]/div[1]/div/div[14]/form/input[1]" 153 | self.wait.until(EC.presence_of_element_located((By.XPATH, lmp_xpath))) 154 | jmol = self.web.find_element(By.XPATH, "/html/body/div[2]/div[2]/div[2]") 155 | self.web.execute_script("arguments[0].remove();", jmol) 156 | self.wait.until(EC.element_to_be_clickable((By.XPATH, lmp_xpath))) 157 | data_lmp = self.web.find_element(By.XPATH, lmp_xpath) 158 | num_file = len([f for f in os.listdir(self.write_dir) if os.path.splitext(f)[1] == ".lmp"]) + 1 159 | data_lmp.click() 160 | while True: 161 | files = sorted( 162 | [ 163 | os.path.join(self.write_dir, f) 164 | for f in os.listdir(self.write_dir) 165 | if os.path.splitext(f)[1] == ".lmp" 166 | ], 167 | key=os.path.getmtime, 168 | ) 169 | # wait for file to finish download 170 | if len(files) < num_file: 171 | time.sleep(1) 172 | print("waiting for download to be initiated") 173 | else: 174 | newest = files[-1] 175 | if ".crdownload" in newest: 176 | time.sleep(1) 177 | print("waiting for download to complete") 178 | else: 179 | break 180 | print("Force field file downloaded.") 181 | lmp_file = newest 182 | if self.xyz: 183 | data_obj = LammpsData.from_file(lmp_file) 184 | element_id_dict = lmp_mass_to_name(data_obj.masses) 185 | coords = data_obj.atoms[["type", "x", "y", "z"]] 186 | lines = [] 187 | lines.append(str(len(coords.index))) 188 | lines.append("") 189 | for _, r in coords.iterrows(): 190 | element_name = element_id_dict.get(int(r["type"])) 191 | assert element_name is not None 192 | line = element_name + " " + " ".join(str(r[loc]) for loc in ["x", "y", "z"]) 193 | lines.append(line) 194 | 195 | with open(os.path.join(self.write_dir, lmp_name + ".xyz"), "w") as xyz_file: 196 | xyz_file.write("\n".join(lines)) 197 | print(".xyz file saved.") 198 | if self.gromacs: 199 | data_gro = self.web.find_element(By.XPATH, "/html/body/div[2]/div[2]/div[1]/div/div[8]/form/input[1]") 200 | data_itp = self.web.find_element(By.XPATH, "/html/body/div[2]/div[2]/div[1]/div/div[9]/form/input[1]") 201 | data_gro.click() 202 | data_itp.click() 203 | time.sleep(1) 204 | gro_file = max( 205 | [self.write_dir + "/" + f for f in os.listdir(self.write_dir) if os.path.splitext(f)[1] == ".gro"], 206 | key=os.path.getctime, 207 | ) 208 | itp_file = max( 209 | [self.write_dir + "/" + f for f in os.listdir(self.write_dir) if os.path.splitext(f)[1] == ".itp"], 210 | key=os.path.getctime, 211 | ) 212 | shutil.move(gro_file, os.path.join(self.write_dir, lmp_name[:-4] + ".gro")) 213 | shutil.move(itp_file, os.path.join(self.write_dir, lmp_name[:-4] + ".itp")) 214 | shutil.move(lmp_file, os.path.join(self.write_dir, lmp_name)) 215 | print("Force field file saved.") 216 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_opc.lmp: -------------------------------------------------------------------------------- 1 | OPC3 water model 2 | # Original model from Izadi, Anandakrishnan, and Onufriev, Building Water Models: 3 | # A Different Approach. J. Phys. Chem. Lett. 2014, 5, 21, 3863–3871 4 | # https://doi.org/10.1021/jz501780a 5 | # Table 2. 6 | # real units (kcal/mol, Angstrom) 7 | # Command 'fix shake' is needed. 8 | # LJ and couloumb cutoffs of 9 and 7 angstroms are adopted from the TIP3B-FB paper 9 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 10 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 11 | 12 | 3 atoms 13 | 2 bonds 14 | 1 angles 15 | 16 | 2 atom types 17 | 1 bond types 18 | 1 angle types 19 | 20 | 0.0 3.1 xlo xhi 21 | 0.0 3.1 ylo yhi 22 | 0.0 3.1 zlo zhi 23 | 24 | Masses 25 | 26 | 1 1.00794 27 | 2 15.9994 28 | 29 | Pair Coeffs #lj/cut/tip4p/long 2 1 1 1 0.1594 9.0 7.0 30 | 31 | 1 0.0000 0.0000 32 | 2 0.21280 3.16655 33 | 34 | Bond Coeffs #harmonic 35 | 36 | 1 1000 0.8724 37 | 38 | Angle Coeffs #harmonic 39 | 40 | 1 1000 103.6 41 | 42 | Atoms 43 | 44 | 1 1 2 -1.3582 1.55000 1.55000 1.50000 45 | 2 1 1 0.6791 1.55000 2.36649 2.07736 46 | 3 1 1 0.6791 1.55000 0.73351 2.07736 47 | 48 | Bonds 49 | 50 | 1 1 1 2 51 | 2 1 1 3 52 | 53 | Angles 54 | 55 | 1 1 2 1 3 56 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_opc3.lmp: -------------------------------------------------------------------------------- 1 | OPC3 water model 2 | # Original model from Izadi and Onufriev, Accuracy limit of rigid 3-point water models 3 | # J. Chemical Physics 145, 074501, 2016. https://doi.org/10.1063/1.4960175, Table II. 4 | # real units (kcal/mol, Angstrom) 5 | # Command 'fix shake' is needed. 6 | # LJ and couloumb cutoffs of 9 and 7 angstroms are adopted from the TIP3B-FB paper 7 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 8 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 9 | 10 | 3 atoms 11 | 2 bonds 12 | 1 angles 13 | 14 | 2 atom types 15 | 1 bond types 16 | 1 angle types 17 | 18 | 0.0 3.1 xlo xhi 19 | 0.0 3.1 ylo yhi 20 | 0.0 3.1 zlo zhi 21 | 22 | Masses 23 | 24 | 1 1.00794 25 | 2 15.9994 26 | 27 | Pair Coeffs #lj/cut/coul/long 9.0 7.0 28 | 29 | 1 0.000 0.000 30 | 2 0.16340 3.17427 31 | 32 | Bond Coeffs #harmonic 33 | 34 | 1 1000 0.97888 35 | 36 | Angle Coeffs #harmonic 37 | 38 | 1 1000 109.47 39 | 40 | Atoms 41 | 42 | 1 1 2 -0.89517 1.55000 1.55000 1.50000 43 | 2 1 1 0.447585 1.55000 2.36649 2.07736 44 | 3 1 1 0.447585 1.55000 0.73351 2.07736 45 | 46 | Bonds 47 | 48 | 1 1 1 2 49 | 2 1 1 3 50 | 51 | Angles 52 | 53 | 1 1 2 1 3 54 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_spc.lmp: -------------------------------------------------------------------------------- 1 | SPC water model 2 | # Berendsen et al, in "Intermolecular forces", p. 331 (1981). Command 'fix shake' is needed. 3 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 4 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 5 | 6 | 3 atoms 7 | 2 bonds 8 | 1 angles 9 | 10 | 2 atom types 11 | 1 bond types 12 | 1 angle types 13 | 14 | 0.0 3.1 xlo xhi 15 | 0.0 3.1 ylo yhi 16 | 0.0 3.1 zlo zhi 17 | 18 | Masses 19 | 20 | 1 1.00794 21 | 2 15.9994 22 | 23 | Pair Coeffs #lj/cut/coul/long 10.0 24 | 25 | 1 0.0000 0.0000 26 | 2 0.1554 3.16557 27 | 28 | Bond Coeffs #harmonic 29 | 30 | 1 1000 1.0 31 | 32 | Angle Coeffs #harmonic 33 | 34 | 1 1000 109.47 35 | 36 | Atoms 37 | 38 | 1 1 2 -0.82 1.55000 1.55000 1.50000 39 | 2 1 1 0.41 1.55000 2.36649 2.07736 40 | 3 1 1 0.41 1.55000 0.73351 2.07736 41 | 42 | Bonds 43 | 44 | 1 1 1 2 45 | 2 1 1 3 46 | 47 | Angles 48 | 49 | 1 1 2 1 3 50 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_spce.lmp: -------------------------------------------------------------------------------- 1 | SPC/E water model 2 | # From Berendsen et al, J Phys Chem 91:6269 (1987). Command 'fix shake' is needed. 3 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 4 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 5 | 6 | 3 atoms 7 | 2 bonds 8 | 1 angles 9 | 10 | 2 atom types 11 | 1 bond types 12 | 1 angle types 13 | 14 | 0.0 3.1 xlo xhi 15 | 0.0 3.1 ylo yhi 16 | 0.0 3.1 zlo zhi 17 | 18 | Masses 19 | 20 | 1 1.00794 21 | 2 15.9994 22 | 23 | Pair Coeffs #lj/cut/coul/long 10.0 24 | 25 | 1 0.0000 0.0000 26 | 2 0.1554 3.16557 27 | 28 | Bond Coeffs #harmonic 29 | 30 | 1 1000 1.0 31 | 32 | Angle Coeffs #harmonic 33 | 34 | 1 1000 109.47 35 | 36 | Atoms 37 | 38 | 1 1 2 -0.8476 1.55000 1.55000 1.50000 39 | 2 1 1 0.4238 1.55000 2.36649 2.07736 40 | 3 1 1 0.4238 1.55000 0.73351 2.07736 41 | 42 | Bonds 43 | 44 | 1 1 1 2 45 | 2 1 1 3 46 | 47 | Angles 48 | 49 | 1 1 2 1 3 50 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_tip3p_ew.lmp: -------------------------------------------------------------------------------- 1 | Optimized TIP3P parameters to use with Ewald methods for long-range electrostatics. (e.g. pppm 1.0e-5) 2 | # Price & Brooks, J Chem Phys 121:10096 (2004). 3 | 4 | 3 atoms 5 | 2 bonds 6 | 1 angles 7 | 8 | 2 atom types 9 | 1 bond types 10 | 1 angle types 11 | 12 | 0.0 3.1 xlo xhi 13 | 0.0 3.1 ylo yhi 14 | 0.0 3.1 zlo zhi 15 | 16 | Masses 17 | 18 | 1 1.00794 19 | 2 15.9994 20 | 21 | Pair Coeffs #lj/cut/coul/long 13.0 22 | 23 | 1 0.000 0.000 24 | 2 0.102 3.188 25 | 26 | Bond Coeffs #harmonic 27 | 28 | 1 450.0 0.9572 29 | 30 | Angle Coeffs #harmonic 31 | 32 | 1 55.0 104.52 33 | 34 | Atoms 35 | 36 | 1 1 2 -0.830 1.55000 1.55000 1.50000 37 | 2 1 1 0.415 1.55000 2.36649 2.07736 38 | 3 1 1 0.415 1.55000 0.73351 2.07736 39 | 40 | Bonds 41 | 42 | 1 1 1 2 43 | 2 1 1 3 44 | 45 | Angles 46 | 47 | 1 1 2 1 3 48 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_tip3p_fb.lmp: -------------------------------------------------------------------------------- 1 | Optimized TIP3P-FB parameters for water 2 | # Original model from Wang et al., Building Force Fields: An Automatic, 3 | # Systematic, and Reproducible Approach, J. Phys. Chem. Letters 5(11), 2014. 4 | # https://pubs.acs.org/doi/10.1021/jz500737m 5 | # real units (kcal/mol, Angstrom) 6 | # Command 'fix shake' is needed. 7 | # LJ and couloumb cutoffs of 9 and 7 angstroms were used in the original work 8 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 9 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 10 | 11 | 3 atoms 12 | 2 bonds 13 | 1 angles 14 | 15 | 2 atom types 16 | 1 bond types 17 | 1 angle types 18 | 19 | 0.0 3.1 xlo xhi 20 | 0.0 3.1 ylo yhi 21 | 0.0 3.1 zlo zhi 22 | 23 | Masses 24 | 25 | 1 1.00794 26 | 2 15.9994 27 | 28 | Pair Coeffs #lj/cut/coul/long 9.0 7.0 29 | 30 | 1 0.000 0.000 31 | 2 0.15587 3.1780 32 | 33 | Bond Coeffs #harmonic 34 | 35 | 1 1000 1.0118 36 | 37 | Angle Coeffs #harmonic 38 | 39 | 1 1000 108.15 40 | 41 | Atoms 42 | 43 | 1 1 2 -0.84844 1.55000 1.55000 1.50000 44 | 2 1 1 0.42422 1.55000 2.36649 2.07736 45 | 3 1 1 0.42422 1.55000 0.73351 2.07736 46 | 47 | Bonds 48 | 49 | 1 1 1 2 50 | 2 1 1 3 51 | 52 | Angles 53 | 54 | 1 1 2 1 3 55 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_tip4p_2005.lmp: -------------------------------------------------------------------------------- 1 | This forcefield file sets a 13-Angstrom cutoff, recommended for liquid-vapor simulations 2 | # [Vega & de Miguel, J Chem Phys 126:154707 (2007), Vega et al, Faraday Discuss 141:251 (2009)]. Note that the original TIP4P/2005 model was run with an 8.5 Angstrom cutoff [Abascal & Vega, J Chem Phys 123:234505 (2005)]. 3 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 4 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 5 | 6 | 3 atoms 7 | 2 bonds 8 | 1 angles 9 | 10 | 2 atom types 11 | 1 bond types 12 | 1 angle types 13 | 14 | 0.0 3.1 xlo xhi 15 | 0.0 3.1 ylo yhi 16 | 0.0 3.1 zlo zhi 17 | 18 | Masses 19 | 20 | 1 1.00794 21 | 2 15.9994 22 | 23 | Pair Coeffs #lj/cut/tip4p/long 2 1 1 1 0.1546 13.0 24 | 25 | 1 0.000 0.000 26 | 2 0.18520 3.1589 27 | 28 | Bond Coeffs #harmonic 29 | 30 | 1 1000 0.9572 31 | 32 | Angle Coeffs #harmonic 33 | 34 | 1 1000 104.52 35 | 36 | Atoms 37 | 38 | 1 1 2 -1.1128 1.55000 1.55000 1.50000 39 | 2 1 1 0.5564 1.55000 2.36649 2.07736 40 | 3 1 1 0.5564 1.55000 0.73351 2.07736 41 | 42 | Bonds 43 | 44 | 1 1 1 2 45 | 2 1 1 3 46 | 47 | Angles 48 | 49 | 1 1 2 1 3 50 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_tip4p_ew.lmp: -------------------------------------------------------------------------------- 1 | Optimized TIP4P parameters to use with Ewald methods for long-range electrostatics. (e.g. pppm/tip4p 1.0e-5) 2 | # Original model from Horn et al, J Chem Phys 120: 9665 (2004). Command 'fix shake' is needed. 3 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 4 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 5 | 6 | 3 atoms 7 | 2 bonds 8 | 1 angles 9 | 10 | 2 atom types 11 | 1 bond types 12 | 1 angle types 13 | 14 | 0.0 3.1 xlo xhi 15 | 0.0 3.1 ylo yhi 16 | 0.0 3.1 zlo zhi 17 | 18 | Masses 19 | 20 | 1 1.00794 21 | 2 15.9994 22 | 23 | Pair Coeffs #lj/cut/tip4p/long 2 1 1 1 0.125 10.0 24 | 25 | 1 0.000 0.000 26 | 2 0.16275 3.16435 27 | 28 | Bond Coeffs #harmonic 29 | 30 | 1 1000 0.9572 31 | 32 | Angle Coeffs #harmonic 33 | 34 | 1 1000 104.52 35 | 36 | Atoms 37 | 38 | 1 1 2 -1.04844 1.55000 1.55000 1.50000 39 | 2 1 1 0.52422 1.55000 2.36649 2.07736 40 | 3 1 1 0.52422 1.55000 0.73351 2.07736 41 | 42 | Bonds 43 | 44 | 1 1 1 2 45 | 2 1 1 3 46 | 47 | Angles 48 | 49 | 1 1 2 1 3 50 | -------------------------------------------------------------------------------- /mdgo/forcefield/data/water/water_tip4p_fb.lmp: -------------------------------------------------------------------------------- 1 | Optimized TIP4P-FB parameters for water 2 | # Original model from Wang et al., Building Force Fields: An Automatic, 3 | # Systematic, and Reproducible Approach, J. Phys. Chem. Letters 5(11), 2014. 4 | # https://pubs.acs.org/doi/10.1021/jz500737m 5 | # real units (kcal/mol, Angstrom) 6 | # Command 'fix shake' is needed. 7 | # LJ and couloumb cutoffs of 9 and 7 angstroms were used in the original work 8 | # Bond and angle coefficients of 1000 are added to keep the water molecule together 9 | # during e.g. energy minimization in LAMMPS when `fix shake` is not active. 10 | 11 | 3 atoms 12 | 2 bonds 13 | 1 angles 14 | 15 | 2 atom types 16 | 1 bond types 17 | 1 angle types 18 | 19 | 0.0 3.1 xlo xhi 20 | 0.0 3.1 ylo yhi 21 | 0.0 3.1 zlo zhi 22 | 23 | Masses 24 | 25 | 1 1.00794 26 | 2 15.9994 27 | 28 | Pair Coeffs #lj/cut/tip4p/long 2 1 1 1 0.10527 9.0 7.0 29 | 30 | 1 0.000 0.000 31 | 2 0.17908 3.1655 32 | 33 | Bond Coeffs #harmonic 34 | 35 | 1 1000 0.9572 36 | 37 | Angle Coeffs #harmonic 38 | 39 | 1 1000 104.52 40 | 41 | Atoms 42 | 43 | 1 1 2 -1.05174 1.55000 1.55000 1.50000 44 | 2 1 1 0.52587 1.55000 2.36649 2.07736 45 | 3 1 1 0.52587 1.55000 0.73351 2.07736 46 | 47 | Bonds 48 | 49 | 1 1 1 2 50 | 2 1 1 3 51 | 52 | Angles 53 | 54 | 1 1 2 1 3 55 | -------------------------------------------------------------------------------- /mdgo/forcefield/maestro.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | This module implements a core class MaestroRunner for generating 6 | LAMMPS/GROMACS data files from molecule structure using Maestro. 7 | 8 | For using the MaestroRunner class: 9 | 10 | * Download a free Maestro via https://www.schrodinger.com/freemaestro 11 | 12 | * Install the package and set the environment variable $SCHRODINGER 13 | (e.g. 'export SCHRODINGER=/opt/schrodinger/suites2021-4', please 14 | check https://www.schrodinger.com/kb/446296 or 15 | https://www.schrodinger.com/kb/1842 for details. 16 | 17 | """ 18 | 19 | from __future__ import annotations 20 | 21 | import os 22 | import signal 23 | import subprocess 24 | import time 25 | from string import Template 26 | from typing import Final 27 | 28 | from mdgo.util.reformat import ff_parser 29 | 30 | MAESTRO: Final[str] = "$SCHRODINGER/maestro -console -nosplash" 31 | FFLD: Final[str] = "$SCHRODINGER/utilities/ffld_server -imae {} -version 14 -print_parameters -out_file {}" 32 | MODULE_DIR: Final[str] = os.path.dirname(os.path.abspath(__file__)) 33 | 34 | 35 | class MaestroRunner: 36 | """ 37 | Wrapper for the Maestro software that can be used to generate the OPLS_2005 38 | force field parameter for a molecule. 39 | 40 | Args: 41 | structure_dir: Path to the structure file. 42 | Supported input format please check 43 | https://www.schrodinger.com/kb/1278 44 | working_dir: Directory for writing intermediate 45 | and final output. 46 | out: Force field output form. Default to "lmp", 47 | the data file for LAMMPS. Other supported formats 48 | are under development. 49 | cmd_template: String template for input script 50 | with placeholders. Default to None, i.e., using 51 | the default template. 52 | assign_bond: Whether to assign bond to the input 53 | structure. Default to None. 54 | 55 | Supported input format please check https://www.schrodinger.com/kb/1278 56 | 57 | The OPLS_2005 parameters are described in 58 | 59 | Banks, J.L.; Beard, H.S.; Cao, Y.; Cho, A.E.; Damm, W.; Farid, R.; 60 | Felts, A.K.; Halgren, T.A.; Mainz, D.T.; Maple, J.R.; Murphy, R.; 61 | Philipp, D.M.; Repasky, M.P.; Zhang, L.Y.; Berne, B.J.; Friesner, R.A.; 62 | Gallicchio, E.; Levy. R.M. Integrated Modeling Program, Applied Chemical 63 | Theory (IMPACT). J. Comp. Chem. 2005, 26, 1752. 64 | 65 | The OPLS_2005 parameters are located in 66 | 67 | $SCHRODINGER/mmshare-vversion/data/f14/ 68 | 69 | Examples: 70 | >>> mr = MaestroRunner('/path/to/structure', '/path/to/working/dir') 71 | >>> mr.get_mae() 72 | >>> mr.get_ff() 73 | """ 74 | 75 | template_assignbond = os.path.join(MODULE_DIR, "..", "templates", "mae_cmd_assignbond.txt") 76 | 77 | template_noassignbond = os.path.join(MODULE_DIR, "..", "templates", "mae_cmd_noassignbond.txt") 78 | 79 | def __init__( 80 | self, 81 | structure_dir: str, 82 | working_dir: str, 83 | out: str = "lmp", 84 | cmd_template: str | None = None, 85 | assign_bond: bool = False, 86 | ): 87 | """Base constructor.""" 88 | self.structure = structure_dir 89 | self.out = out 90 | self.structure_format = os.path.splitext(self.structure)[1][1:] 91 | self.name = os.path.splitext(os.path.split(self.structure)[-1])[0] 92 | print("Input format:", self.structure_format) 93 | self.work = working_dir 94 | self.cmd = os.path.join(self.work, "maetro_script.cmd") 95 | self.mae = os.path.join(self.work, self.name) 96 | self.ff = os.path.join(self.work, self.name + ".out") 97 | self.xyz = os.path.join(self.work, self.name + ".xyz") 98 | if cmd_template: 99 | self.cmd_template = cmd_template 100 | else: 101 | if assign_bond: 102 | with open(self.template_assignbond) as f: 103 | cmd_template = f.read() 104 | self.cmd_template = cmd_template 105 | else: 106 | with open(self.template_noassignbond) as f: 107 | cmd_template = f.read() 108 | self.cmd_template = cmd_template 109 | 110 | def get_mae(self, wait: float = 30): 111 | """Write a Maestro command script and execute it to generate a 112 | maestro file containing all the info needed. 113 | 114 | Args: 115 | wait: The time waiting for Maestro execution in seconds. Default to 30. 116 | """ 117 | with open(self.cmd, "w") as f: 118 | cmd_template = Template(self.cmd_template) 119 | cmd_script = cmd_template.substitute(file=self.structure, mae=self.mae, xyz=self.xyz) 120 | f.write(cmd_script) 121 | try: 122 | p = subprocess.Popen( # pylint: disable=consider-using-with 123 | f"{MAESTRO} -c {self.cmd}", 124 | shell=True, 125 | stdout=subprocess.PIPE, 126 | stderr=subprocess.PIPE, 127 | start_new_session=True, 128 | ) 129 | except subprocess.CalledProcessError as e: 130 | raise ValueError(f"Maestro failed with errorcode {e.returncode} and stderr: {e.stderr}") from e 131 | 132 | counter = 0 133 | while not os.path.isfile(self.mae + ".mae"): 134 | time.sleep(1) 135 | counter += 1 136 | if counter > wait: 137 | os.killpg(os.getpgid(p.pid), signal.SIGTERM) 138 | raise TimeoutError(f"Failed to generate Maestro file in {wait} secs!") 139 | print("Maestro file generated!") 140 | os.killpg(os.getpgid(p.pid), signal.SIGTERM) 141 | 142 | def get_ff(self): 143 | """Read the Maestro file and save the force field as LAMMPS data file.""" 144 | try: 145 | subprocess.run( 146 | FFLD.format(self.mae + ".mae", self.ff), 147 | check=True, 148 | shell=True, 149 | capture_output=True, 150 | ) 151 | except subprocess.CalledProcessError as e: 152 | raise ValueError(f"Maestro failed with errorcode {e.returncode} and stderr: {e.stderr}") from e 153 | print("Maestro force field file generated.") 154 | if self.out: 155 | if self.out == "lmp": 156 | with open(os.path.join(self.work, self.name + "." + self.out), "w") as f: 157 | f.write(ff_parser(self.ff, self.xyz)) 158 | print("LAMMPS data file generated.") 159 | else: 160 | print("Output format not supported, ff format not converted.") 161 | -------------------------------------------------------------------------------- /mdgo/residence_time.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """This module calculates species correlation lifetime (residence time).""" 5 | 6 | from __future__ import annotations 7 | 8 | import os 9 | from typing import TYPE_CHECKING 10 | 11 | import matplotlib.pyplot as plt 12 | import numpy as np 13 | from scipy.optimize import curve_fit 14 | from statsmodels.tsa.stattools import acovf 15 | from tqdm.auto import tqdm 16 | 17 | if TYPE_CHECKING: 18 | from MDAnalysis import Universe 19 | from MDAnalysis.core.groups import Atom 20 | 21 | __author__ = "Kara Fong, Tingzheng Hou" 22 | __version__ = "0.3.0" 23 | __maintainer__ = "Tingzheng Hou" 24 | __email__ = "tingzheng_hou@berkeley.edu" 25 | __date__ = "Jul 19, 2021" 26 | 27 | 28 | def neighbors_one_atom( 29 | nvt_run: Universe, 30 | center_atom: Atom, 31 | species: str, 32 | select_dict: dict[str, str], 33 | distance: float, 34 | run_start: int, 35 | run_end: int, 36 | ) -> dict[str, np.ndarray]: 37 | """ 38 | Create adjacency matrix for one center atom. 39 | 40 | Args: 41 | nvt_run: An MDAnalysis ``Universe``. 42 | center_atom: The center atom object. 43 | species: The neighbor species in the select_dict. 44 | select_dict: A dictionary of atom species selection, where each atom species name is a key 45 | and the corresponding values are the selection language. 46 | distance: The neighbor cutoff distance. 47 | run_start: Start frame of analysis. 48 | run_end: End frame of analysis. 49 | 50 | Returns: 51 | A neighbor dict with neighbor atom id as keys and arrays of adjacent boolean (0/1) as values. 52 | """ 53 | bool_values = {} 54 | for time_count, _ts in enumerate(nvt_run.trajectory[run_start:run_end:]): 55 | if species in select_dict: 56 | selection = ( 57 | "(" 58 | + select_dict[species] 59 | + ") and (around " 60 | + str(distance) 61 | + " index " 62 | + str(center_atom.id - 1) 63 | + ")" 64 | ) 65 | shell = nvt_run.select_atoms(selection) 66 | else: 67 | raise ValueError("Invalid species selection") 68 | for atom in shell.atoms: 69 | if str(atom.id) not in bool_values: 70 | bool_values[str(atom.id)] = np.zeros(int((run_end - run_start) / 1)) 71 | bool_values[str(atom.id)][time_count] = 1 72 | return bool_values 73 | 74 | 75 | def calc_acf(a_values: dict[str, np.ndarray]) -> list[np.ndarray]: 76 | """ 77 | Calculate auto-correlation function (ACF). 78 | 79 | Args: 80 | a_values: A dict of adjacency matrix with neighbor atom id as keys and arrays 81 | of adjacent boolean (0/1) as values. 82 | 83 | Returns: 84 | A list of auto-correlation functions for each neighbor species. 85 | """ 86 | acfs = [] 87 | for neighbors in a_values.values(): # for _atom_id, neighbors in a_values.items(): 88 | # atom_id_numeric = int(re.search(r"\d+", _atom_id).group()) 89 | acfs.append(acovf(neighbors, demean=False, unbiased=True, fft=True)) 90 | return acfs 91 | 92 | 93 | def exponential_func( 94 | x: float | np.floating | np.ndarray, 95 | a: float | np.floating | np.ndarray, 96 | b: float | np.floating | np.ndarray, 97 | c: float | np.floating | np.ndarray, 98 | ) -> np.floating | np.ndarray: 99 | """ 100 | An exponential decay function. 101 | 102 | Args: 103 | x: Independent variable. 104 | a: Initial quantity. 105 | b: Exponential decay constant. 106 | c: Constant. 107 | 108 | Returns: 109 | The acf 110 | """ 111 | return a * np.exp(-b * x) + c 112 | 113 | 114 | def calc_neigh_corr( 115 | nvt_run: Universe, 116 | distance_dict: dict[str, float], 117 | select_dict: dict[str, str], 118 | time_step: float, 119 | run_start: int, 120 | run_end: int, 121 | center_atom: str = "cation", 122 | ) -> tuple[np.ndarray, dict[str, np.ndarray]]: 123 | """Calculates the neighbor auto-correlation function (ACF) 124 | of selected species around center atom. 125 | 126 | Args: 127 | nvt_run: An MDAnalysis ``Universe``. 128 | distance_dict: A dict of coordination cutoff distance of the neighbor species. 129 | select_dict: A dictionary of atom species selection. 130 | time_step: Timestep between each frame, in ps. 131 | run_start: Start frame of analysis. 132 | run_end: End frame of analysis. 133 | center_atom: The center atom to calculate the ACF for. Default to "cation". 134 | 135 | Returns: 136 | A tuple containing the time series, and a dict of acf of neighbor species. 137 | """ 138 | # Set up times array 139 | times = [] 140 | center_atoms = nvt_run.select_atoms(select_dict[center_atom]) 141 | for step, _ts in enumerate(nvt_run.trajectory[run_start:run_end]): 142 | times.append(step * time_step) 143 | times = np.array(times) 144 | 145 | acf_avg = {} 146 | for kw in distance_dict: 147 | acf_all = [] 148 | for atom in tqdm(center_atoms[::]): 149 | distance = distance_dict.get(kw) 150 | assert distance is not None 151 | adjacency_matrix = neighbors_one_atom( 152 | nvt_run, 153 | atom, 154 | kw, 155 | select_dict, 156 | distance, 157 | run_start, 158 | run_end, 159 | ) 160 | acfs = calc_acf(adjacency_matrix) 161 | acf_all.extend(list(acfs)) 162 | acf_avg[kw] = np.mean(acf_all, axis=0) 163 | return times, acf_avg 164 | 165 | 166 | def fit_residence_time( 167 | times: np.ndarray, 168 | acf_avg_dict: dict[str, np.ndarray], 169 | cutoff_time: int, 170 | time_step: float, 171 | save_curve: str | bool = False, 172 | ) -> dict[str, np.floating]: 173 | """ 174 | Use the ACF to fit the residence time (Exponential decay constant). 175 | TODO: allow defining the residence time according to a threshold value of the decay. 176 | 177 | Args: 178 | times: A time series. 179 | acf_avg_dict: A dict containing the ACFs of the species. 180 | cutoff_time: Fitting cutoff time. 181 | time_step: The time step between each frame, in ps. 182 | save_curve: Whether to save the curve as a csv file for post-processing. 183 | Default to False. 184 | 185 | Returns: 186 | A dict containing residence time of each species 187 | """ 188 | acf_avg_norm = {} 189 | popt = {} 190 | pcov = {} 191 | tau = {} 192 | species_list = list(acf_avg_dict.keys()) 193 | 194 | # Exponential fit of solvent-Li ACF 195 | for kw in species_list: 196 | acf_avg_norm[kw] = acf_avg_dict[kw] / acf_avg_dict[kw][0] 197 | popt[kw], pcov[kw] = curve_fit( 198 | exponential_func, 199 | times[:cutoff_time], 200 | acf_avg_norm[kw][:cutoff_time], 201 | p0=(1, 1e-4, 0), 202 | ) 203 | tau[kw] = 1 / popt[kw][1] # ps 204 | 205 | # Plot ACFs 206 | colors = ["b", "g", "r", "c", "m", "y"] 207 | line_styles = ["-", "--", "-.", ":"] 208 | for i, kw in enumerate(species_list): 209 | plt.plot(times, acf_avg_norm[kw], label=kw, color=colors[i]) 210 | fitted_x = np.linspace(0, cutoff_time * time_step, cutoff_time) 211 | fitted_y = exponential_func(np.linspace(0, cutoff_time * time_step, cutoff_time), *popt[kw]) 212 | save_decay = np.vstack( 213 | ( 214 | times[:cutoff_time], 215 | acf_avg_norm[kw][:cutoff_time], 216 | fitted_x, 217 | fitted_y, 218 | ) 219 | ) 220 | if save_curve: 221 | if save_curve is True: 222 | np.savetxt(f"decay{i}.csv", save_decay.T, delimiter=",") 223 | elif os.path.exists(str(save_curve)): 224 | np.savetxt(str(save_curve) + f"decay{i}.csv", save_decay.T, delimiter=",") 225 | else: 226 | raise ValueError("Please specify a bool or a path in string.") 227 | plt.plot( 228 | fitted_x, 229 | fitted_y, 230 | line_styles[i], 231 | color="k", 232 | label=kw + " Fit", 233 | ) 234 | 235 | plt.xlabel("Time (ps)") 236 | plt.legend() 237 | plt.ylabel("Neighbor Auto-correlation Function") 238 | plt.ylim(0, 1) 239 | plt.xlim(0, cutoff_time * time_step) 240 | plt.show() 241 | 242 | return tau 243 | -------------------------------------------------------------------------------- /mdgo/templates/mae_cmd_assignbond.txt: -------------------------------------------------------------------------------- 1 | entryimport $file 2 | beginundoblock Assign Bond Orders 3 | pythonrunbuiltin fix_bond_orders.fix_bond_orders 4 | endundoblock 5 | assigncharges 6 | assignforcefieldtypes 7 | prefer savescratchproject=false 8 | entryexport format=xyz $xyz 9 | entryexport format=maestro $mae 10 | 11 | -------------------------------------------------------------------------------- /mdgo/templates/mae_cmd_noassignbond.txt: -------------------------------------------------------------------------------- 1 | entryimport $file 2 | assigncharges 3 | assignforcefieldtypes 4 | prefer savescratchproject=false 5 | entryexport format=xyz $xyz 6 | entryexport format=maestro $mae 7 | 8 | -------------------------------------------------------------------------------- /mdgo/util/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """The util package implements various utilities that are commonly used by various packages.""" 5 | 6 | from __future__ import annotations 7 | 8 | __author__ = "Tingzheng Hou" 9 | __version__ = "0.3.0" 10 | __maintainer__ = "Tingzheng Hou" 11 | __email__ = "tingzheng_hou@berkeley.edu" 12 | __date__ = "Jul 19, 2021" 13 | 14 | from typing import Final 15 | 16 | MM_of_Elements: Final[dict[str, float]] = { 17 | "H": 1.00794, 18 | "He": 4.002602, 19 | "Li": 6.941, 20 | "Be": 9.012182, 21 | "B": 10.811, 22 | "C": 12.0107, 23 | "N": 14.0067, 24 | "O": 15.9994, 25 | "F": 18.9984032, 26 | "Ne": 20.1797, 27 | "Na": 22.98976928, 28 | "Mg": 24.305, 29 | "Al": 26.9815386, 30 | "Si": 28.0855, 31 | "P": 30.973762, 32 | "S": 32.065, 33 | "Cl": 35.453, 34 | "Ar": 39.948, 35 | "K": 39.0983, 36 | "Ca": 40.078, 37 | "Sc": 44.955912, 38 | "Ti": 47.867, 39 | "V": 50.9415, 40 | "Cr": 51.9961, 41 | "Mn": 54.938045, 42 | "Fe": 55.845, 43 | "Co": 58.933195, 44 | "Ni": 58.6934, 45 | "Cu": 63.546, 46 | "Zn": 65.409, 47 | "Ga": 69.723, 48 | "Ge": 72.64, 49 | "As": 74.9216, 50 | "Se": 78.96, 51 | "Br": 79.904, 52 | "Kr": 83.798, 53 | "Rb": 85.4678, 54 | "Sr": 87.62, 55 | "Y": 88.90585, 56 | "Zr": 91.224, 57 | "Nb": 92.90638, 58 | "Mo": 95.94, 59 | "Tc": 98.9063, 60 | "Ru": 101.07, 61 | "Rh": 102.9055, 62 | "Pd": 106.42, 63 | "Ag": 107.8682, 64 | "Cd": 112.411, 65 | "In": 114.818, 66 | "Sn": 118.71, 67 | "Sb": 121.760, 68 | "Te": 127.6, 69 | "I": 126.90447, 70 | "Xe": 131.293, 71 | "Cs": 132.9054519, 72 | "Ba": 137.327, 73 | "La": 138.90547, 74 | "Ce": 140.116, 75 | "Pr": 140.90465, 76 | "Nd": 144.242, 77 | "Pm": 146.9151, 78 | "Sm": 150.36, 79 | "Eu": 151.964, 80 | "Gd": 157.25, 81 | "Tb": 158.92535, 82 | "Dy": 162.5, 83 | "Ho": 164.93032, 84 | "Er": 167.259, 85 | "Tm": 168.93421, 86 | "Yb": 173.04, 87 | "Lu": 174.967, 88 | "Hf": 178.49, 89 | "Ta": 180.9479, 90 | "W": 183.84, 91 | "Re": 186.207, 92 | "Os": 190.23, 93 | "Ir": 192.217, 94 | "Pt": 195.084, 95 | "Au": 196.966569, 96 | "Hg": 200.59, 97 | "Tl": 204.3833, 98 | "Pb": 207.2, 99 | "Bi": 208.9804, 100 | "Po": 208.9824, 101 | "At": 209.9871, 102 | "Rn": 222.0176, 103 | "Fr": 223.0197, 104 | "Ra": 226.0254, 105 | "Ac": 227.0278, 106 | "Th": 232.03806, 107 | "Pa": 231.03588, 108 | "U": 238.02891, 109 | "Np": 237.0482, 110 | "Pu": 244.0642, 111 | "Am": 243.0614, 112 | "Cm": 247.0703, 113 | "Bk": 247.0703, 114 | "Cf": 251.0796, 115 | "Es": 252.0829, 116 | "Fm": 257.0951, 117 | "Md": 258.0951, 118 | "No": 259.1009, 119 | "Lr": 262, 120 | "Rf": 267, 121 | "Db": 268, 122 | "Sg": 271, 123 | "Bh": 270, 124 | "Hs": 269, 125 | "Mt": 278, 126 | "Ds": 281, 127 | "Rg": 281, 128 | "Cn": 285, 129 | "Nh": 284, 130 | "Fl": 289, 131 | "Mc": 289, 132 | "Lv": 292, 133 | "Ts": 294, 134 | "Og": 294, 135 | "ZERO": 0, 136 | } 137 | -------------------------------------------------------------------------------- /mdgo/util/coord.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """Utilities for manipulating coordinates under periodic boundary conditions.""" 5 | 6 | from __future__ import annotations 7 | 8 | from typing import TYPE_CHECKING 9 | 10 | import numpy as np 11 | 12 | if TYPE_CHECKING: 13 | from MDAnalysis.core.groups import Atom 14 | 15 | 16 | def atom_vec(atom1: Atom, atom2: Atom, dimension: np.ndarray) -> np.ndarray: 17 | """ 18 | Calculate the vector of the positions from atom2 to atom1. 19 | 20 | Args: 21 | atom1: Atom obj 1. 22 | atom2: Atom obj 2. 23 | dimension: box dimension. 24 | 25 | Return: 26 | The obtained vector 27 | """ 28 | vec = [0, 0, 0] 29 | for i in range(3): 30 | diff = atom1.position[i] - atom2.position[i] 31 | if diff > dimension[i] / 2: 32 | vec[i] = diff - dimension[i] 33 | elif diff < -dimension[i] / 2: 34 | vec[i] = diff + dimension[i] 35 | else: 36 | vec[i] = diff 37 | return np.array(vec) 38 | 39 | 40 | def position_vec( 41 | pos1: list[float] | np.ndarray, 42 | pos2: list[float] | np.ndarray, 43 | dimension: list[float] | np.ndarray, 44 | ) -> np.ndarray: 45 | """ 46 | Calculate the vector from pos2 to pos2. 47 | 48 | Args: 49 | pos1: Array of 3d coordinates 1. 50 | pos2: Array of 3d coordinates 2. 51 | dimension: box dimension. 52 | 53 | Return: 54 | The obtained vector. 55 | """ 56 | vec: list[int | float | np.floating] = [0, 0, 0] 57 | for i in range(3): 58 | diff = pos1[i] - pos2[i] 59 | if diff > dimension[i] / 2: 60 | vec[i] = diff - dimension[i] 61 | elif diff < -dimension[i] / 2: 62 | vec[i] = diff + dimension[i] 63 | else: 64 | vec[i] = diff 65 | return np.array(vec) 66 | 67 | 68 | def angle(a: np.ndarray, b: np.ndarray, c: np.ndarray) -> np.floating: 69 | """ 70 | Calculate the angle between three atoms. 71 | 72 | Args: 73 | a: Coordinates of atom A. 74 | b: Coordinates of atom B. 75 | c: Coordinates of atom C. 76 | 77 | Returns: 78 | The degree A-B-C. 79 | """ 80 | ba = a - b 81 | bc = c - b 82 | cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) 83 | cosine_angle = np.clip(cosine_angle, -1.0, 1.0) 84 | angle_in_radian = np.arccos(cosine_angle) 85 | return np.degrees(angle_in_radian) 86 | -------------------------------------------------------------------------------- /mdgo/util/num.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """Utilities for manipulating numbers in data structures.""" 5 | 6 | from __future__ import annotations 7 | 8 | 9 | def strip_zeros(items: list[str | float | int] | str) -> list[int] | None: 10 | """ 11 | Strip the trailing zeros of a sequence. 12 | 13 | Args: 14 | items: The sequence. 15 | 16 | Return: 17 | A new list of numbers. 18 | """ 19 | new_items = [int(i) for i in items] 20 | while new_items[-1] == 0: 21 | new_items.pop() 22 | while new_items[0] == 0: 23 | new_items.pop(0) 24 | if len(new_items) == 0: 25 | return None 26 | return new_items 27 | -------------------------------------------------------------------------------- /mdgo/util/packmol.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """ 5 | This module implements a core class PackmolWrapper for packing molecules 6 | into a single box. 7 | 8 | You need the Packmol package to run the code, see 9 | http://m3g.iqm.unicamp.br/packmol or 10 | http://leandro.iqm.unicamp.br/m3g/packmol/home.shtml 11 | for download and setup instructions. You may need to manually 12 | set the folder of the packmol executable to the PATH environment variable. 13 | """ 14 | 15 | from __future__ import annotations 16 | 17 | import os 18 | import subprocess 19 | from pathlib import Path 20 | from shutil import which 21 | 22 | from pymatgen.core import Molecule 23 | 24 | # from pymatgen.io.core import InputFile, InputSet, InputGenerator 25 | from mdgo.util.volume import molecular_volume 26 | 27 | __author__ = "Tingzheng Hou, Ryan Kingsbury" 28 | __version__ = "1.0" 29 | __maintainer__ = "Tingzheng Hou" 30 | __email__ = "tingzheng_hou@berkeley.edu" 31 | __date__ = "Feb - Oct, 2021" 32 | 33 | 34 | # TODO - inherit from InputSet 35 | # consider adding a generator 36 | class PackmolWrapper: 37 | """ 38 | Wrapper for the Packmol software that can be used to pack various types of 39 | molecules into a one single unit. 40 | 41 | Examples: 42 | >>> molecules = [{"name": "EMC", 43 | "number": 2, 44 | "coords": "/Users/th/Downloads/test_selenium/EMC.lmp.xyz"}] 45 | >>> pw = PackmolWrapper("/path/to/work/dir", 46 | ... molecules, 47 | ... [0., 0., 0., 10., 10., 10.] 48 | ... ) 49 | >>> pw.make_packmol_input() 50 | >>> pw.run_packmol() 51 | """ 52 | 53 | def __init__( 54 | self, 55 | path: str, 56 | molecules: list[dict], 57 | box: list[float] | None = None, 58 | tolerance: float = 2.0, 59 | seed: int = 1, 60 | control_params: dict | None = None, 61 | inputfile: str | Path = "packmol.inp", 62 | outputfile: str | Path = "packmol_out.xyz", 63 | ): 64 | """ 65 | Args: 66 | path: The path to the directory for file i/o. Note that the path 67 | cannot contain any spaces. 68 | molecules: A list of dict containing information about molecules to pack 69 | into the box. Each dict requires three keys: 70 | 1. "name" - the structure name 71 | 2. "number" - the number of that molecule to pack into the box 72 | 3. "coords" - Coordinates in the form of either a Molecule object or 73 | a path to a file. 74 | For Example, 75 | {"name": "water", 76 | "number": 500, 77 | "coords": "/path/to/input/file.xyz"} 78 | box: A list of box dimensions xlo, ylo, zlo, xhi, yhi, zhi, in Å. If set to None 79 | (default), mdgo will estimate the required box size based on the volumes of 80 | the provided molecules using mdgo.volume.molecular_volume() 81 | tolerance: Tolerance for packmol, in Å. 82 | seed: Random seed for packmol. Use a value of 1 (default) for deterministic 83 | output, or -1 to generate a new random seed from the current time. 84 | control_params: Specify custom control parapeters, e,g, "maxit" and "nloop", in a dict 85 | inputfile: Path to the input file. Default to 'packmol.inp'. 86 | outputfile: Path to the output file. Default to 'output.xyz'. 87 | """ 88 | self.path = path 89 | self.input = os.path.join(self.path, inputfile) 90 | self.output = os.path.join(self.path, outputfile) 91 | self.screen = os.path.join(self.path, "packmol.stdout") 92 | self.molecules = molecules 93 | self.control_params = control_params if control_params else {} 94 | self.box = box 95 | self.tolerance = tolerance 96 | self.seed = seed 97 | 98 | def run_packmol(self, timeout=30): 99 | """Run packmol and write out the packed structure. 100 | 101 | Args: 102 | timeout: Timeout in seconds. 103 | 104 | Raises: 105 | ValueError if packmol does not succeed in packing the box. 106 | TimeoutExpiredError if packmold does not finish within the timeout. 107 | """ 108 | if not which("packmol"): 109 | raise RuntimeError( 110 | "PackmolWrapper requires the executable 'packmol' to be in " 111 | "the path. Please download packmol from " 112 | "https://github.com/leandromartinez98/packmol " 113 | "and follow the instructions in the README to compile. " 114 | "Don't forget to add the packmol binary to your path" 115 | ) 116 | try: 117 | p = subprocess.run( 118 | f"packmol < '{self.input}'", 119 | check=True, 120 | shell=True, 121 | timeout=timeout, 122 | capture_output=True, 123 | ) 124 | # this workaround is needed because packmol can fail to find 125 | # a solution but still return a zero exit code 126 | # see https://github.com/m3g/packmol/issues/28 127 | if "ERROR" in p.stdout.decode(): 128 | msg = p.stdout.decode().split("ERROR")[-1] 129 | if "Could not open file." in p.stdout.decode(): 130 | raise ValueError( 131 | "Your packmol might be too old to handle paths with spaces." 132 | "Please try again with a newer version or use paths without spaces." 133 | f"Packmol failed with return code 0 and stdout: {msg}" 134 | ) 135 | raise ValueError(f"Packmol failed with return code 0 and stdout: {msg}") 136 | except subprocess.CalledProcessError as e: 137 | raise ValueError(f"Packmol failed with errorcode {e.returncode} and stderr: {e.stderr}") from e 138 | else: 139 | with open(self.screen, "w") as out: 140 | out.write(p.stdout.decode()) 141 | 142 | # TODO - should be in InputSet.get_inputs() or Inputfile.get_string() 143 | # maybe don't define an Inputfile class, just keep internal to 144 | # InputSet 145 | def make_packmol_input(self): 146 | """Make a Packmol usable input file.""" 147 | if self.box: 148 | box_list = " ".join(str(i) for i in self.box) 149 | else: 150 | # estimate the total volume of all molecules 151 | net_volume = 0.0 152 | for _idx, d in enumerate(self.molecules): 153 | mol = Molecule.from_file(d["coords"]) if not isinstance(d["coords"], Molecule) else d["coords"] 154 | # molecular volume in cubic Å 155 | vol = molecular_volume(mol, radii_type="pymatgen", molar_volume=False) 156 | # pad the calculated length by an amount related to the tolerance parameter 157 | # the amount to add was determined arbitrarily 158 | vol *= self.tolerance 159 | net_volume += vol * d["number"] 160 | 161 | box_length = net_volume ** (1.0 / 3.0) 162 | print(f"Auto determined box size is {box_length:.1f} Å per side.") 163 | box_list = f"0.0 0.0 0.0 {box_length:.1f} {box_length:.1f} {box_length:.1f}" 164 | 165 | with open(self.input, "w") as out: 166 | out.write("# " + " + ".join(str(d["number"]) + " " + d["name"] for d in self.molecules) + "\n") 167 | out.write("# Packmol input generated by mdgo.\n") 168 | for k, v in self.control_params.items(): 169 | if isinstance(v, list): 170 | out.write(f'{k} {" ".join(str(x) for x in v)}\n') 171 | else: 172 | out.write(f"{k} {v!s}\n") 173 | out.write(f"seed {self.seed}\n") 174 | out.write(f"tolerance {self.tolerance}\n\n") 175 | 176 | out.write("filetype xyz\n\n") 177 | if " " in str(self.output): 178 | out.write(f'output "{self.output}"\n\n') 179 | else: 180 | out.write(f"output {self.output}\n\n") 181 | 182 | for _i, d in enumerate(self.molecules): 183 | if isinstance(d["coords"], str): 184 | if " " in d["coords"]: 185 | out.write(f'structure "{d["coords"]}"\n') 186 | else: 187 | out.write(f'structure {d["coords"]}\n') 188 | elif isinstance(d["coords"], Path): 189 | if " " in str(d["coords"]): 190 | out.write(f'structure "{d["coords"]!s}"\n') 191 | else: 192 | out.write(f'structure {d["coords"]!s}\n') 193 | elif isinstance(d["coords"], Molecule): 194 | fname = os.path.join(self.path, f'packmol_{d["name"]}.xyz') 195 | d["coords"].to(filename=fname) 196 | if " " in str(fname): 197 | out.write(f'structure "{fname}"\n') 198 | else: 199 | out.write(f"structure {fname}\n") 200 | out.write(f' number {d["number"]!s}\n') 201 | out.write(f" inside box {box_list}\n") 202 | out.write("end structure\n\n") 203 | 204 | 205 | if __name__ == "__main__": 206 | """ 207 | molecules = [{"name": "EMC", 208 | "number": 2, 209 | "coords": "/Users/th/Downloads/test_selenium/EMC.lmp.xyz"}] 210 | pw = PackmolWrapper("/Users/th/Downloads/test_selenium/", molecules, 211 | [0., 0., 0., 10., 10., 10.]) 212 | pw.make_packmol_input() 213 | pw.run_packmol() 214 | """ 215 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=65.0.0", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | 7 | [tool.ruff] 8 | target-version = "py39" 9 | line-length = 120 10 | lint.select = [ 11 | "B", # flake8-bugbear 12 | "C4", # flake8-comprehensions 13 | "D", # pydocstyle 14 | "E", # pycodestyle error 15 | "EXE", # flake8-executable 16 | "F", # pyflakes 17 | "FA", # flake8-future-annotations 18 | "FBT003", # boolean-positional-value-in-call 19 | "FLY", # flynt 20 | "I", # isort 21 | "ICN", # flake8-import-conventions 22 | "ISC", # flake8-implicit-str-concat 23 | "PD", # pandas-vet 24 | "PERF", # perflint 25 | "PIE", # flake8-pie 26 | "PL", # pylint 27 | "PT", # flake8-pytest-style 28 | "PYI", # flakes8-pyi 29 | "Q", # flake8-quotes 30 | "RET", # flake8-return 31 | "RSE", # flake8-raise 32 | "RUF", # Ruff-specific rules 33 | "SIM", # flake8-simplify 34 | "SLOT", # flake8-slots 35 | "TCH", # flake8-type-checking 36 | "TID", # tidy imports 37 | "TID", # flake8-tidy-imports 38 | "UP", # pyupgrade 39 | "W", # pycodestyle warning 40 | "YTT", # flake8-2020 41 | ] 42 | lint.ignore = [ 43 | "B023", # Function definition does not bind loop variable 44 | "B028", # No explicit stacklevel keyword argument found 45 | "B904", # Within an except clause, raise exceptions with ... 46 | "C408", # unnecessary-collection-call 47 | "COM812", # missing trailing comma 48 | "D105", # Missing docstring in magic method 49 | "D205", # 1 blank line required between summary line and description 50 | "D212", # Multi-line docstring summary should start at the first line 51 | "NPY002", # replace legacy numpy.random with numpy.random.Generator 52 | "PD901", # pandas-df-variable-name 53 | "PERF203", # try-except-in-loop 54 | "PERF401", # manual-list-comprehension (TODO fix these or wait for autofix) 55 | "PLC1901", # can be simplified to ... as empty is falsey 56 | "PLR", # pylint refactor 57 | "PLW1514", # open() without explicit encoding argument 58 | "PLW2901", # Outer for loop variable overwritten by inner assignment target 59 | "PT013", # pytest-incorrect-pytest-import 60 | "PTH", # prefer pathlib to os.path 61 | "PYI024", # collections-named-tuple (TODO should maybe fix these) 62 | "RUF012", # Disable checks for mutable class args. This is a non-problem. 63 | "SIM105", # Use contextlib.suppress(OSError) instead of try-except-pass 64 | ] 65 | lint.pydocstyle.convention = "google" 66 | lint.isort.required-imports = ["from __future__ import annotations"] 67 | lint.isort.split-on-trailing-comma = false 68 | 69 | [tool.ruff.lint.per-file-ignores] 70 | "__init__.py" = ["F401"] 71 | "tests/**" = ["ANN201", "D", "S101"] 72 | "tasks.py" = ["D"] 73 | "pymatgen/analysis/*" = ["D"] 74 | "pymatgen/vis/*" = ["D"] 75 | "pymatgen/io/*" = ["D"] 76 | "dev_scripts/*" = ["D"] 77 | 78 | [tool.pytest.ini_options] 79 | addopts = "--durations=30 --quiet -r xXs --color=yes -p no:warnings --import-mode=importlib" 80 | 81 | [tool.coverage.run] 82 | parallel = true 83 | 84 | [tool.coverage.report] 85 | exclude_also = [ 86 | "@deprecated", 87 | "@np.deprecate", 88 | "def __repr__", 89 | "except ImportError:", 90 | "if 0:", 91 | "if TYPE_CHECKING:", 92 | "if __name__ == .__main__.:", 93 | "if self.debug:", 94 | "if settings.DEBUG", 95 | "if typing.TYPE_CHECKING:", 96 | "pragma: no cover", 97 | "raise AssertionError", 98 | "raise NotImplementedError", 99 | "show_plot", 100 | ] 101 | 102 | [tool.mypy] 103 | ignore_missing_imports = true 104 | namespace_packages = true 105 | explicit_package_bases = true 106 | no_implicit_optional = false 107 | disable_error_code = "annotation-unchecked" 108 | 109 | [[tool.mypy.overrides]] 110 | module = ["requests.*", "tabulate.*"] 111 | ignore_missing_imports = true 112 | 113 | [tool.codespell] 114 | ignore-words-list = """ 115 | titel,alls,ans,nd,mater,nwo,te,hart,ontop,ist,ot,fo,nax,coo,coul,ser,leary,thre,fase, 116 | rute,reson,titels,ges,scalr,strat,struc,hda,nin,ons,pres,kno,loos,lamda,lew,atomate 117 | """ 118 | check-filenames = true -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | doc2dash 3 | sphinx_rtd_theme 4 | sphinx-autodoc-typehints 5 | invoke 6 | pre-commit 7 | pytest 8 | pytest-cov 9 | pytest-split 10 | mypy 11 | ruff -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | MDAnalysis>=2.2.0 3 | numpy>=1.16.0 4 | pandas 5 | monty 6 | pubchempy 7 | pymatgen 8 | scipy 9 | selenium 10 | setuptools 11 | sphinx 12 | sphinx_rtd_theme 13 | sphinx-autodoc-typehints 14 | statsmodels 15 | tqdm 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [isort] 2 | profile=black 3 | known_first_party=mdgo 4 | 5 | [tool:pytest] 6 | addopts = -x --durations=30 --quiet -rxXs --color=yes 7 | filterwarnings = 8 | ignore::UserWarning 9 | ignore::FutureWarning 10 | ignore::RuntimeWarning 11 | ignore::DeprecationWarning 12 | ignore::PendingDeprecationWarning 13 | 14 | [pycodestyle] 15 | count = True 16 | ignore = E121,E123,E126,E133,E226,E241,E242,E704,W503,W504,W505,E741,W605,W293,W291,W292,E203,E231 17 | max-line-length = 120 18 | statistics = True 19 | exclude=docs_rst/*.py 20 | 21 | [flake8] 22 | exclude = .git,__pycache__,docs_rst/conf.py,tests 23 | # max-complexity = 10 24 | extend-ignore = E741,W291,W293,E501,E231,E203 25 | max-line-length = 120 26 | per-file-ignores = 27 | # F401: imported but unused 28 | __init__.py: F401 29 | 30 | [pydocstyle] 31 | ignore = D103,D105,D2,D4 32 | match-dir=(?!(tests)).* 33 | 34 | [mypy] 35 | ignore_missing_imports = True 36 | namespace_packages = True 37 | explicit_package_bases = True 38 | 39 | [mypy-tabulate.*] 40 | ignore_missing_imports = True 41 | 42 | [mypy-requests.*] 43 | ignore_missing_imports = True -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Tingzheng Hou. 2 | # Distributed under the terms of the MIT License. 3 | 4 | """Setup.py for MDGO.""" 5 | 6 | from __future__ import annotations 7 | 8 | import os 9 | 10 | from setuptools import find_packages, setup 11 | 12 | module_dir = os.path.dirname(os.path.abspath(__file__)) 13 | 14 | with open(os.path.join(module_dir, "README.md")) as f: 15 | readme = f.read() 16 | 17 | INSTALL_REQUIRES = [ 18 | "numpy>=1.16.0", 19 | "pandas", 20 | "matplotlib", 21 | "scipy", 22 | "tqdm", 23 | "pymatgen>=2022.7.8", 24 | "statsmodels", 25 | "pubchempy", 26 | "MDAnalysis>=2.2.0", 27 | "selenium", 28 | ] 29 | 30 | on_rtd = os.environ.get("READTHEDOCS") == "True" 31 | if on_rtd: 32 | INSTALL_REQUIRES = [] 33 | 34 | if __name__ == "__main__": 35 | setup( 36 | name="mdgo", 37 | version="0.3.1", 38 | description="A codebase for MD simulation setup and results analysis.", 39 | long_description=readme, 40 | long_description_content_type="text/markdown", 41 | license="MIT", 42 | author="mdgo development team", 43 | author_email="tingzheng_hou@berkeley.edu", 44 | maintainer="Tingzheng Hou", 45 | maintainer_email="tingzheng_hou@berkeley.edu", 46 | url="https://github.com/HT-MD/mdgo", 47 | keywords=[ 48 | "LAMMPS", 49 | "Gromacs", 50 | "Molecular dynamics", 51 | "liquid", 52 | "charge", 53 | "materials", 54 | "science", 55 | "solvation", 56 | "diffusion", 57 | "transport", 58 | "conductivity", 59 | "force field", 60 | ], 61 | classifiers=[ 62 | "Programming Language :: Python :: 3", 63 | "Programming Language :: Python :: 3.8", 64 | "Programming Language :: Python :: 3.9", 65 | "Programming Language :: Python :: 3.10", 66 | "Development Status :: 3 - Alpha", 67 | "Intended Audience :: Science/Research", 68 | "License :: OSI Approved :: MIT License", 69 | "Operating System :: OS Independent", 70 | "Topic :: Scientific/Engineering :: Information Analysis", 71 | "Topic :: Scientific/Engineering :: Physics", 72 | "Topic :: Scientific/Engineering :: Chemistry", 73 | "Topic :: Software Development :: Libraries :: Python Modules", 74 | ], 75 | packages=find_packages(), 76 | install_requires=INSTALL_REQUIRES, 77 | extras_require={ 78 | "web": [ 79 | "sphinx", 80 | "sphinx_rtd_theme", 81 | "sphinx-autodoc-typehints", 82 | ], 83 | }, 84 | python_requires=">=3.8", 85 | ) 86 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pyinvoke tasks.py file for automating releases and admin stuff. 3 | 4 | To cut a new mdgo release, use `invoke update-changelog` followed by `invoke release`. 5 | 6 | Author: Tingzheng Hou 7 | """ 8 | from __future__ import annotations 9 | 10 | import glob 11 | import json 12 | import os 13 | import re 14 | import subprocess 15 | import webbrowser 16 | 17 | import requests 18 | from invoke import task 19 | from monty.os import cd 20 | 21 | from mdgo import __version__ as CURRENT_VER 22 | 23 | 24 | @task 25 | def make_doc(ctx): 26 | """ 27 | Generate API documentation + run Sphinx. 28 | :param ctx: 29 | """ 30 | with open("CHANGES.rst") as f: 31 | contents = f.read() 32 | 33 | toks = re.split(r"\-{3,}", contents) 34 | n = len(toks[0].split()[-1]) 35 | changes = [toks[0]] 36 | changes.append("\n" + "\n".join(toks[1].strip().split("\n")[0:-1])) 37 | changes = ("-" * n).join(changes) 38 | 39 | with open("docs/source/latest_changes.rst", "w") as f: 40 | f.write(changes) 41 | 42 | with cd("docs/source"): 43 | ctx.run("cp ../CHANGES.rst change_log.rst") 44 | ctx.run("rm mdgo.*.rst", warn=True) 45 | ctx.run("sphinx-apidoc --implicit-namespaces --separate -d 7 -o . -f ../mdgo") 46 | ctx.run("rm *.tests.*rst") 47 | for f in glob.glob("*.rst"): 48 | if f.startswith("mdgo") and f.endswith("rst"): 49 | newoutput = [] 50 | suboutput = [] 51 | subpackage = False 52 | with open(f) as fid: 53 | for line in fid: 54 | clean = line.strip() 55 | if clean == "Subpackages": 56 | subpackage = True 57 | if not subpackage and not clean.endswith("tests"): 58 | newoutput.append(line) 59 | else: 60 | if not clean.endswith("tests"): 61 | suboutput.append(line) 62 | if clean.startswith("mdgo") and not clean.endswith("tests"): 63 | newoutput.extend(suboutput) 64 | subpackage = False 65 | suboutput = [] 66 | 67 | with open(f, "w") as fid: 68 | fid.write("".join(newoutput)) 69 | ctx.run("make html") 70 | 71 | ctx.run("cp _static/* ../docs/html/_static", warn=True) 72 | 73 | with cd("docs"): 74 | ctx.run("rm *.html", warn=True) 75 | ctx.run("cp -r html/* .", warn=True) 76 | ctx.run("rm -r html", warn=True) 77 | ctx.run("rm -r doctrees", warn=True) 78 | ctx.run("rm -r _sources", warn=True) 79 | ctx.run("rm -r _build", warn=True) 80 | 81 | # This makes sure mdgo.readthedocs.io works to redirect to the Github page 82 | ctx.run('echo "mdgo.readthedocs.io" > CNAME') 83 | # Avoid the use of jekyll so that _dir works as intended. 84 | ctx.run("touch .nojekyll") 85 | 86 | 87 | @task 88 | def update_doc(ctx): 89 | """ 90 | Update the web documentation. 91 | :param ctx: 92 | """ 93 | ctx.run("cp docs_rst/conf-normal.py docs_rst/conf.py") 94 | make_doc(ctx) 95 | ctx.run("git add .") 96 | commit(ctx, "Update docs") 97 | 98 | 99 | @task 100 | def publish(ctx): 101 | """ 102 | Upload release to Pypi using twine. 103 | :param ctx: 104 | """ 105 | ctx.run("rm dist/*.*", warn=True) 106 | ctx.run("python setup.py sdist bdist_wheel") 107 | ctx.run("twine upload dist/*") 108 | 109 | 110 | @task 111 | def set_ver(ctx, version): 112 | with open("mdgo/__init__.py") as f: 113 | contents = f.read() 114 | contents = re.sub(r"__version__ = .*\n", '__version__ = "%s"\n' % version, contents) 115 | 116 | with open("mdgo/__init__.py", "w") as f: 117 | f.write(contents) 118 | 119 | with open("mdgo/core/__init__.py") as f: 120 | contents = f.read() 121 | contents = re.sub(r"__version__ = .*\n", '__version__ = "%s"\n' % version, contents) 122 | 123 | with open("mdgo/core/__init__.py", "w") as f: 124 | f.write(contents) 125 | 126 | with open("mdgo/forcefield/__init__.py") as f: 127 | contents = f.read() 128 | contents = re.sub(r"__version__ = .*\n", '__version__ = "%s"\n' % version, contents) 129 | 130 | with open("mdgo/forcefield/__init__.py", "w") as f: 131 | f.write(contents) 132 | 133 | with open("mdgo/util/__init__.py") as f: 134 | contents = f.read() 135 | contents = re.sub(r"__version__ = .*\n", '__version__ = "%s"\n' % version, contents) 136 | 137 | with open("mdgo/util/__init__.py", "w") as f: 138 | f.write(contents) 139 | 140 | with open("setup.py") as f: 141 | contents = f.read() 142 | contents = re.sub(r"version=([^,]+),", 'version="%s",' % version, contents) 143 | 144 | with open("setup.py", "w") as f: 145 | f.write(contents) 146 | 147 | 148 | @task 149 | def release_github(ctx, version): 150 | """ 151 | Release to Github using Github API. 152 | :param ctx: 153 | """ 154 | with open("CHANGES.rst") as f: 155 | contents = f.read() 156 | toks = re.split(r"\-+", contents) 157 | desc = toks[1].strip() 158 | toks = desc.split("\n") 159 | desc = "\n".join(toks[:-1]).strip() 160 | payload = { 161 | "tag_name": "v" + version, 162 | "target_commitish": "main", 163 | "name": "v" + version, 164 | "body": desc, 165 | "draft": False, 166 | "prerelease": False, 167 | } 168 | response = requests.post( 169 | "https://api.github.com/repos/HT-MD/mdgo/releases", 170 | data=json.dumps(payload), 171 | headers={"Authorization": "token " + os.environ["GITHUB_RELEASES_TOKEN"]}, 172 | ) 173 | print(response.text) 174 | 175 | 176 | @task 177 | def update_changelog(ctx, version, sim=False): 178 | """ 179 | Create a preliminary change log using the git logs. 180 | :param ctx: 181 | """ 182 | output = subprocess.check_output(["git", "log", "--pretty=format:%s", "v%s..HEAD" % CURRENT_VER]) 183 | lines = [] 184 | misc = [] 185 | for line in output.decode("utf-8").strip().split("\n"): 186 | m = re.match(r"Merge pull request \#(\d+) from (.*)", line) 187 | if m: 188 | pr_number = m.group(1) 189 | contrib, pr_name = m.group(2).split("/", 1) 190 | response = requests.get(f"https://api.github.com/repos/HT-MD/mdgo/pulls/{pr_number}") 191 | lines.append(f"* PR #{pr_number} from @{contrib} {pr_name}") 192 | if "body" in response.json(): 193 | for ll in response.json()["body"].split("\n"): 194 | ll = ll.strip() 195 | if ll in ["", "## Summary"]: 196 | continue 197 | if ll.startswith(("## Checklist", "## TODO")): 198 | break 199 | lines.append(f" {ll}") 200 | misc.append(line) 201 | with open("CHANGES.rst") as f: 202 | contents = f.read() 203 | line = "==========" 204 | toks = contents.split(line) 205 | head = "\n\nv%s\n" % version + "-" * (len(version) + 1) + "\n" 206 | toks.insert(-1, head + "\n".join(lines)) 207 | if not sim: 208 | with open("CHANGES.rst", "w") as f: 209 | f.write(toks[0] + line + "".join(toks[1:])) 210 | ctx.run("open CHANGES.rst") 211 | else: 212 | print(toks[0] + line + "".join(toks[1:])) 213 | print("The following commit messages were not included...") 214 | print("\n".join(misc)) 215 | 216 | 217 | @task 218 | def release(ctx, version, nodoc=False): 219 | """ 220 | Run full sequence for releasing mdgo. 221 | :param ctx: 222 | :param nodoc: Whether to skip doc generation. 223 | """ 224 | ctx.run("rm -r dist build mdgo.egg-info", warn=True) 225 | set_ver(ctx, version) 226 | if not nodoc: 227 | make_doc(ctx) 228 | ctx.run("git add .") 229 | commit(ctx, "Update docs") 230 | release_github(ctx, version) 231 | 232 | 233 | @task 234 | def commit(ctx, message): 235 | ctx.run(f'git commit -a -m "{message}"', warn=True) 236 | ctx.run(f'git push https://{os.environ["GITHUB_RELEASES_TOKEN"]}@github.com/HT-MD/mdgo.git', warn=True) 237 | 238 | 239 | @task 240 | def open_doc(ctx): 241 | """ 242 | Open local documentation in web browser. 243 | :param ctx: 244 | """ 245 | pth = os.path.abspath("docs/_build/html/index.html") 246 | webbrowser.open("file://" + pth) 247 | 248 | 249 | @task 250 | def lint(ctx): 251 | for cmd in ["pycodestyle", "mypy", "flake8", "pydocstyle"]: 252 | ctx.run("%s mdgo" % cmd) 253 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/tests/__init__.py -------------------------------------------------------------------------------- /tests/packmol/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.mod 3 | *.o 4 | *.swp 5 | 6 | -------------------------------------------------------------------------------- /tests/packmol/AUTHORS: -------------------------------------------------------------------------------- 1 | L. Martinez, R. Andrade, E. G. Birgin, J. M. Martinez. Packmol: A 2 | package for building initial configurations for molecular dynamics 3 | simulations. Journal of Computational Chemistry, 30(13):2157-2164, 4 | 2009. 5 | 6 | J. M. Martinez and L. Martinez. Packing optimization for automated 7 | generation of complex system's initial configurations for molecular 8 | dynamics and docking. Journal of Computational Chemistry, 24(7):819-825, 9 | 2003. 10 | 11 | Home-Page: http://m3g.iqm.unicamp.br/packmol 12 | -------------------------------------------------------------------------------- /tests/packmol/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009-2018 Leandro Martínez, José Mario Martínez, Ernesto Birgin 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 | -------------------------------------------------------------------------------- /tests/packmol/README.md: -------------------------------------------------------------------------------- 1 | # Packmol 2 | 3 | Packmol - Creates Initial Configurations for Molecular Dynamics Simulations 4 | 5 | **https://m3g.github.io/packmol** 6 | 7 | ## What is Packmol 8 | 9 | Packmol creates an initial point for molecular dynamics simulations by packing molecules in defined regions of space. The packing guarantees that short range repulsive interactions do not disrupt the simulations. 10 | 11 | The great variety of types of spatial constraints that can be attributed to the molecules, or atoms within the molecules, makes it easy to create ordered systems, such as lamellar, spherical or tubular lipid layers. 12 | 13 | The user must provide only the coordinates of one molecule of each type, the number of molecules of each type and the spatial constraints that each type of molecule must satisfy. 14 | 15 | The package is compatible with input files of PDB, TINKER, XYZ and MOLDY formats. 16 | 17 | ## Usage 18 | 19 | User guide, examples, and tutorials, are available at: https://m3g.github.io/packmol 20 | 21 | ## Installation instructions 22 | 23 | ### Multi-platform package provider with Julia 24 | 25 | If you are not familiar with compiling packages, you may find it easier to get the Julia interface for 26 | `packmol`, which provides executables for all common platforms: https://github.com/m3g/Packmol.jl 27 | 28 | Installation of the Julia programming language and of the Julia `Packmol` package are necessary, but 29 | these follow simple instructions which are described in the link above. 30 | 31 | Compilation of the package, particularly on Linux platforms is, nevertheless, easy, following the instructions 32 | below. 33 | 34 | ### Downloading 35 | 36 | 1. Download the `.tar.gz` or `.zip` files of the latest version from: https://github.com/m3g/packmol/releases 37 | 38 | 2. Unpack the files, for example with: 39 | ```bash 40 | tar -xzvf packmol-20.13.0.tar.gz 41 | ``` 42 | or 43 | ```bash 44 | unzip -xzvf packmol-20.13.0.zip 45 | ``` 46 | substituting the `20.13.0` with the correct version number. 47 | 48 | ### Using `make` 49 | 50 | 3. Go into the `packmol` directory, and compile the package (we assume `gfortran` or other compiler is available): 51 | ```bash 52 | cd packmol 53 | ./configure [optional: path to fortran compiler] 54 | make 55 | ``` 56 | 57 | 4. An executable called `packmol` will be created in the main directory. Add that directory to your path. 58 | 59 | ### Using the Fortran Package Manager (`fpm`) 60 | 61 | 3. Install the Fortran Package Manager from: https://fpm.fortran-lang.org/en/install/index.html#install 62 | 63 | 4. Go into the `packmol` directory, and run: 64 | ```bash 65 | fpm install --profile release 66 | ``` 67 | this will compile and send the executable somewhere in your `PATH`. 68 | By default (on Linux systems) it will be `~/.local/bin`. Making it available 69 | as a `packmol` command anywhere in your computer. 70 | 71 | `fpm` will look for Fortran compilers automatically and will use `gfortran` 72 | as default. To use another compiler modify the environment variable 73 | `FPM_FC=compiler`, for example for `ifort`, use in bash, `export FPM_FC=ifort`. 74 | 75 | ## References 76 | 77 | Please always cite one of the following references in publications for which Packmol was useful: 78 | 79 | L Martinez, R Andrade, EG Birgin, JM Martinez, Packmol: A package for building initial configurations for molecular dynamics simulations. Journal of Computational Chemistry, 30, 2157-2164, 2009. (http://www3.interscience.wiley.com/journal/122210103/abstract) 80 | 81 | JM Martinez, L Martinez, Packing optimization for the automated generation of complex system's initial configurations for molecular dynamics and docking. Journal of Computational Chemistry, 24, 819-825, 2003. 82 | (http://www3.interscience.wiley.com/journal/104086246/abstract) 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/packmol/packmol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/tests/packmol/packmol -------------------------------------------------------------------------------- /tests/test_conductivity.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import unittest 5 | 6 | import MDAnalysis 7 | import numpy as np 8 | 9 | from mdgo.conductivity import calc_cond_msd, get_beta 10 | 11 | test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") 12 | 13 | 14 | class ConductivityTest(unittest.TestCase): 15 | @classmethod 16 | def setUpClass(cls) -> None: 17 | cls.gen2 = MDAnalysis.Universe( 18 | os.path.join(test_dir, "gen2_light", "gen2_mdgo.data"), 19 | os.path.join(test_dir, "gen2_light", "gen2_mdgo_unwrapped_nvt_main.dcd"), 20 | format="LAMMPS", 21 | ) 22 | cls.anions = cls.gen2.select_atoms("type 1") 23 | cls.cations = cls.gen2.select_atoms("type 3") 24 | cls.cond_array = calc_cond_msd(cls.gen2, cls.anions, cls.cations, 100, 1, -1) 25 | cls.time_array = np.array([i * 10 for i in range(cls.gen2.trajectory.n_frames - 100)]) 26 | 27 | def test_calc_cond_msd(self): 28 | assert self.cond_array[0] == -2.9103830456733704e-11 29 | assert self.cond_array[1] == 112.66080481783138 30 | assert self.cond_array[-1] == 236007.76624833583 31 | 32 | def test_get_beta(self): 33 | assert get_beta(self.cond_array, self.time_array, 10, 100) == (0.8188201425517928, 0.2535110576154693) 34 | assert get_beta(self.cond_array, self.time_array, 1000, 2000) == (1.2525648107674503, 1.0120346984003845) 35 | assert get_beta(self.cond_array, self.time_array, 1500, 2500) == (1.4075552564189142, 1.3748981878979976) 36 | assert get_beta(self.cond_array, self.time_array, 2000, 4000) == (1.5021915651236932, 51.79451695748163) 37 | 38 | 39 | if __name__ == "__main__": 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /tests/test_coordination.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import unittest 4 | 5 | 6 | class MyTestCase(unittest.TestCase): 7 | def test_something(self): 8 | assert True is True 9 | 10 | 11 | if __name__ == "__main__": 12 | unittest.main() 13 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import unittest 4 | 5 | 6 | class MyTestCase(unittest.TestCase): 7 | def test_something(self): 8 | assert True is True 9 | 10 | 11 | if __name__ == "__main__": 12 | unittest.main() 13 | -------------------------------------------------------------------------------- /tests/test_files/C.lmp: -------------------------------------------------------------------------------- 1 | LAMMPS data file Created by LigParGen - (Written by Leela S. Dodda) 2 | 3 | 5 atoms 4 | 4 bonds 5 | 6 angles 6 | 0 dihedrals 7 | 2 impropers 8 | 9 | 5 atom types 10 | 4 bond types 11 | 6 angle types 12 | 0 dihedral types 13 | 2 improper types 14 | 15 | -0.092200 49.907800 xlo xhi 16 | 0.107450 50.107450 ylo yhi 17 | -1.030120 48.969880 zlo zhi 18 | 19 | Masses 20 | 21 | 1 12.011 22 | 2 1.008 23 | 3 1.008 24 | 4 1.008 25 | 5 1.008 26 | 27 | Pair Coeffs 28 | 29 | 1 0.066 3.5000000 30 | 2 0.030 2.5000000 31 | 3 0.030 2.5000000 32 | 4 0.030 2.5000000 33 | 5 0.030 2.5000000 34 | 35 | Bond Coeffs 36 | 37 | 1 340.0000 1.0900 38 | 2 340.0000 1.0900 39 | 3 340.0000 1.0900 40 | 4 340.0000 1.0900 41 | 42 | Angle Coeffs 43 | 44 | 1 33.000 107.800 45 | 2 33.000 107.800 46 | 3 33.000 107.800 47 | 4 33.000 107.800 48 | 5 33.000 107.800 49 | 6 33.000 107.800 50 | 51 | Dihedral Coeffs 52 | 53 | 54 | Improper Coeffs 55 | 56 | 1 0.000 -1 2 57 | 2 0.000 -1 2 58 | 59 | Atoms 60 | 61 | 1 1 1 -0.29940000 1.000 1.00000 0.00000 62 | 2 1 2 0.07480000 -0.092 1.00000 0.00000 63 | 3 1 3 0.07480000 1.363 1.00000 -1.03012 64 | 4 1 4 0.07480000 1.363 0.10745 0.51431 65 | 5 1 5 0.07480000 1.363 1.89253 0.51430 66 | 67 | Bonds 68 | 69 | 1 1 2 1 70 | 2 2 3 1 71 | 3 3 4 1 72 | 4 4 5 1 73 | 74 | Angles 75 | 76 | 1 1 2 1 3 77 | 2 2 2 1 4 78 | 3 3 2 1 5 79 | 4 4 3 1 4 80 | 5 5 4 1 5 81 | 6 6 3 1 5 82 | 83 | Dihedrals 84 | 85 | 86 | Impropers 87 | 88 | 1 1 1 2 3 4 89 | 2 2 1 2 3 5 90 | 91 | -------------------------------------------------------------------------------- /tests/test_files/CCOC(=O)OC.lmp: -------------------------------------------------------------------------------- 1 | LAMMPS data file Created by LigParGen - (Written by Leela S. Dodda) 2 | 3 | 15 atoms 4 | 14 bonds 5 | 23 angles 6 | 19 dihedrals 7 | 7 impropers 8 | 9 | 15 atom types 10 | 14 bond types 11 | 23 angle types 12 | 19 dihedral types 13 | 7 improper types 14 | 15 | -2.012410 47.987590 xlo xhi 16 | -1.850300 48.149700 ylo yhi 17 | -1.017950 48.982050 zlo zhi 18 | 19 | Masses 20 | 21 | 1 12.011 22 | 2 12.011 23 | 3 15.999 24 | 4 12.011 25 | 5 15.999 26 | 6 15.999 27 | 7 12.011 28 | 8 1.008 29 | 9 1.008 30 | 10 1.008 31 | 11 1.008 32 | 12 1.008 33 | 13 1.008 34 | 14 1.008 35 | 15 1.008 36 | 37 | Pair Coeffs 38 | 39 | 1 0.066 3.5000000 40 | 2 0.066 3.5000000 41 | 3 0.140 2.9000000 42 | 4 0.070 3.5500000 43 | 5 0.210 2.9600000 44 | 6 0.140 2.9000000 45 | 7 0.066 3.5000000 46 | 8 0.030 2.5000000 47 | 9 0.030 2.5000000 48 | 10 0.030 2.5000000 49 | 11 0.030 2.5000000 50 | 12 0.030 2.5000000 51 | 13 0.030 2.5000000 52 | 14 0.030 2.5000000 53 | 15 0.030 2.5000000 54 | 55 | Bond Coeffs 56 | 57 | 1 268.0000 1.5290 58 | 2 320.0000 1.4100 59 | 3 214.0000 1.3270 60 | 4 570.0000 1.2290 61 | 5 214.0000 1.3270 62 | 6 320.0000 1.4100 63 | 7 340.0000 1.0900 64 | 8 340.0000 1.0900 65 | 9 340.0000 1.0900 66 | 10 340.0000 1.0900 67 | 11 340.0000 1.0900 68 | 12 340.0000 1.0900 69 | 13 340.0000 1.0900 70 | 14 340.0000 1.0900 71 | 72 | Angle Coeffs 73 | 74 | 1 50.000 109.500 75 | 2 83.000 116.900 76 | 3 83.000 123.400 77 | 4 69.900 118.180 78 | 5 83.000 116.900 79 | 6 37.500 110.700 80 | 7 37.500 110.700 81 | 8 37.500 110.700 82 | 9 37.500 110.700 83 | 10 37.500 110.700 84 | 11 35.000 109.500 85 | 12 35.000 109.500 86 | 13 35.000 109.500 87 | 14 83.000 123.400 88 | 15 33.000 107.800 89 | 16 33.000 107.800 90 | 17 33.000 107.800 91 | 18 33.000 107.800 92 | 19 35.000 109.500 93 | 20 33.000 107.800 94 | 21 35.000 109.500 95 | 22 33.000 107.800 96 | 23 33.000 107.800 97 | 98 | Dihedral Coeffs 99 | 100 | 1 -1.220 -0.126 0.422 0.000 101 | 2 0.000 5.124 0.000 0.000 102 | 3 4.669 5.124 0.000 0.000 103 | 4 0.000 0.000 0.468 0.000 104 | 5 0.000 0.000 0.198 0.000 105 | 6 0.000 0.000 0.300 0.000 106 | 7 0.000 0.000 0.300 0.000 107 | 8 4.669 5.124 0.000 0.000 108 | 9 0.000 0.000 0.300 0.000 109 | 10 0.000 0.000 0.468 0.000 110 | 11 0.000 0.000 0.198 0.000 111 | 12 0.000 0.000 0.300 0.000 112 | 13 0.000 0.000 0.300 0.000 113 | 14 0.000 0.000 0.468 0.000 114 | 15 0.000 0.000 0.198 0.000 115 | 16 0.000 5.124 0.000 0.000 116 | 17 0.000 0.000 0.198 0.000 117 | 18 0.000 0.000 0.300 0.000 118 | 19 0.000 0.000 0.198 0.000 119 | 120 | Improper Coeffs 121 | 122 | 1 10.500 -1 2 123 | 2 0.000 -1 2 124 | 3 0.000 -1 2 125 | 4 0.000 -1 2 126 | 5 0.000 -1 2 127 | 6 0.000 -1 2 128 | 7 0.000 -1 2 129 | 130 | Atoms 131 | 132 | 1 1 1 -0.28070000 1.000 1.00000 0.00000 133 | 2 1 2 0.01710000 -0.520 1.00000 0.00000 134 | 3 1 3 -0.32310000 -0.985 1.00000 1.35431 135 | 4 1 4 0.57010000 -1.060 -0.22226 1.93502 136 | 5 1 5 -0.49590000 -0.937 -1.29627 1.36526 137 | 6 1 6 -0.31640000 -1.213 -0.04501 3.26928 138 | 7 1 7 -0.04510000 -1.103 -1.25137 4.02168 139 | 8 1 8 0.10110000 1.393 1.00129 -1.01795 140 | 9 1 9 0.10110000 1.391 0.12572 0.52971 141 | 10 1 10 0.10110000 1.376 1.88379 0.52868 142 | 11 1 11 0.12390000 -0.921 0.15139 -0.56659 143 | 12 1 12 0.12390000 -0.891 1.91359 -0.47627 144 | 13 1 13 0.10760000 -0.989 -0.98456 5.07574 145 | 14 1 14 0.10760000 -0.218 -1.82392 3.72415 146 | 15 1 15 0.10760000 -2.012 -1.85030 3.90293 147 | 148 | Bonds 149 | 150 | 1 1 2 1 151 | 2 2 3 2 152 | 3 3 4 3 153 | 4 4 5 4 154 | 5 5 6 4 155 | 6 6 7 6 156 | 7 7 8 1 157 | 8 8 9 1 158 | 9 9 10 1 159 | 10 10 11 2 160 | 11 11 12 2 161 | 12 12 13 7 162 | 13 13 14 7 163 | 14 14 15 7 164 | 165 | Angles 166 | 167 | 1 1 1 2 3 168 | 2 2 2 3 4 169 | 3 3 3 4 5 170 | 4 4 3 4 6 171 | 5 5 4 6 7 172 | 6 6 2 1 8 173 | 7 7 2 1 9 174 | 8 8 2 1 10 175 | 9 9 1 2 11 176 | 10 10 1 2 12 177 | 11 11 6 7 13 178 | 12 12 6 7 14 179 | 13 13 6 7 15 180 | 14 14 5 4 6 181 | 15 15 8 1 10 182 | 16 16 11 2 12 183 | 17 17 13 7 14 184 | 18 18 8 1 9 185 | 19 19 3 2 11 186 | 20 20 14 7 15 187 | 21 21 3 2 12 188 | 22 22 9 1 10 189 | 23 23 13 7 15 190 | 191 | Dihedrals 192 | 193 | 1 1 4 3 2 1 194 | 2 2 5 4 3 2 195 | 3 3 7 6 4 3 196 | 4 4 8 1 2 3 197 | 5 5 13 7 6 4 198 | 6 6 11 2 1 8 199 | 7 7 11 2 1 9 200 | 8 8 6 4 3 2 201 | 9 9 12 2 1 10 202 | 10 10 10 1 2 3 203 | 11 11 14 7 6 4 204 | 12 12 12 2 1 9 205 | 13 13 12 2 1 8 206 | 14 14 9 1 2 3 207 | 15 15 11 2 3 4 208 | 16 16 7 6 4 5 209 | 17 17 15 7 6 4 210 | 18 18 11 2 1 10 211 | 19 19 12 2 3 4 212 | 213 | Impropers 214 | 215 | 1 1 4 3 5 6 216 | 2 2 1 9 8 2 217 | 3 3 1 10 8 2 218 | 4 4 2 1 11 3 219 | 5 5 2 1 3 12 220 | 6 6 7 13 14 6 221 | 7 7 7 13 15 6 222 | 223 | -------------------------------------------------------------------------------- /tests/test_files/DEC.xyz: -------------------------------------------------------------------------------- 1 | 18 2 | 3 | H 1 1 0 4 | C -0.094 1 0 5 | C -0.633 1 1.41453 6 | H -0.447 0.11987 -0.5476 7 | H -0.448 1.87998 -0.5475 8 | O -2.064 0.99829 1.36735 9 | H -0.284 0.10521 1.94213 10 | H -0.287 1.89555 1.94216 11 | C -2.691 0.99654 2.57594 12 | O -4.035 0.99344 2.35302 13 | O -2.142 0.99632 3.66972 14 | C -4.855 0.98961 3.52597 15 | C -6.31 0.98471 3.10902 16 | H -4.637 0.09499 4.12004 17 | H -4.643 1.88407 4.12085 18 | H -6.532 0.10455 2.49647 19 | H -6.967 0.9804 3.98425 20 | H -6.538 1.86395 2.49704 -------------------------------------------------------------------------------- /tests/test_files/EC.xyz: -------------------------------------------------------------------------------- 1 | 10 2 | comment line 3 | C -0.369000 1.465000 0.099000 4 | O -0.283000 0.274000 0.149000 5 | O -1.490000 2.178000 0.407000 6 | O 0.638000 2.304000 -0.273000 7 | C -1.288000 3.563000 0.069000 8 | C 0.237000 3.669000 -0.049000 9 | H -1.713000 4.180000 0.868000 10 | H -1.803000 3.770000 -0.878000 11 | H 0.712000 4.025000 0.875000 12 | H 0.574000 4.273000 -0.898000 13 | -------------------------------------------------------------------------------- /tests/test_files/EMC.gro: -------------------------------------------------------------------------------- 1 | LIGPARGEN GENERATED GRO FILE 2 | 15 3 | 1UNK O00 1 -0.071 0.138 0.000 4 | 1UNK C01 2 -0.053 0.017 0.000 5 | 1UNK O02 3 0.068 -0.046 0.000 6 | 1UNK O03 4 -0.150 -0.078 0.000 7 | 1UNK C04 5 0.182 0.041 0.000 8 | 1UNK C05 6 -0.285 -0.029 -0.000 9 | 1UNK C06 7 0.308 -0.044 -0.000 10 | 1UNK H07 8 0.180 0.104 -0.090 11 | 1UNK H08 9 0.180 0.104 0.090 12 | 1UNK H09 10 0.397 0.019 -0.000 13 | 1UNK H0A 11 0.310 -0.109 0.088 14 | 1UNK H0B 12 0.310 -0.109 -0.088 15 | 1UNK H0C 13 -0.303 0.030 0.090 16 | 1UNK H0D 14 -0.352 -0.115 0.000 17 | 1UNK H0E 15 -0.303 0.030 -0.090 18 | 1.00000 1.00000 1.00000 19 | 20 | -------------------------------------------------------------------------------- /tests/test_files/EMC.itp: -------------------------------------------------------------------------------- 1 | 2 | ; 3 | ; GENERATED BY LigParGen Server 4 | ; Jorgensen Lab @ Yale University 5 | ; 6 | [ atomtypes ] 7 | opls_802 O802 15.9990 0.000 A 2.90000E-01 5.85760E-01 8 | opls_810 H810 1.0080 0.000 A 2.50000E-01 1.25520E-01 9 | opls_806 C806 12.0110 0.000 A 3.50000E-01 2.76144E-01 10 | opls_809 H809 1.0080 0.000 A 2.50000E-01 1.25520E-01 11 | opls_803 O803 15.9990 0.000 A 2.90000E-01 5.85760E-01 12 | opls_808 H808 1.0080 0.000 A 2.50000E-01 1.25520E-01 13 | opls_814 H814 1.0080 0.000 A 2.50000E-01 1.25520E-01 14 | opls_800 O800 15.9990 0.000 A 2.96000E-01 8.78640E-01 15 | opls_812 H812 1.0080 0.000 A 2.50000E-01 1.25520E-01 16 | opls_807 H807 1.0080 0.000 A 2.50000E-01 1.25520E-01 17 | opls_804 C804 12.0110 0.000 A 3.50000E-01 2.76144E-01 18 | opls_813 H813 1.0080 0.000 A 2.50000E-01 1.25520E-01 19 | opls_801 C801 12.0110 0.000 A 3.55000E-01 2.92880E-01 20 | opls_811 H811 1.0080 0.000 A 2.50000E-01 1.25520E-01 21 | opls_805 C805 12.0110 0.000 A 3.50000E-01 2.76144E-01 22 | [ moleculetype ] 23 | ; Name nrexcl 24 | UNK 3 25 | [ atoms ] 26 | ; nr type resnr residue atom cgnr charge mass 27 | 1 opls_800 1 UNK O00 1 -0.4944 15.9990 28 | 2 opls_801 1 UNK C01 1 0.5748 12.0110 29 | 3 opls_802 1 UNK O02 1 -0.3288 15.9990 30 | 4 opls_803 1 UNK O03 1 -0.3251 15.9990 31 | 5 opls_804 1 UNK C04 1 0.0233 12.0110 32 | 6 opls_805 1 UNK C05 1 -0.0374 12.0110 33 | 7 opls_806 1 UNK C06 1 -0.2516 12.0110 34 | 8 opls_807 1 UNK H07 1 0.1046 1.0080 35 | 9 opls_808 1 UNK H08 1 0.1046 1.0080 36 | 10 opls_809 1 UNK H09 1 0.1029 1.0080 37 | 11 opls_810 1 UNK H0A 1 0.1029 1.0080 38 | 12 opls_811 1 UNK H0B 1 0.1029 1.0080 39 | 13 opls_812 1 UNK H0C 1 0.1071 1.0080 40 | 14 opls_813 1 UNK H0D 1 0.1071 1.0080 41 | 15 opls_814 1 UNK H0E 1 0.1071 1.0080 42 | [ bonds ] 43 | 2 1 1 0.1229 476976.000 44 | 3 2 1 0.1327 179075.200 45 | 4 2 1 0.1327 179075.200 46 | 5 3 1 0.1410 267776.000 47 | 6 4 1 0.1410 267776.000 48 | 7 5 1 0.1529 224262.400 49 | 8 5 1 0.1090 284512.000 50 | 9 5 1 0.1090 284512.000 51 | 10 7 1 0.1090 284512.000 52 | 11 7 1 0.1090 284512.000 53 | 12 7 1 0.1090 284512.000 54 | 13 6 1 0.1090 284512.000 55 | 14 6 1 0.1090 284512.000 56 | 15 6 1 0.1090 284512.000 57 | 58 | [ angles ] 59 | ; ai aj ak funct c0 c1 c2 c3 60 | 1 2 3 1 123.400 694.544 61 | 1 2 4 1 123.400 694.544 62 | 2 3 5 1 116.900 694.544 63 | 2 4 6 1 116.900 694.544 64 | 3 5 7 1 109.500 418.400 65 | 3 5 8 1 109.500 292.880 66 | 3 5 9 1 109.500 292.880 67 | 5 7 10 1 110.700 313.800 68 | 5 7 11 1 110.700 313.800 69 | 5 7 12 1 110.700 313.800 70 | 4 6 13 1 109.500 292.880 71 | 4 6 14 1 109.500 292.880 72 | 4 6 15 1 109.500 292.880 73 | 11 7 12 1 107.800 276.144 74 | 13 6 15 1 107.800 276.144 75 | 7 5 9 1 110.700 313.800 76 | 10 7 12 1 107.800 276.144 77 | 3 2 4 1 118.180 584.923 78 | 13 6 14 1 107.800 276.144 79 | 14 6 15 1 107.800 276.144 80 | 10 7 11 1 107.800 276.144 81 | 7 5 8 1 110.700 313.800 82 | 8 5 9 1 107.800 276.144 83 | 84 | [ dihedrals ] 85 | ; IMPROPER DIHEDRAL ANGLES 86 | ; ai aj ak al funct c0 c1 c2 c3 c4 c5 87 | 4 2 1 3 4 180.000 43.932 2 88 | 89 | [ dihedrals ] 90 | ; PROPER DIHEDRAL ANGLES 91 | ; ai aj ak al funct c0 c1 c2 c3 c4 c5 92 | 7 5 3 2 3 -2.197 5.201 0.527 -3.531 -0.000 0.000 93 | 5 3 2 1 3 21.439 0.000 -21.439 -0.000 -0.000 0.000 94 | 6 4 2 1 3 21.439 0.000 -21.439 -0.000 -0.000 0.000 95 | 6 4 2 3 3 31.206 -9.768 -21.439 -0.000 -0.000 0.000 96 | 5 3 2 4 3 31.206 -9.768 -21.439 -0.000 -0.000 0.000 97 | 12 7 5 8 3 0.628 1.883 0.000 -2.510 -0.000 0.000 98 | 12 7 5 9 3 0.628 1.883 0.000 -2.510 -0.000 0.000 99 | 10 7 5 8 3 0.628 1.883 0.000 -2.510 -0.000 0.000 100 | 10 7 5 9 3 0.628 1.883 0.000 -2.510 -0.000 0.000 101 | 11 7 5 8 3 0.628 1.883 0.000 -2.510 -0.000 0.000 102 | 11 7 5 9 3 0.628 1.883 0.000 -2.510 -0.000 0.000 103 | 10 7 5 3 3 0.979 2.937 0.000 -3.916 -0.000 0.000 104 | 11 7 5 3 3 0.979 2.937 0.000 -3.916 -0.000 0.000 105 | 12 7 5 3 3 0.979 2.937 0.000 -3.916 -0.000 0.000 106 | 8 5 3 2 3 0.414 1.243 0.000 -1.657 -0.000 0.000 107 | 14 6 4 2 3 0.414 1.243 0.000 -1.657 -0.000 0.000 108 | 9 5 3 2 3 0.414 1.243 0.000 -1.657 -0.000 0.000 109 | 13 6 4 2 3 0.414 1.243 0.000 -1.657 -0.000 0.000 110 | 15 6 4 2 3 0.414 1.243 0.000 -1.657 -0.000 0.000 111 | 112 | [ pairs ] 113 | 1 5 1 114 | 1 6 1 115 | 4 5 1 116 | 3 6 1 117 | 2 7 1 118 | 2 8 1 119 | 2 9 1 120 | 3 10 1 121 | 3 11 1 122 | 3 12 1 123 | 2 13 1 124 | 2 14 1 125 | 2 15 1 126 | 8 10 1 127 | 9 10 1 128 | 8 11 1 129 | 9 11 1 130 | 8 12 1 131 | 9 12 1 132 | 133 | -------------------------------------------------------------------------------- /tests/test_files/EMC.lmp: -------------------------------------------------------------------------------- 1 | LAMMPS data file Created by LigParGen - (Written by Leela S. Dodda) 2 | 3 | 15 atoms 4 | 14 bonds 5 | 23 angles 6 | 19 dihedrals 7 | 7 impropers 8 | 9 | 15 atom types 10 | 14 bond types 11 | 23 angle types 12 | 19 dihedral types 13 | 7 improper types 14 | 15 | -2.011740 47.988260 xlo xhi 16 | 0.097060 50.097060 ylo yhi 17 | -3.154030 46.845970 zlo zhi 18 | 19 | Masses 20 | 21 | 1 15.999 22 | 2 12.011 23 | 3 15.999 24 | 4 15.999 25 | 5 12.011 26 | 6 12.011 27 | 7 12.011 28 | 8 1.008 29 | 9 1.008 30 | 10 1.008 31 | 11 1.008 32 | 12 1.008 33 | 13 1.008 34 | 14 1.008 35 | 15 1.008 36 | 37 | Pair Coeffs 38 | 39 | 1 0.210 2.9600000 40 | 2 0.070 3.5500000 41 | 3 0.140 2.9000000 42 | 4 0.140 2.9000000 43 | 5 0.066 3.5000000 44 | 6 0.066 3.5000000 45 | 7 0.066 3.5000000 46 | 8 0.030 2.5000000 47 | 9 0.030 2.5000000 48 | 10 0.030 2.5000000 49 | 11 0.030 2.5000000 50 | 12 0.030 2.5000000 51 | 13 0.030 2.5000000 52 | 14 0.030 2.5000000 53 | 15 0.030 2.5000000 54 | 55 | Bond Coeffs 56 | 57 | 1 570.0000 1.2290 58 | 2 214.0000 1.3270 59 | 3 214.0000 1.3270 60 | 4 320.0000 1.4100 61 | 5 320.0000 1.4100 62 | 6 268.0000 1.5290 63 | 7 340.0000 1.0900 64 | 8 340.0000 1.0900 65 | 9 340.0000 1.0900 66 | 10 340.0000 1.0900 67 | 11 340.0000 1.0900 68 | 12 340.0000 1.0900 69 | 13 340.0000 1.0900 70 | 14 340.0000 1.0900 71 | 72 | Angle Coeffs 73 | 74 | 1 83.000 123.400 75 | 2 83.000 123.400 76 | 3 83.000 116.900 77 | 4 83.000 116.900 78 | 5 50.000 109.500 79 | 6 35.000 109.500 80 | 7 35.000 109.500 81 | 8 37.500 110.700 82 | 9 37.500 110.700 83 | 10 37.500 110.700 84 | 11 35.000 109.500 85 | 12 35.000 109.500 86 | 13 35.000 109.500 87 | 14 33.000 107.800 88 | 15 33.000 107.800 89 | 16 37.500 110.700 90 | 17 33.000 107.800 91 | 18 69.900 118.180 92 | 19 33.000 107.800 93 | 20 33.000 107.800 94 | 21 33.000 107.800 95 | 22 37.500 110.700 96 | 23 33.000 107.800 97 | 98 | Dihedral Coeffs 99 | 100 | 1 0.000 5.124 0.000 0.000 101 | 2 0.000 5.124 0.000 0.000 102 | 3 -1.220 -0.126 0.422 0.000 103 | 4 0.000 0.000 0.468 0.000 104 | 5 0.000 0.000 0.198 0.000 105 | 6 0.000 0.000 0.198 0.000 106 | 7 0.000 0.000 0.300 0.000 107 | 8 4.669 5.124 0.000 0.000 108 | 9 0.000 0.000 0.300 0.000 109 | 10 0.000 0.000 0.198 0.000 110 | 11 0.000 0.000 0.198 0.000 111 | 12 0.000 0.000 0.198 0.000 112 | 13 0.000 0.000 0.468 0.000 113 | 14 0.000 0.000 0.300 0.000 114 | 15 0.000 0.000 0.300 0.000 115 | 16 0.000 0.000 0.300 0.000 116 | 17 0.000 0.000 0.300 0.000 117 | 18 0.000 0.000 0.468 0.000 118 | 19 4.669 5.124 0.000 0.000 119 | 120 | Improper Coeffs 121 | 122 | 1 10.500 -1 2 123 | 2 0.000 -1 2 124 | 3 0.000 -1 2 125 | 4 0.000 -1 2 126 | 5 0.000 -1 2 127 | 6 0.000 -1 2 128 | 7 0.000 -1 2 129 | 130 | Atoms 131 | 132 | 1 1 1 -0.49440000 1.000 1.00000 0.00000 133 | 2 1 2 0.57480000 -0.223 1.00000 0.00000 134 | 3 1 3 -0.32880000 -1.023 1.00000 1.10234 135 | 4 1 4 -0.32510000 -1.024 0.99861 -1.10290 136 | 5 1 5 0.02330000 -0.340 1.00000 2.36095 137 | 6 1 6 -0.03740000 -0.341 0.99702 -2.36128 138 | 7 1 7 -0.25160000 -1.363 0.99819 3.47610 139 | 8 1 8 0.10460000 0.289 0.10525 2.43386 140 | 9 1 9 0.10460000 0.287 1.89585 2.43492 141 | 10 1 10 0.10290000 -0.875 0.99685 4.45447 142 | 11 1 11 0.10290000 -2.012 1.87718 3.40445 143 | 12 1 12 0.10290000 -2.011 0.11809 3.40254 144 | 13 1 13 0.10710000 0.274 1.89689 -2.45868 145 | 14 1 14 0.10710000 -1.093 0.99851 -3.15403 146 | 15 1 15 0.10710000 0.273 0.09706 -2.45671 147 | 148 | Bonds 149 | 150 | 1 1 2 1 151 | 2 2 3 2 152 | 3 3 4 2 153 | 4 4 5 3 154 | 5 5 6 4 155 | 6 6 7 5 156 | 7 7 8 5 157 | 8 8 9 5 158 | 9 9 10 7 159 | 10 10 11 7 160 | 11 11 12 7 161 | 12 12 13 6 162 | 13 13 14 6 163 | 14 14 15 6 164 | 165 | Angles 166 | 167 | 1 1 1 2 3 168 | 2 2 1 2 4 169 | 3 3 2 3 5 170 | 4 4 2 4 6 171 | 5 5 3 5 7 172 | 6 6 3 5 8 173 | 7 7 3 5 9 174 | 8 8 5 7 10 175 | 9 9 5 7 11 176 | 10 10 5 7 12 177 | 11 11 4 6 13 178 | 12 12 4 6 14 179 | 13 13 4 6 15 180 | 14 14 11 7 12 181 | 15 15 13 6 15 182 | 16 16 7 5 9 183 | 17 17 10 7 12 184 | 18 18 3 2 4 185 | 19 19 13 6 14 186 | 20 20 14 6 15 187 | 21 21 10 7 11 188 | 22 22 7 5 8 189 | 23 23 8 5 9 190 | 191 | Dihedrals 192 | 193 | 1 1 5 3 2 1 194 | 2 2 6 4 2 1 195 | 3 3 7 5 3 2 196 | 4 4 10 7 5 3 197 | 5 5 13 6 4 2 198 | 6 6 14 6 4 2 199 | 7 7 12 7 5 8 200 | 8 8 5 3 2 4 201 | 9 9 11 7 5 9 202 | 10 10 9 5 3 2 203 | 11 11 15 6 4 2 204 | 12 12 8 5 3 2 205 | 13 13 11 7 5 3 206 | 14 14 12 7 5 9 207 | 15 15 10 7 5 8 208 | 16 16 10 7 5 9 209 | 17 17 11 7 5 8 210 | 18 18 12 7 5 3 211 | 19 19 6 4 2 3 212 | 213 | Impropers 214 | 215 | 1 1 2 1 3 4 216 | 2 2 5 3 7 8 217 | 3 3 5 9 3 7 218 | 4 4 7 10 11 5 219 | 5 5 7 10 12 5 220 | 6 6 6 4 13 14 221 | 7 7 6 4 13 15 222 | 223 | -------------------------------------------------------------------------------- /tests/test_files/EMC.lmp.xyz: -------------------------------------------------------------------------------- 1 | 15 2 | 3 | O 1.0 1.0 0.0 4 | C -0.223 1.0 0.0 5 | O -1.023 1.0 1.10234 6 | O -1.024 0.99861 -1.1029 7 | C -0.34 1.0 2.36095 8 | C -0.341 0.99702 -2.36128 9 | C -1.363 0.99819 3.4761 10 | H 0.289 0.10525 2.43386 11 | H 0.287 1.89585 2.43492 12 | H -0.875 0.99685 4.45447 13 | H -2.012 1.87718 3.40445 14 | H -2.011 0.11809 3.40254 15 | H 0.274 1.89689 -2.45868 16 | H -1.093 0.99851 -3.15403 17 | H 0.273 0.09706 -2.45671 -------------------------------------------------------------------------------- /tests/test_files/EMC.pdb: -------------------------------------------------------------------------------- 1 | TITLE cid_522046 2 | REMARK 4 COMPLIES WITH FORMAT V. 3.3, 21-NOV-2012 3 | REMARK 888 4 | REMARK 888 WRITTEN BY MDGO (CREATED BY TINGZHENG HOU) 5 | HETATM 1 O UNK 900 0.680 -0.454 0.001 1.00 0.00 O 6 | HETATM 2 O UNK 900 -1.500 -0.780 0.000 1.00 0.00 O 7 | HETATM 3 O UNK 900 -0.709 1.385 0.000 1.00 0.00 O 8 | HETATM 4 C UNK 900 1.825 0.406 -0.000 1.00 0.00 C 9 | HETATM 5 C UNK 900 3.079 -0.441 -0.000 1.00 0.00 C 10 | HETATM 6 C UNK 900 -0.528 0.175 0.001 1.00 0.00 C 11 | HETATM 7 C UNK 900 -2.846 -0.292 -0.001 1.00 0.00 C 12 | HETATM 8 H UNK 900 1.805 1.039 -0.895 1.00 0.00 H 13 | HETATM 9 H UNK 900 1.806 1.040 0.894 1.00 0.00 H 14 | HETATM 10 H UNK 900 3.975 0.186 -0.001 1.00 0.00 H 15 | HETATM 11 H UNK 900 3.103 -1.093 0.879 1.00 0.00 H 16 | HETATM 12 H UNK 900 3.103 -1.094 -0.879 1.00 0.00 H 17 | HETATM 13 H UNK 900 -3.034 0.301 0.899 1.00 0.00 H 18 | HETATM 14 H UNK 900 -3.519 -1.153 -0.001 1.00 0.00 H 19 | HETATM 15 H UNK 900 -3.033 0.300 -0.901 1.00 0.00 H 20 | CONECT 1 4 6 21 | CONECT 2 6 7 22 | CONECT 3 6 23 | CONECT 3 6 24 | CONECT 4 1 5 8 9 25 | CONECT 5 4 10 11 12 26 | CONECT 6 1 2 3 27 | CONECT 6 3 28 | CONECT 7 2 13 14 15 29 | CONECT 8 4 30 | CONECT 9 4 31 | CONECT 10 5 32 | CONECT 11 5 33 | CONECT 12 5 34 | CONECT 13 7 35 | CONECT 14 7 36 | CONECT 15 7 37 | END 38 | -------------------------------------------------------------------------------- /tests/test_files/EMC.xyz: -------------------------------------------------------------------------------- 1 | 15 2 | 3 | C 0.18700 -1.22900 -0.00000 4 | O 1.37700 -1.84500 -0.00000 5 | O -0.76900 -2.17300 -0.00000 6 | O 0.00000 -0.03100 -0.00100 7 | C 2.53300 -0.96600 -0.00000 8 | C -2.11800 -1.66800 -0.00000 9 | C 3.76700 -1.84400 -0.00000 10 | H 2.48200 -0.32400 -0.88900 11 | H 2.48200 -0.32300 0.88800 12 | H -2.29900 -1.06000 0.89400 13 | H -2.75500 -2.55600 -0.00000 14 | H -2.29900 -1.06100 -0.89600 15 | H 4.66400 -1.20900 -0.00000 16 | H 3.79600 -2.48400 0.89100 17 | H 3.79600 -2.48400 -0.89200 18 | -------------------------------------------------------------------------------- /tests/test_files/LiPF6.xyz: -------------------------------------------------------------------------------- 1 | 8 2 | comment line 3 | P 0.000 0.000 0.000 4 | F 1.600 0.000 0.000 5 | F 0.000 1.600 0.000 6 | F 0.000 0.000 1.600 7 | F -1.600 0.000 0.000 8 | F 0.000 -1.600 0.000 9 | F 0.000 0.000 -1.600 10 | Li 1.500 1.500 1.500 11 | -------------------------------------------------------------------------------- /tests/test_files/LiTFSi.xyz: -------------------------------------------------------------------------------- 1 | 16 2 | 3 | S 1.05507 0.03860 -0.05294 4 | C 0.83897 1.68146 0.69485 5 | N 0.44039 -0.28197 -1.68418 6 | O 0.54145 -1.11301 0.82473 7 | O 2.54386 -0.32393 -0.15642 8 | F 0.81407 2.64204 -0.29726 9 | F -0.34126 1.72090 1.40997 10 | F 1.89449 1.94060 1.54824 11 | S -1.33167 -0.30235 -1.70408 12 | Li 1.09090 1.09186 -2.90735 13 | C -2.26217 1.23900 -1.45284 14 | O -1.66432 -0.92535 -3.06792 15 | O -1.75088 -1.43998 -0.76061 16 | F -1.52007 2.30877 -1.91390 17 | F -3.45147 1.17789 -2.15425 18 | F -2.53718 1.40794 -0.11042 19 | -------------------------------------------------------------------------------- /tests/test_files/PF6.xyz: -------------------------------------------------------------------------------- 1 | 7 2 | comment line 3 | P 0.000 0.000 0.000 4 | F 1.600 0.000 0.000 5 | F 0.000 1.600 0.000 6 | F 0.000 0.000 1.600 7 | F -1.600 0.000 0.000 8 | F 0.000 -1.600 0.000 9 | F 0.000 0.000 -1.600 10 | -------------------------------------------------------------------------------- /tests/test_files/TFSI.xyz: -------------------------------------------------------------------------------- 1 | 15 2 | 3 | C 22.780434 54.146672 45.365179 4 | F 22.878768 54.583165 46.672383 5 | F 22.446375 52.805867 45.352616 6 | F 21.799203 54.871104 44.716093 7 | S 24.373355 54.385695 44.520460 8 | O 25.368966 53.237750 44.745699 9 | O 25.150603 55.584772 45.083778 10 | N 24.374095 54.658060 42.773780 11 | S 23.246807 53.505802 42.053388 12 | O 24.499940 53.306699 41.183721 13 | O 22.770008 54.866430 41.516197 14 | C 22.511203 53.101717 40.378193 15 | F 23.014430 53.848260 39.309506 16 | F 21.136240 53.280136 40.422653 17 | F 22.738580 51.762762 40.103706 18 | -------------------------------------------------------------------------------- /tests/test_files/gen2_light/gen2_mdgo_unwrapped_nvt_main.dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HouGroup/mdgo/b3d4dab22dddd67505d15e0ed3305c01d4f602e4/tests/test_files/gen2_light/gen2_mdgo_unwrapped_nvt_main.dcd -------------------------------------------------------------------------------- /tests/test_files/subdir with spaces/EMC.xyz: -------------------------------------------------------------------------------- 1 | 15 2 | 3 | C 0.18700 -1.22900 -0.00000 4 | O 1.37700 -1.84500 -0.00000 5 | O -0.76900 -2.17300 -0.00000 6 | O 0.00000 -0.03100 -0.00100 7 | C 2.53300 -0.96600 -0.00000 8 | C -2.11800 -1.66800 -0.00000 9 | C 3.76700 -1.84400 -0.00000 10 | H 2.48200 -0.32400 -0.88900 11 | H 2.48200 -0.32300 0.88800 12 | H -2.29900 -1.06000 0.89400 13 | H -2.75500 -2.55600 -0.00000 14 | H -2.29900 -1.06100 -0.89600 15 | H 4.66400 -1.20900 -0.00000 16 | H 3.79600 -2.48400 0.89100 17 | H 3.79600 -2.48400 -0.89200 18 | -------------------------------------------------------------------------------- /tests/test_forcefield.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import shutil 5 | import sys 6 | import tempfile 7 | import unittest 8 | from io import StringIO 9 | 10 | import numpy as np 11 | import pytest 12 | from pymatgen.io.lammps.data import LammpsData 13 | 14 | from mdgo.forcefield.aqueous import Aqueous, Ion 15 | from mdgo.forcefield.crawler import FFcrawler 16 | 17 | test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") 18 | 19 | 20 | class FFcrawlerTest(unittest.TestCase): 21 | def test_chrome(self) -> None: 22 | with open(os.path.join(test_dir, "EMC.lmp")) as f: 23 | pdf = f.readlines() 24 | with open(os.path.join(test_dir, "CCOC(=O)OC.lmp")) as f: 25 | smiles = f.readlines() 26 | with open(os.path.join(test_dir, "EMC.lmp.xyz")) as f: 27 | xyz = f.readlines() 28 | with open(os.path.join(test_dir, "EMC.gro")) as f: 29 | gro = f.readlines() 30 | with open(os.path.join(test_dir, "EMC.itp")) as f: 31 | itp = f.readlines() 32 | 33 | saved_stdout = sys.stdout 34 | download_dir = tempfile.mkdtemp() 35 | try: 36 | out = StringIO() 37 | sys.stdout = out 38 | 39 | lpg = FFcrawler(download_dir, xyz=True, gromacs=True) 40 | lpg.data_from_pdb(os.path.join(test_dir, "EMC.pdb")) 41 | assert "LigParGen server connected.\nStructure info uploaded. Rendering force field...\n" in out.getvalue() 42 | assert "Force field file downloaded.\n.xyz file saved.\nForce field file saved.\n" in out.getvalue() 43 | assert os.path.exists(os.path.join(download_dir, "EMC.lmp")) 44 | assert os.path.exists(os.path.join(download_dir, "EMC.lmp.xyz")) 45 | assert os.path.exists(os.path.join(download_dir, "EMC.gro")) 46 | assert os.path.exists(os.path.join(download_dir, "EMC.itp")) 47 | with open(os.path.join(download_dir, "EMC.lmp")) as f: 48 | pdf_actual = f.readlines() 49 | assert pdf == pdf_actual 50 | with open(os.path.join(download_dir, "EMC.lmp.xyz")) as f: 51 | xyz_actual = f.readlines() 52 | assert xyz == xyz_actual 53 | with open(os.path.join(download_dir, "EMC.gro")) as f: 54 | gro_actual = f.readlines() 55 | assert gro == gro_actual 56 | with open(os.path.join(download_dir, "EMC.itp")) as f: 57 | itp_actual = f.readlines() 58 | assert itp == itp_actual 59 | lpg = FFcrawler(download_dir) 60 | lpg.data_from_smiles("CCOC(=O)OC") 61 | with open(os.path.join(download_dir, "CCOC(=O)OC.lmp")) as f: 62 | smiles_actual = f.readlines() 63 | assert smiles_actual[:13] == smiles[:13] 64 | assert smiles_actual[18:131] == smiles[18:131] 65 | assert smiles_actual[131][:26] == " 1 1 1 -0.28" 66 | assert smiles_actual[132][:25] == " 2 1 2 0.01" 67 | assert smiles_actual[145][:25] == " 15 1 15 0.10" 68 | assert smiles_actual[146:] == smiles[146:] 69 | finally: 70 | sys.stdout = saved_stdout 71 | shutil.rmtree(download_dir) 72 | 73 | 74 | class AqueousTest(unittest.TestCase): 75 | def test_get_ion(self) -> None: 76 | """ 77 | Some unit tests for get_ion 78 | """ 79 | # string input, all lowercase 80 | cation_ff = Aqueous.get_ion(parameter_set="lm", water_model="opc3", ion="li+") 81 | assert isinstance(cation_ff, LammpsData) 82 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.354, atol=0.001) 83 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.0064158, atol=0.0000001) 84 | 85 | # string input, using the default ion parameter set for the water model 86 | cation_ff = Aqueous.get_ion(parameter_set="lm", water_model="opc3", ion="li+") 87 | assert isinstance(cation_ff, LammpsData) 88 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.354, atol=0.001) 89 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.0064158, atol=0.0000001) 90 | 91 | # Ion object input, all lowercase 92 | li = Ion.from_formula("Li+") 93 | cation_ff = Aqueous.get_ion(parameter_set="jc", water_model="spce", ion=li) 94 | assert isinstance(cation_ff, LammpsData) 95 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 1.409, atol=0.001) 96 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.3367344, atol=0.0000001) 97 | 98 | # anion 99 | anion_ff = Aqueous.get_ion(parameter_set="jj", water_model="tip4p", ion="F-") 100 | assert isinstance(anion_ff, LammpsData) 101 | assert np.allclose(anion_ff.force_field["Pair Coeffs"]["coeff2"].item(), 3.05, atol=0.001) 102 | assert np.allclose(anion_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.71, atol=0.0000001) 103 | 104 | # divalent, uppercase water model with hyphen 105 | cation_ff = Aqueous.get_ion(parameter_set="lm", water_model="TIP3P-FB", ion="Zn+2") 106 | assert isinstance(cation_ff, LammpsData) 107 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.495, atol=0.001) 108 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.01570749, atol=0.0000001) 109 | 110 | # trivalent, with brackets in ion name 111 | cation_ff = Aqueous.get_ion(parameter_set="lm", water_model="auto", ion="La[3+]") 112 | assert isinstance(cation_ff, LammpsData) 113 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 3.056, atol=0.001) 114 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.1485017, atol=0.0000001) 115 | 116 | # model auto selection 117 | cation_ff = Aqueous.get_ion(ion="li+") 118 | assert isinstance(cation_ff, LammpsData) 119 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 1.409, atol=0.001) 120 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.3367344, atol=0.0000001) 121 | cation_ff = Aqueous.get_ion(parameter_set="jj", ion="li+") 122 | assert isinstance(cation_ff, LammpsData) 123 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.87, atol=0.001) 124 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.0005, atol=0.0000001) 125 | cation_ff = Aqueous.get_ion(water_model="opc3", ion="li+") 126 | assert isinstance(cation_ff, LammpsData) 127 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.3537544133267763, atol=0.001) 128 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.0064158, atol=0.0000001) 129 | 130 | # ion not found 131 | with pytest.raises(ValueError, match="not found in database"): 132 | cation_ff = Aqueous.get_ion(parameter_set="jj", water_model="opc3", ion="Cu+3") 133 | 134 | # parameter set not found 135 | with pytest.raises(ValueError, match="No jensen_jorgensen parameters for water model opc3 for ion"): 136 | cation_ff = Aqueous.get_ion(parameter_set="jj", water_model="opc3", ion="Cu+") 137 | 138 | # water model not found 139 | with pytest.raises(ValueError, match="No ryan parameters for water model tip8p for ion"): 140 | cation_ff = Aqueous.get_ion(parameter_set="ryan", water_model="tip8p", ion="Cu+") 141 | 142 | # mixing rule 143 | cation_ff = Aqueous.get_ion(parameter_set="jc", water_model="spce", ion="li+", mixing_rule="LB") 144 | assert isinstance(cation_ff, LammpsData) 145 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 1.409, atol=0.001) 146 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.3367344, atol=0.0000001) 147 | cation_ff = Aqueous.get_ion(parameter_set="jj", water_model="tip4p", ion="li+", mixing_rule="arithmetic") 148 | assert isinstance(cation_ff, LammpsData) 149 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.863, atol=0.001) 150 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.0005, atol=0.0000001) 151 | cation_ff = Aqueous.get_ion( 152 | parameter_set="lm", water_model="tip3pfb", ion="li+", mixing_rule="lorentz-berthelot" 153 | ) 154 | assert isinstance(cation_ff, LammpsData) 155 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.352, atol=0.001) 156 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.00633615, atol=0.0000001) 157 | cation_ff = Aqueous.get_ion(parameter_set="jc", water_model="spce", ion="li+", mixing_rule="geometric") 158 | assert isinstance(cation_ff, LammpsData) 159 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 1.653, atol=0.001) 160 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.3367344, atol=0.0000001) 161 | cation_ff = Aqueous.get_ion(parameter_set="jc", water_model="tip4pew", ion="na+", mixing_rule="geometric") 162 | assert isinstance(cation_ff, LammpsData) 163 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff2"].item(), 2.260, atol=0.001) 164 | assert np.allclose(cation_ff.force_field["Pair Coeffs"]["coeff1"].item(), 0.1684375, atol=0.0000001) 165 | 166 | 167 | if __name__ == "__main__": 168 | unittest.main() 169 | -------------------------------------------------------------------------------- /tests/test_mdgopackmol.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import tempfile 5 | from pathlib import Path 6 | from subprocess import TimeoutExpired 7 | 8 | import numpy as np 9 | import pytest 10 | from pymatgen.core import Molecule 11 | 12 | from mdgo.util.packmol import PackmolWrapper 13 | 14 | test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") 15 | 16 | 17 | @pytest.fixture() 18 | def ethanol(): 19 | """ 20 | Returns a Molecule of ethanol 21 | """ 22 | ethanol_coords = [ 23 | [0.00720, -0.56870, 0.00000], 24 | [-1.28540, 0.24990, 0.00000], 25 | [1.13040, 0.31470, 0.00000], 26 | [0.03920, -1.19720, 0.89000], 27 | [0.03920, -1.19720, -0.89000], 28 | [-1.31750, 0.87840, 0.89000], 29 | [-1.31750, 0.87840, -0.89000], 30 | [-2.14220, -0.42390, -0.00000], 31 | [1.98570, -0.13650, -0.00000], 32 | ] 33 | ethanol_atoms = ["C", "C", "O", "H", "H", "H", "H", "H", "H"] 34 | 35 | return Molecule(ethanol_atoms, ethanol_coords) 36 | 37 | 38 | @pytest.fixture() 39 | def water(): 40 | """ 41 | Returns a Molecule of water 42 | """ 43 | water_coords = [ 44 | [9.626, 6.787, 12.673], 45 | [9.626, 8.420, 12.673], 46 | [10.203, 7.604, 12.673], 47 | ] 48 | water_atoms = ["H", "H", "O"] 49 | 50 | return Molecule(water_atoms, water_coords) 51 | 52 | 53 | class TestPackmolWrapper: 54 | def test_packmol_with_molecule(self, water, ethanol): 55 | """ 56 | Test coords input as Molecule 57 | """ 58 | with tempfile.TemporaryDirectory() as scratch_dir: 59 | pw = PackmolWrapper( 60 | scratch_dir, 61 | molecules=[ 62 | {"name": "water", "number": 10, "coords": water}, 63 | {"name": "ethanol", "number": 20, "coords": ethanol}, 64 | ], 65 | ) 66 | pw.make_packmol_input() 67 | pw.run_packmol() 68 | assert os.path.exists(os.path.join(scratch_dir, "packmol_out.xyz")) 69 | out = Molecule.from_file(os.path.join(scratch_dir, "packmol_out.xyz")) 70 | assert out.composition.num_atoms == 10 * 3 + 20 * 9 71 | 72 | def test_packmol_with_str(self): 73 | """ 74 | Test coords input as strings 75 | """ 76 | with tempfile.TemporaryDirectory() as scratch_dir: 77 | pw = PackmolWrapper( 78 | scratch_dir, 79 | molecules=[ 80 | {"name": "EMC", "number": 10, "coords": os.path.join(test_dir, "subdir with spaces", "EMC.xyz")}, 81 | {"name": "LiTFSi", "number": 20, "coords": os.path.join(test_dir, "LiTFSi.xyz")}, 82 | ], 83 | ) 84 | pw.make_packmol_input() 85 | pw.run_packmol() 86 | assert os.path.exists(os.path.join(scratch_dir, "packmol_out.xyz")) 87 | out = Molecule.from_file(os.path.join(scratch_dir, "packmol_out.xyz")) 88 | assert out.composition.num_atoms == 10 * 15 + 20 * 16 89 | 90 | def test_packmol_with_path(self): 91 | """ 92 | Test coords input as Path. Use a subdirectory with spaces. 93 | """ 94 | p1 = Path(os.path.join(test_dir, "subdir with spaces", "EMC.xyz")) 95 | p2 = Path(os.path.join(test_dir, "LiTFSi.xyz")) 96 | with tempfile.TemporaryDirectory() as scratch_dir: 97 | pw = PackmolWrapper( 98 | scratch_dir, 99 | molecules=[ 100 | {"name": "EMC", "number": 10, "coords": p1}, 101 | {"name": "LiTFSi", "number": 20, "coords": p2}, 102 | ], 103 | ) 104 | pw.make_packmol_input() 105 | pw.run_packmol() 106 | assert os.path.exists(os.path.join(scratch_dir, "packmol_out.xyz")) 107 | out = Molecule.from_file(os.path.join(scratch_dir, "packmol_out.xyz")) 108 | assert out.composition.num_atoms == 10 * 15 + 20 * 16 109 | 110 | def test_control_params(self, water, ethanol): 111 | """ 112 | Check that custom control_params work and that ValueError 113 | is raised when 'ERROR' appears in stdout (even if return code is 0) 114 | """ 115 | with tempfile.TemporaryDirectory() as scratch_dir: 116 | pw = PackmolWrapper( 117 | scratch_dir, 118 | molecules=[ 119 | {"name": "water", "number": 1000, "coords": water}, 120 | {"name": "ethanol", "number": 2000, "coords": ethanol}, 121 | ], 122 | control_params={"maxit": 0, "nloop": 0}, 123 | ) 124 | pw.make_packmol_input() 125 | with open(os.path.join(scratch_dir, "packmol.inp")) as f: 126 | input_string = f.read() 127 | assert "maxit 0" in input_string 128 | assert "nloop 0" in input_string 129 | with pytest.raises(ValueError, match="Packmol failed with 1"): 130 | pw.run_packmol() 131 | 132 | def test_timeout(self, water, ethanol): 133 | """ 134 | Check that the timeout works 135 | """ 136 | with tempfile.TemporaryDirectory() as scratch_dir: 137 | pw = PackmolWrapper( 138 | scratch_dir, 139 | molecules=[ 140 | {"name": "water", "number": 1000, "coords": water}, 141 | {"name": "ethanol", "number": 2000, "coords": ethanol}, 142 | ], 143 | ) 144 | pw.make_packmol_input() 145 | with pytest.raises(TimeoutExpired): 146 | pw.run_packmol(1) 147 | 148 | def test_no_return_and_box(self, water, ethanol): 149 | """ 150 | Make sure the code raises an error if packmol doesn't 151 | exit cleanly. Also verify the box arg works properly. 152 | """ 153 | with tempfile.TemporaryDirectory() as scratch_dir: 154 | pw = PackmolWrapper( 155 | scratch_dir, 156 | molecules=[ 157 | {"name": "water", "number": 1000, "coords": water}, 158 | {"name": "ethanol", "number": 2000, "coords": ethanol}, 159 | ], 160 | box=[0, 0, 0, 2, 2, 2], 161 | ) 162 | pw.make_packmol_input() 163 | with open(os.path.join(scratch_dir, "packmol.inp")) as f: 164 | input_string = f.read() 165 | assert "inside box 0 0 0 2 2 2" in input_string 166 | with pytest.raises(ValueError, match="Packmol failed with"): 167 | pw.run_packmol() 168 | 169 | def test_random_seed(self, water, ethanol): 170 | """ 171 | Confirm that seed = -1 generates random structures 172 | while seed = 1 is deterministic 173 | """ 174 | # deterministic output 175 | with tempfile.TemporaryDirectory() as scratch_dir: 176 | pw = PackmolWrapper( 177 | scratch_dir, 178 | molecules=[ 179 | {"name": "water", "number": 10, "coords": water}, 180 | {"name": "ethanol", "number": 20, "coords": ethanol}, 181 | ], 182 | seed=1, 183 | inputfile="input.in", 184 | outputfile="output.xyz", 185 | ) 186 | pw.make_packmol_input() 187 | pw.run_packmol() 188 | out1 = Molecule.from_file(os.path.join(scratch_dir, "output.xyz")) 189 | pw.run_packmol() 190 | out2 = Molecule.from_file(os.path.join(scratch_dir, "output.xyz")) 191 | assert np.array_equal(out1.cart_coords, out2.cart_coords) 192 | 193 | # randomly generated structures 194 | with tempfile.TemporaryDirectory() as scratch_dir: 195 | pw = PackmolWrapper( 196 | scratch_dir, 197 | molecules=[ 198 | {"name": "water", "number": 10, "coords": water}, 199 | {"name": "ethanol", "number": 20, "coords": ethanol}, 200 | ], 201 | seed=-1, 202 | inputfile="input.in", 203 | outputfile="output.xyz", 204 | ) 205 | pw.make_packmol_input() 206 | pw.run_packmol() 207 | out1 = Molecule.from_file(os.path.join(scratch_dir, "output.xyz")) 208 | pw.run_packmol() 209 | out2 = Molecule.from_file(os.path.join(scratch_dir, "output.xyz")) 210 | assert not np.array_equal(out1.cart_coords, out2.cart_coords) 211 | 212 | def test_arbitrary_filenames(self, water, ethanol): 213 | """ 214 | Make sure custom input and output filenames work. 215 | Use a subdirectory with spaces. 216 | """ 217 | with tempfile.TemporaryDirectory() as scratch_dir: 218 | os.mkdir(os.path.join(scratch_dir, "subdirectory with spaces")) 219 | pw = PackmolWrapper( 220 | os.path.join(scratch_dir, "subdirectory with spaces"), 221 | molecules=[ 222 | {"name": "water", "number": 10, "coords": water}, 223 | {"name": "ethanol", "number": 20, "coords": ethanol}, 224 | ], 225 | inputfile="input.in", 226 | outputfile="output.xyz", 227 | ) 228 | pw.make_packmol_input() 229 | assert os.path.exists(os.path.join(scratch_dir, "subdirectory with spaces", "input.in")) 230 | pw.run_packmol() 231 | assert os.path.exists(os.path.join(scratch_dir, "subdirectory with spaces", "output.xyz")) 232 | out = Molecule.from_file(os.path.join(scratch_dir, "subdirectory with spaces", "output.xyz")) 233 | assert out.composition.num_atoms == 10 * 3 + 20 * 9 234 | -------------------------------------------------------------------------------- /tests/test_msd.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import unittest 5 | 6 | import MDAnalysis 7 | import numpy as np 8 | from numpy.testing import assert_allclose 9 | 10 | try: 11 | import tidynamics as td 12 | except ImportError: 13 | td = None 14 | 15 | import pytest 16 | 17 | from mdgo.msd import ( 18 | create_position_arrays, 19 | mda_msd_wrapper, 20 | msd_fft, 21 | msd_straight_forward, 22 | onsager_ii_self, 23 | parse_msd_type, 24 | total_msd, 25 | ) 26 | 27 | test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") 28 | 29 | 30 | class MyTestCase(unittest.TestCase): 31 | @classmethod 32 | def setUpClass(cls) -> None: 33 | cls.arr1 = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [1, 1, 0], [4, 4, 2]]) 34 | n = 100 35 | cls.arr2 = np.cumsum(np.random.choice([-1.0, 0.0, 1.0], size=(n, 3)), axis=0) 36 | cls.fft = np.array([0.0, 8.5, 7.0, 10.5, 36.0]) 37 | cls.gen2 = MDAnalysis.Universe( 38 | os.path.join(test_dir, "gen2_light", "gen2_mdgo.data"), 39 | os.path.join(test_dir, "gen2_light", "gen2_mdgo_unwrapped_nvt_main.dcd"), 40 | format="LAMMPS", 41 | ) 42 | cls.dims = ["x", "y", "z"] 43 | 44 | def test_msd_straight_forward(self): 45 | assert_allclose( 46 | self.fft, 47 | msd_straight_forward(self.arr1), 48 | atol=1e-12, 49 | ) 50 | 51 | def test_msd_fft(self): 52 | assert_allclose(self.fft, msd_fft(self.arr1), atol=1e-12) 53 | assert_allclose(msd_straight_forward(self.arr2), msd_fft(self.arr2), atol=1e-12) 54 | 55 | def test_create_position_arrays(self): 56 | assert_allclose( 57 | np.array([21.53381769, 14.97501839, -3.87998785]), 58 | create_position_arrays(self.gen2, 0, 100, select="type 3")[50][2], 59 | ) 60 | assert_allclose( 61 | np.array([-2.78550047, -11.85487624, -17.1221954]), 62 | create_position_arrays(self.gen2, 0, 100, select="type 3")[99][10], 63 | ) 64 | assert_allclose( 65 | np.array([41.1079216, 34.95127106, 18.00482368]), 66 | create_position_arrays(self.gen2, 0, 100, select="type 3", center_of_mass=False)[50][2], 67 | ) 68 | assert_allclose( 69 | np.array([16.98478317, 8.27190208, 5.07116079]), 70 | create_position_arrays(self.gen2, 0, 100, select="type 3", center_of_mass=False)[99][10], 71 | ) 72 | 73 | def test_parse_msd_type(self): 74 | xyz = parse_msd_type("xyz") 75 | assert ["x", "y", "z"] == self.dims[xyz[0] : xyz[1] : xyz[2]] 76 | xy = parse_msd_type("xy") 77 | assert ["x", "y"] == self.dims[xy[0] : xy[1] : xy[2]] 78 | yz = parse_msd_type("yz") 79 | assert ["y", "z"] == self.dims[yz[0] : yz[1] : yz[2]] 80 | xz = parse_msd_type("xz") 81 | assert ["x", "z"] == self.dims[xz[0] : xz[1] : xz[2]] 82 | x = parse_msd_type("x") 83 | assert ["x"] == self.dims[x[0] : x[1] : x[2]] 84 | y = parse_msd_type("y") 85 | assert ["y"] == self.dims[y[0] : y[1] : y[2]] 86 | z = parse_msd_type("z") 87 | assert ["z"] == self.dims[z[0] : z[1] : z[2]] 88 | 89 | def test_onsager_ii_self(self): 90 | onsager_ii_self_fft = onsager_ii_self(self.gen2, 0, 100, select="type 3") 91 | onsager_ii_self_nocom = onsager_ii_self(self.gen2, 0, 100, select="type 3", center_of_mass=False) 92 | onsager_ii_self_nofft = onsager_ii_self(self.gen2, 0, 100, select="type 3", fft=False) 93 | 94 | assert_allclose(32.14254152556588, onsager_ii_self_fft[50]) 95 | assert_allclose(63.62190983, onsager_ii_self_fft[98]) 96 | assert_allclose(67.29990019, onsager_ii_self_fft[99]) 97 | assert_allclose(32.14254152556588, onsager_ii_self_nofft[50]) 98 | assert_allclose(63.62190983, onsager_ii_self_nofft[98]) 99 | assert_allclose(67.29990019, onsager_ii_self_nofft[99]) 100 | assert_allclose(32.338364098424634, onsager_ii_self_nocom[50]) 101 | assert_allclose(63.52915984813752, onsager_ii_self_nocom[98]) 102 | assert_allclose(67.29599346166411, onsager_ii_self_nocom[99]) 103 | 104 | def test_mda_msd_wrapper(self): 105 | mda_msd_cation = mda_msd_wrapper(self.gen2, 0, 100, select="type 3", fft=False) 106 | mda_msd_anion = mda_msd_wrapper(self.gen2, 0, 100, select="type 1", fft=False) 107 | assert_allclose(32.338364098424634, mda_msd_cation[50]) 108 | assert_allclose(63.52915984813752, mda_msd_cation[98]) 109 | assert_allclose(67.29599346166411, mda_msd_cation[99]) 110 | assert_allclose(42.69200176568008, mda_msd_anion[50]) 111 | assert_allclose(86.9209518, mda_msd_anion[98]) 112 | assert_allclose(89.84668178, mda_msd_anion[99]) 113 | assert_allclose( 114 | onsager_ii_self(self.gen2, 0, 10, select="type 3", msd_type="x", center_of_mass=False), 115 | mda_msd_wrapper(self.gen2, 0, 10, select="type 3", msd_type="x", fft=False), 116 | atol=1e-12, 117 | ) 118 | assert_allclose( 119 | onsager_ii_self(self.gen2, 0, 10, select="type 3", msd_type="y", center_of_mass=False), 120 | mda_msd_wrapper(self.gen2, 0, 10, select="type 3", msd_type="y", fft=False), 121 | atol=1e-12, 122 | ) 123 | assert_allclose( 124 | onsager_ii_self(self.gen2, 0, 10, select="type 3", msd_type="z", center_of_mass=False), 125 | mda_msd_wrapper(self.gen2, 0, 10, select="type 3", msd_type="z", fft=False), 126 | atol=1e-12, 127 | ) 128 | assert_allclose( 129 | onsager_ii_self(self.gen2, 0, 100, select="type 3", msd_type="xy", center_of_mass=False), 130 | mda_msd_wrapper(self.gen2, 0, 100, select="type 3", msd_type="xy", fft=False), 131 | atol=1e-12, 132 | ) 133 | assert_allclose( 134 | onsager_ii_self(self.gen2, 0, 100, select="type 3", msd_type="yz", center_of_mass=False), 135 | mda_msd_wrapper(self.gen2, 0, 100, select="type 3", msd_type="yz", fft=False), 136 | atol=1e-12, 137 | ) 138 | assert_allclose( 139 | onsager_ii_self(self.gen2, 0, 100, select="type 3", msd_type="xz", center_of_mass=False), 140 | mda_msd_wrapper(self.gen2, 0, 100, select="type 3", msd_type="xz", fft=False), 141 | atol=1e-12, 142 | ) 143 | if td is not None: 144 | assert_allclose( 145 | mda_msd_cation, 146 | mda_msd_wrapper(self.gen2, 0, 100, select="type 3"), 147 | atol=1e-12, 148 | ) 149 | 150 | def test_total_msd(self): 151 | total_builtin_cation = total_msd(self.gen2, 0, 100, select="type 3", fft=True, built_in=True) 152 | total_mda_cation = total_msd( 153 | self.gen2, 0, 100, select="type 3", fft=False, built_in=False, center_of_mass=False 154 | ) 155 | assert_allclose(total_builtin_cation[50], 32.14254152556588) 156 | assert_allclose(total_mda_cation[50], 32.338364098424634) 157 | with pytest.raises( 158 | ValueError, 159 | match="Warning! MDAnalysis does not support subtracting center " 160 | "of mass. Calculating without subtracting...", 161 | ): 162 | total_msd(self.gen2, 0, 100, select="type 3", fft=True, built_in=False, center_of_mass=True) 163 | 164 | 165 | if __name__ == "__main__": 166 | unittest.main() 167 | -------------------------------------------------------------------------------- /tests/test_residence_time.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import unittest 4 | 5 | 6 | class MyTestCase(unittest.TestCase): 7 | def test_something(self): 8 | assert True is True 9 | 10 | 11 | if __name__ == "__main__": 12 | unittest.main() 13 | -------------------------------------------------------------------------------- /tests/test_util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import unittest 4 | 5 | 6 | class MyTestCase(unittest.TestCase): 7 | def test_something(self): 8 | assert True is True 9 | 10 | 11 | if __name__ == "__main__": 12 | unittest.main() 13 | -------------------------------------------------------------------------------- /tests/test_volume.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import unittest 5 | 6 | from numpy.testing import assert_allclose 7 | from pymatgen.core import Molecule 8 | 9 | from mdgo.util.volume import molecular_volume 10 | 11 | test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") 12 | 13 | 14 | class MyTestCase(unittest.TestCase): 15 | @classmethod 16 | def setUpClass(cls) -> None: 17 | cls.ec = Molecule.from_file(filename=os.path.join(test_dir, "EC.xyz")) 18 | cls.emc = Molecule.from_file(filename=os.path.join(test_dir, "EMC.xyz")) 19 | cls.dec = Molecule.from_file(filename=os.path.join(test_dir, "DEC.xyz")) 20 | cls.pf6 = Molecule.from_file(filename=os.path.join(test_dir, "PF6.xyz")) 21 | cls.tfsi = Molecule.from_file(filename=os.path.join(test_dir, "TFSI.xyz")) 22 | cls.litfsi = Molecule.from_file(filename=os.path.join(test_dir, "LiTFSI.xyz")) 23 | cls.lipf6 = Molecule.from_file(filename=os.path.join(test_dir, "LiPF6.xyz")) 24 | 25 | def test_molecular_volume(self) -> None: 26 | lipf6_volume_1 = molecular_volume(self.lipf6) 27 | lipf6_volume_2 = molecular_volume(self.lipf6, res=1.0) 28 | lipf6_volume_3 = molecular_volume(self.lipf6, radii_type="Lange") 29 | lipf6_volume_4 = molecular_volume(self.lipf6, radii_type="pymatgen") 30 | lipf6_volume_5 = molecular_volume(self.lipf6, molar_volume=False) 31 | assert_allclose(lipf6_volume_1, 47.62, atol=0.01) 32 | assert_allclose(lipf6_volume_2, 43.36, atol=0.01) 33 | assert_allclose(lipf6_volume_3, 41.49, atol=0.01) 34 | assert_allclose(lipf6_volume_4, 51.94, atol=0.01) 35 | assert_allclose(lipf6_volume_5, 79.08, atol=0.01) 36 | ec_volume_1 = molecular_volume(self.ec) 37 | ec_volume_2 = molecular_volume(self.ec, exclude_h=False) 38 | ec_volume_3 = molecular_volume(self.ec, res=1.0) 39 | ec_volume_4 = molecular_volume(self.ec, radii_type="Lange") 40 | ec_volume_5 = molecular_volume(self.ec, radii_type="pymatgen") 41 | ec_volume_6 = molecular_volume(self.ec, molar_volume=False) 42 | assert_allclose(ec_volume_1, 38.44, atol=0.01) 43 | assert_allclose(ec_volume_2, 43.17, atol=0.01) 44 | assert_allclose(ec_volume_3, 40.95, atol=0.01) 45 | assert_allclose(ec_volume_4, 41.07, atol=0.01) 46 | assert_allclose(ec_volume_5, 38.44, atol=0.01) 47 | assert_allclose(ec_volume_6, 63.83, atol=0.01) 48 | litfsi_volume_1 = molecular_volume(self.litfsi) 49 | litfsi_volume_2 = molecular_volume(self.litfsi, exclude_h=False) 50 | litfsi_volume_3 = molecular_volume(self.litfsi, res=1.0) 51 | litfsi_volume_4 = molecular_volume(self.litfsi, radii_type="Lange") 52 | litfsi_volume_5 = molecular_volume(self.litfsi, radii_type="pymatgen") 53 | litfsi_volume_6 = molecular_volume(self.litfsi, molar_volume=False) 54 | litfsi_volume_7 = molecular_volume(self.litfsi, mode="act", x_size=8, y_size=8, z_size=8) 55 | assert_allclose(litfsi_volume_1, 100.16, atol=0.01) 56 | assert_allclose(litfsi_volume_2, 100.16, atol=0.01) 57 | assert_allclose(litfsi_volume_3, 99.37, atol=0.01) 58 | assert_allclose(litfsi_volume_4, 90.78, atol=0.01) 59 | assert_allclose(litfsi_volume_5, 105.31, atol=0.01) 60 | assert_allclose(litfsi_volume_6, 166.32, atol=0.01) 61 | assert_allclose(litfsi_volume_7, 124.66, atol=0.01) 62 | 63 | 64 | if __name__ == "__main__": 65 | unittest.main() 66 | --------------------------------------------------------------------------------