├── 02-Logging ├── .gitignore ├── Tutorial concept map.pptx ├── 00-index.ipynb ├── 03-Storing-Particle-Shape.ipynb └── 04-Writing-Formatted-Output.ipynb ├── 04-Custom-Actions-In-Python ├── .gitignore ├── 00-index.ipynb ├── 01-What-are-Actions.ipynb ├── 02-An-Initial-Custom-Action.ipynb ├── 03-Custom-Action-Features.ipynb ├── 06-Improving-Performance.ipynb ├── 04-Custom-Updater.ipynb └── 05-Custom-Writer.ipynb ├── 03-Parallel-Simulations-With-MPI ├── hello_world.py ├── Tutorial concept map.pptx ├── hello_partition.py ├── hello_hoomd.py ├── global_snapshot.py ├── lj_domain_error.py ├── local_snapshot.py ├── domain_decomposition.py ├── lj_performance.py ├── lj_trajectory.py ├── lj_kinetic_energy.py ├── lj_partition.py ├── 00-index.ipynb ├── 01-Introduction-to-MPI.ipynb └── 04-Running-Multiple-Simulations-With-Partitions.ipynb ├── 05-Organizing-and-Executing-Simulations ├── .gitignore ├── Tutorial concept map.pptx ├── 00-index.ipynb ├── project.py ├── project_partitioned.py └── 01-Organizing-Data.ipynb ├── .mailmap ├── .gitignore ├── 00-Introducing-HOOMD-blue ├── octahedra_assembly.png ├── Tutorial concept map.pptx ├── 00-index.ipynb ├── 01-The-Simulation-Object.ipynb └── 02-Performing-Hard-Particle-Monte-Carlo-Simulations.ipynb ├── 07-Modelling-Patchy-Particles ├── Tutorial-concept-map.pptx ├── 00-index.ipynb ├── 01-Kern-Frenkel-Model.ipynb └── kern-frenkel-schematic.svg ├── 01-Introducing-Molecular-Dynamics ├── Tutorial concept map.pptx └── 00-index.ipynb ├── .github ├── dependabot.yml ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── new_tutorial.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── setup.cfg ├── AUTHORS.md ├── hoomd-examples.code-workspace ├── LICENSE ├── .pre-commit-config.yaml ├── 06-Modelling-Rigid-Bodies └── 00-index.ipynb ├── README.md ├── CONTRIBUTING.md └── ContributorAgreement.md /02-Logging/.gitignore: -------------------------------------------------------------------------------- 1 | log.txt 2 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/.gitignore: -------------------------------------------------------------------------------- 1 | *.h5 2 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/hello_world.py: -------------------------------------------------------------------------------- 1 | print('Hello, world') 2 | -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/.gitignore: -------------------------------------------------------------------------------- 1 | dataspace 2 | signac.rc 3 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Tobias Dwyer Tobias-Dwyer <55504950+Tobias-Dwyer@users.noreply.github.com> 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | *.gsd 3 | *.log 4 | *.xml 5 | *.bib 6 | *.json 7 | *.dcd 8 | *.pyc 9 | __pycache__ 10 | -------------------------------------------------------------------------------- /02-Logging/Tutorial concept map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/02-Logging/Tutorial concept map.pptx -------------------------------------------------------------------------------- /00-Introducing-HOOMD-blue/octahedra_assembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/00-Introducing-HOOMD-blue/octahedra_assembly.png -------------------------------------------------------------------------------- /00-Introducing-HOOMD-blue/Tutorial concept map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/00-Introducing-HOOMD-blue/Tutorial concept map.pptx -------------------------------------------------------------------------------- /07-Modelling-Patchy-Particles/Tutorial-concept-map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/07-Modelling-Patchy-Particles/Tutorial-concept-map.pptx -------------------------------------------------------------------------------- /01-Introducing-Molecular-Dynamics/Tutorial concept map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/01-Introducing-Molecular-Dynamics/Tutorial concept map.pptx -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/Tutorial concept map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/03-Parallel-Simulations-With-MPI/Tutorial concept map.pptx -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/Tutorial concept map.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaander/hoomd-examples/HEAD/05-Organizing-and-Executing-Simulations/Tutorial concept map.pptx -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/hello_partition.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | communicator = hoomd.communicator.Communicator(ranks_per_partition=2) 4 | print(f'Hello from partition {communicator.partition} rank {communicator.rank}') 5 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/hello_hoomd.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import hoomd 4 | 5 | device = hoomd.device.CPU() 6 | rank = device.communicator.rank 7 | pid = os.getpid() 8 | print(f'Hello HOOMD-blue rank {rank} from process id {pid}') 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | target-branch: trunk 6 | schedule: 7 | interval: "monthly" 8 | pull-request-branch-name: 9 | separator: "-" 10 | open-pull-requests-limit: 10 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [yapf] 2 | based_on_style = google 3 | align_closing_bracket_with_visual_indent = True 4 | split_before_arithmetic_operator = True 5 | split_before_bitwise_operator = True 6 | split_before_logical_operator = True 7 | blank_line_before_module_docstring = True 8 | split_before_dot = True 9 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # hoomd-examples authors 2 | 3 | * Joshua A. Anderson - University of Michigan 4 | * Isaac Bruss - University of Michigan 5 | * Bradley Dice - University of Michigan 6 | * Tobias Dwyer - University of Michigan 7 | * Brandon Butler - University of Michigan 8 | * Tim Moore - University of Michigan 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # by default, assign a maintainer and a contributor to review a pull request 2 | * @glotzerlab/hoomd-maintainers @glotzerlab/hoomd-reviewers 3 | 4 | # For changes to github settings and workflows, only assign maintainers 5 | /.github/ @glotzerlab/hoomd-maintainers 6 | 7 | .pre-commit-config.yaml @glotzerlab/hoomd-maintainers 8 | -------------------------------------------------------------------------------- /hoomd-examples.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "cSpell.words": [ 9 | "Bruss", 10 | "HOOMD", 11 | "conda", 12 | "ipynb", 13 | "jupyter" 14 | ], 15 | "editor.rulers": [ 16 | 100 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/global_snapshot.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | import numpy 3 | 4 | # Initialize the simulation. 5 | device = hoomd.device.CPU() 6 | sim = hoomd.Simulation(device=device) 7 | sim.create_state_from_gsd(filename='random.gsd') 8 | 9 | # Call get_snapshot on all ranks. 10 | snapshot = sim.state.get_snapshot() 11 | 12 | # Access particle data on rank 0 only. 13 | if snapshot.communicator.rank == 0: 14 | total_mass = numpy.sum(snapshot.particles.mass) 15 | print(total_mass) 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_tutorial.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New tutorial 3 | about: Suggest a new HOOMD-blue tutorial 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Tutorial description 13 | 14 | 15 | 16 | ## Section 17 | 18 | 19 | 20 | ## Author 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem with a HOOMD example notebook 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## File name 11 | 12 | 13 | 14 | ## Description 15 | 16 | 17 | 18 | ## Can you fix it? 19 | 20 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | 6 | Resolves: #??? 7 | 8 | ## Checklist: 9 | - [ ] I have reviewed the [**Contributor Guidelines**](https://github.com/glotzerlab/hoomd-examples/blob/trunk/CONTRIBUTING.md). 10 | - [ ] I agree with the terms of the [**HOOMD-blue Contributor Agreement**](https://github.com/glotzerlab/hoomd-examples/blob/trunk/ContributorAgreement.md). 11 | - [ ] My name is on the [list of authors](https://github.com/glotzerlab/hoomd-examples/blob/trunk/AUTHORS.md). 12 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/lj_domain_error.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | device = hoomd.device.CPU() 4 | sim = hoomd.Simulation(device=device, seed=1) 5 | sim.create_state_from_gsd( 6 | filename='../01-Introducing-Molecular-Dynamics/random.gsd') 7 | 8 | integrator = hoomd.md.Integrator(dt=0.005) 9 | cell = hoomd.md.nlist.Cell(buffer=0.4) 10 | lj = hoomd.md.pair.LJ(nlist=cell) 11 | lj.params[('A', 'A')] = dict(epsilon=1, sigma=1) 12 | lj.r_cut[('A', 'A')] = 2.5 13 | integrator.forces.append(lj) 14 | nvt = hoomd.md.methods.ConstantVolume( 15 | filter=hoomd.filter.All(), 16 | thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5)) 17 | integrator.methods.append(nvt) 18 | sim.operations.integrator = integrator 19 | sim.run(0) 20 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/local_snapshot.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # Initialize the simulation. 4 | device = hoomd.device.CPU() 5 | sim = hoomd.Simulation(device=device) 6 | sim.create_state_from_gsd(filename='random.gsd') 7 | 8 | # Access the local snapshot. 9 | with sim.state.cpu_local_snapshot as snapshot: 10 | N = len(snapshot.particles.position) 11 | 12 | # Double the mass of every particle. 13 | snapshot.particles.mass *= 2 14 | 15 | # Look up the index of the particle with tag 100. 16 | idx = snapshot.particles.rtag[100] 17 | 18 | # Modify the particle, but only if it is found. 19 | # This condition will be true on one rank and false on the others. 20 | if idx < N: 21 | snapshot.particles.mass[idx] *= 2 22 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/domain_decomposition.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # Initialize the system. 4 | device = hoomd.device.CPU() 5 | sim = hoomd.Simulation(device=device) 6 | sim.create_state_from_gsd(filename='random.gsd') 7 | 8 | # Print the domain decomposition. 9 | domain_decomposition = sim.state.domain_decomposition 10 | device.notice(f'domain_decomposition={domain_decomposition}') 11 | 12 | # Print the location of the split planes. 13 | split_fractions = sim.state.domain_decomposition_split_fractions 14 | device.notice(f'split_fractions={split_fractions}') 15 | 16 | # Print the number of particles on each rank. 17 | with sim.state.cpu_local_snapshot as snap: 18 | N = len(snap.particles.position) 19 | print(f'{N} particles on rank {device.communicator.rank}') 20 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/lj_performance.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # Initialize the simulation. 4 | device = hoomd.device.CPU() 5 | sim = hoomd.Simulation(device=device, seed=1) 6 | sim.create_state_from_gsd(filename='random.gsd') 7 | 8 | # Set the operations for a Lennard-Jones particle simulation. 9 | integrator = hoomd.md.Integrator(dt=0.005) 10 | cell = hoomd.md.nlist.Cell(buffer=0.4) 11 | lj = hoomd.md.pair.LJ(nlist=cell) 12 | lj.params[('A', 'A')] = dict(epsilon=1, sigma=1) 13 | lj.r_cut[('A', 'A')] = 2.5 14 | integrator.forces.append(lj) 15 | nvt = hoomd.md.methods.ConstantVolume( 16 | filter=hoomd.filter.All(), 17 | thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5)) 18 | integrator.methods.append(nvt) 19 | sim.operations.integrator = integrator 20 | 21 | # Run a short time before measuring performance. 22 | sim.run(100) 23 | 24 | # Run the simulation and print the performance. 25 | sim.run(1000) 26 | device.notice(f'{sim.tps}') 27 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/lj_trajectory.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # Initialize the simulation. 4 | device = hoomd.device.CPU() 5 | sim = hoomd.Simulation(device=device, seed=1) 6 | sim.create_state_from_gsd(filename='random.gsd') 7 | 8 | # Set the operations for a Lennard-Jones particle simulation. 9 | integrator = hoomd.md.Integrator(dt=0.005) 10 | cell = hoomd.md.nlist.Cell(buffer=0.4) 11 | lj = hoomd.md.pair.LJ(nlist=cell) 12 | lj.params[('A', 'A')] = dict(epsilon=1, sigma=1) 13 | lj.r_cut[('A', 'A')] = 2.5 14 | integrator.forces.append(lj) 15 | nvt = hoomd.md.methods.ConstantVolume( 16 | filter=hoomd.filter.All(), 17 | thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5)) 18 | integrator.methods.append(nvt) 19 | sim.operations.integrator = integrator 20 | 21 | # Define and add the GSD operation. 22 | gsd_writer = hoomd.write.GSD(filename='trajectory.gsd', 23 | trigger=hoomd.trigger.Periodic(1000), 24 | mode='xb') 25 | sim.operations.writers.append(gsd_writer) 26 | 27 | # Run the simulation. 28 | sim.run(1000) 29 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/lj_kinetic_energy.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # Initialize the simulation. 4 | device = hoomd.device.CPU() 5 | sim = hoomd.Simulation(device=device, seed=1) 6 | sim.create_state_from_gsd(filename='random.gsd') 7 | 8 | # Set the operations for a Lennard-Jones particle simulation. 9 | integrator = hoomd.md.Integrator(dt=0.005) 10 | cell = hoomd.md.nlist.Cell(buffer=0.4) 11 | lj = hoomd.md.pair.LJ(nlist=cell) 12 | lj.params[('A', 'A')] = dict(epsilon=1, sigma=1) 13 | lj.r_cut[('A', 'A')] = 2.5 14 | integrator.forces.append(lj) 15 | nvt = hoomd.md.methods.ConstantVolume( 16 | filter=hoomd.filter.All(), 17 | thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5)) 18 | integrator.methods.append(nvt) 19 | sim.operations.integrator = integrator 20 | 21 | # Instantiate a ThermodyanmicQuantities object to compute kinetic energy. 22 | thermodynamic_properties = hoomd.md.compute.ThermodynamicQuantities( 23 | filter=hoomd.filter.All()) 24 | sim.operations.computes.append(thermodynamic_properties) 25 | 26 | # Run the simulation. 27 | sim.run(1000) 28 | 29 | # Access the system kinetic energy on all ranks. 30 | kinetic_energy = thermodynamic_properties.kinetic_energy 31 | 32 | # Print the kinetic energy only on rank 0. 33 | if device.communicator.rank == 0: 34 | print(kinetic_energy) 35 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/lj_partition.py: -------------------------------------------------------------------------------- 1 | import hoomd 2 | 3 | # kT values to execute at: 4 | kT_values = [1.5, 2.0] 5 | 6 | # Instantiate a Communicator with 2 ranks in each partition. 7 | communicator = hoomd.communicator.Communicator(ranks_per_partition=2) 8 | 9 | # Pass the communicator to the device. 10 | device = hoomd.device.CPU(communicator=communicator) 11 | 12 | # Initialize the simulation. 13 | sim = hoomd.Simulation(device=device, seed=1) 14 | sim.create_state_from_gsd(filename='random.gsd') 15 | 16 | # Choose system parameters based on the partition 17 | sim.seed = communicator.partition 18 | kT = kT_values[communicator.partition] 19 | 20 | # Set the operations for a Lennard-Jones particle simulation. 21 | integrator = hoomd.md.Integrator(dt=0.005) 22 | cell = hoomd.md.nlist.Cell(buffer=0.4) 23 | lj = hoomd.md.pair.LJ(nlist=cell) 24 | lj.params[('A', 'A')] = dict(epsilon=1, sigma=1) 25 | lj.r_cut[('A', 'A')] = 2.5 26 | integrator.forces.append(lj) 27 | nvt = hoomd.md.methods.ConstantVolume( 28 | filter=hoomd.filter.All(), 29 | thermostat=hoomd.md.methods.thermostats.Bussi(kT=kT)) 30 | integrator.methods.append(nvt) 31 | sim.operations.integrator = integrator 32 | 33 | # Use the partition id in the output file name. 34 | gsd_writer = hoomd.write.GSD(filename=f'trajectory{communicator.partition}.gsd', 35 | trigger=hoomd.trigger.Periodic(1000), 36 | mode='xb') 37 | sim.operations.writers.append(gsd_writer) 38 | 39 | # Run the simulation. 40 | sim.run(1000) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2023 The Regents of the University of Michigan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_schedule: quarterly 3 | autofix_prs: false 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: 'v4.4.0' 8 | hooks: 9 | - id: end-of-file-fixer 10 | exclude: 'setup.cfg' 11 | - id: trailing-whitespace 12 | exclude: 'setup.cfg' 13 | - id: debug-statements 14 | - id: check-yaml 15 | - id: check-case-conflict 16 | - repo: https://github.com/asottile/pyupgrade 17 | rev: 'v3.3.1' 18 | hooks: 19 | - id: pyupgrade 20 | args: 21 | - --py36-plus 22 | - repo: https://github.com/PyCQA/isort 23 | rev: '5.12.0' 24 | hooks: 25 | - id: isort 26 | - repo: https://github.com/google/yapf 27 | rev: 'v0.32.0' 28 | hooks: 29 | - id: yapf 30 | - repo: https://github.com/PyCQA/flake8 31 | rev: '6.0.0' 32 | hooks: 33 | - id: flake8 34 | args: 35 | - --max-line-length=100 36 | - repo: https://github.com/nbQA-dev/nbQA 37 | rev: 1.7.0 38 | hooks: 39 | - id: nbqa-pyupgrade 40 | args: 41 | - --py36-plus 42 | - id: nbqa-isort 43 | - id: nbqa-yapf 44 | - repo: https://github.com/kynan/nbstripout 45 | rev: 0.6.1 46 | hooks: 47 | - id: nbstripout 48 | args: 49 | # Strip metadata but keep counts and outputs 50 | - --keep-count 51 | - --keep-output 52 | - --extra-keys 53 | - metadata.kernelspec cell.metadata.ExecuteTime 54 | - --drop-empty-cells 55 | 56 | - repo: https://github.com/bdice/nb-strip-paths 57 | rev: v0.1.0 58 | hooks: 59 | - id: nb-strip-paths 60 | -------------------------------------------------------------------------------- /07-Modelling-Patchy-Particles/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Modelling Patchy Particles\n", 8 | "\n", 9 | "In this tutorial you will implement the Kern–Frenkel potential as a model for patchy particle interactions. To do so, you will use the `hpmc.pair.user` module, which enables the addition of user-defined, arbitrary, pairwise energetic interactions to a HPMC simulation.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "This tutorial assumes you are familiar with the concepts introduced in [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) and [Introducing Molecular Dynamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb)." 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "nbsphinx-toctree": { 20 | "maxdepth": 1 21 | } 22 | }, 23 | "source": [ 24 | "## Outline\n", 25 | "\n", 26 | "1. [Kern–Frenkel Model](01-Kern-Frenkel-Model.ipynb)\n", 27 | "1. [Simulating a System of Patchy Particles](02-Simulating-a-System-of-Patchy-Particles.ipynb)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 35 | ] 36 | } 37 | ], 38 | "metadata": { 39 | "language_info": { 40 | "codemirror_mode": { 41 | "name": "ipython", 42 | "version": 3 43 | }, 44 | "file_extension": ".py", 45 | "mimetype": "text/x-python", 46 | "name": "python", 47 | "nbconvert_exporter": "python", 48 | "pygments_lexer": "ipython3", 49 | "version": "3.11.2" 50 | }, 51 | "record_timing": false 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 4 55 | } 56 | -------------------------------------------------------------------------------- /06-Modelling-Rigid-Bodies/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Modelling Rigid Bodies\n", 8 | "\n", 9 | "This tutorial shows you how to run simulations of rigid bodies.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "You should be familiar with the concepts taught in the tutorial [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb), [Introducing Molecular Dynamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb), and have some background knowledge on statistical mechanics as well as linear algebra." 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "nbsphinx-toctree": { 20 | "maxdepth": 1 21 | } 22 | }, 23 | "source": [ 24 | "## Outline\n", 25 | "\n", 26 | "1. [Introduction to Rigid Bodies](01-Introduction-to-Rigid-Bodies.ipynb)\n", 27 | "2. [Running Rigid Body Simulations](02-Running-Rigid-Body-Simulations.ipynb)\n", 28 | "3. [Preparing a General Body](03-Preparing-a-General-Body.ipynb)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 36 | ] 37 | } 38 | ], 39 | "metadata": { 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.9.13" 51 | }, 52 | "record_timing": false, 53 | "vscode": { 54 | "interpreter": { 55 | "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" 56 | } 57 | } 58 | }, 59 | "nbformat": 4, 60 | "nbformat_minor": 4 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HOOMD-blue tutorials 2 | 3 | These [jupyter] notebooks provide a tutorial for the [HOOMD-blue] simulation software and are 4 | included in HOOMD-blue's [documentation]. 5 | 6 | [jupyter]: https://jupyter.org 7 | [HOOMD-blue]: https://glotzerlab.engin.umich.edu/hoomd-blue 8 | [documentation]: http://hoomd-blue.readthedocs.io 9 | 10 | ## Outline 11 | 12 | * [Introducing HOOMD-blue](00-Introducing-HOOMD-blue/00-index.ipynb) 13 | * [Introducing Molecular Dynamics](01-Introducing-Molecular-Dynamics/00-index.ipynb) 14 | * [Logging](02-Logging/00-index.ipynb) 15 | * [Parallel Simulations With MPI](03-Parallel-Simulations-With-MPI/00-index.ipynb) 16 | * [Custom Actions In Python](04-Custom-Actions-In-Python/00-index.ipynb) 17 | * [Organizing and Executing Simulations](05-Organizing-and-Executing-Simulations/00-index.ipynb) 18 | * [Modelling Rigid Bodies](06-Modelling-Rigid-Bodies/00-index.ipynb) 19 | * [Modelling Patchy Particles](07-Modelling-Patchy-Particles/00-index.ipynb) 20 | 21 | ## Executing the tutorials 22 | 23 | You can [install HOOMD-blue] and run these examples interactively. 24 | 25 | [install HOOMD-blue]: http://hoomd-blue.readthedocs.io 26 | 27 | Clone the **hoomd-examples** repository and start **jupyter notebook** 28 | 29 | ```bash 30 | $ git clone https://github.com/glotzerlab/hoomd-examples 31 | $ cd hoomd-examples 32 | $ jupyter lab 33 | ``` 34 | 35 | ## Prerequisites 36 | 37 | These examples use the following python packages: 38 | 39 | * [fresnel](https://github.com/glotzerlab/fresnel) 40 | * [freud](http://glotzerlab.engin.umich.edu/freud/) 41 | * [GSD](https://github.com/glotzerlab/gsd) 42 | * [HOOMD-blue](http://glotzerlab.engin.umich.edu/hoomd-blue/) 43 | * [jupyterlab](http://jupyterlab.io/) 44 | * [matplotlib](http://matplotlib.org/) 45 | * [pillow](https://python-pillow.org/) 46 | 47 | Conda users can install these from [conda-forge](https://conda-forge.org/): 48 | 49 | ```bash 50 | conda install -c conda-forge fresnel freud gsd hoomd jupyterlab matplotlib 51 | ``` 52 | -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Organizing and Executing Simulations\n", 8 | "\n", 9 | "This tutorial explains how to organize and execute many simulations.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "You should be familiar with the concepts taught in [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb).\n", 14 | "The \"Submitting Cluster Jobs\" section assumes you are familiar with HPC resources and schedulers." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": { 20 | "nbsphinx-toctree": { 21 | "maxdepth": 1 22 | } 23 | }, 24 | "source": [ 25 | "## Outline\n", 26 | "\n", 27 | "1. [Organizing Data](01-Organizing-Data.ipynb) - How can I organize data from many simulations? How do I relate the parameters of the simulation to the data?\n", 28 | "2. [Executing Simulations](02-Executing-Simulations.ipynb) - How can I execute a series of workflow steps on many simulations?\n", 29 | "3. [Continuing Simulations](03-Continuing-Simulations.ipynb) - How do I continue running a simulation?\n", 30 | "4. [Submitting Cluster Jobs](04-Submitting-Cluster-Jobs.ipynb) - How do I submit workflows on HPC resources? How can I combine many simulations into a single cluster job?" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.9.7" 53 | }, 54 | "record_timing": false 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 4 58 | } 59 | -------------------------------------------------------------------------------- /01-Introducing-Molecular-Dynamics/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introducing Molecular Dynamics\n", 8 | "\n", 9 | "This tutorial introduces the molecular dynamics simulation in HOOMD-blue using the Lennard-Jones system as an example.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "This tutorial assumes you are familiar with the concepts taught in the tutorial [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) and have some background knowledge on statistical mechanics." 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "nbsphinx-toctree": { 20 | "maxdepth": 1 21 | } 22 | }, 23 | "source": [ 24 | "## Outline\n", 25 | "\n", 26 | "1. [Molecular Dynamics Simulations](01-Molecular-Dynamics-Simulations.ipynb) - What is molecular dynamics? How do I set up a molecular dynamics simulation in HOOMD-blue?\n", 27 | "2. [Initializing a Random System](02-Initializing-a-Random-System.ipynb) - How can I generate a random initial condition? What units does HOOMD-blue use?\n", 28 | "3. [Compressing the System](03-Compressing-the-System.ipynb) - How do I compress the system to a target density?" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": { 41 | "nbsphinx": "hidden" 42 | }, 43 | "source": [ 44 | "[Next section](01-Molecular-Dynamics-Simulations.ipynb)." 45 | ] 46 | } 47 | ], 48 | "metadata": { 49 | "language_info": { 50 | "codemirror_mode": { 51 | "name": "ipython", 52 | "version": 3 53 | }, 54 | "file_extension": ".py", 55 | "mimetype": "text/x-python", 56 | "name": "python", 57 | "nbconvert_exporter": "python", 58 | "pygments_lexer": "ipython3", 59 | "version": "3.9.6" 60 | }, 61 | "record_timing": false 62 | }, 63 | "nbformat": 4, 64 | "nbformat_minor": 4 65 | } 66 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Custom Actions in Python\n", 8 | "\n", 9 | "This tutorial introduces the necessary information to create\n", 10 | "custom actions in Python. Briefly, custom actions allow users\n", 11 | "and developers to implement new functionality into HOOMD-blue\n", 12 | "without having to write C++ code.\n", 13 | "\n", 14 | "**Prerequisites:**\n", 15 | "\n", 16 | "This tutorial assumes you are familiar with the concepts presented in\n", 17 | "[Introducting HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) and\n", 18 | "[Introducing Molecular Dyanamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb).\n", 19 | "Throughout this tutorial we may use other libraries to introduce writing\n", 20 | "performant actions in Python. Familiarity with these packages may help,\n", 21 | "but are not necessary to learn from this tutorial." 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": { 27 | "nbsphinx-toctree": { 28 | "maxdepth": 1 29 | } 30 | }, 31 | "source": [ 32 | "## Outline\n", 33 | "\n", 34 | "1. [What are Actions?](01-What-are-Actions.ipynb)\n", 35 | "2. [An Initial Custom Action](02-An-Initial-Custom-Action.ipynb)\n", 36 | "3. [Custom Action Features](03-Custom-Action-Features.ipynb)\n", 37 | "4. [Custom Updater](04-Custom-Updater.ipynb)\n", 38 | "5. [Custom Writer](05-Custom-Writer.ipynb)\n", 39 | "6. [Improving Performance](06-Improving-Performance.ipynb)" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 47 | ] 48 | } 49 | ], 50 | "metadata": { 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.9.2" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 4 66 | } 67 | -------------------------------------------------------------------------------- /02-Logging/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Logging\n", 8 | "\n", 9 | "This tutorial explains how to log quantities computed during the simulation.\n", 10 | "\n", 11 | "\n", 12 | "**Prerequisites:**\n", 13 | "\n", 14 | "While logging is a general topic, this tutorial uses MD and HPMC simulations to demonstrate.\n", 15 | "The tutorials [Introducing HOOMD blue](../00-Introducing-HOOMD-blue/00-index.ipynb) and [Introducing Molecular Dynamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb) teach these concepts." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": { 21 | "nbsphinx-toctree": { 22 | "maxdepth": 1 23 | } 24 | }, 25 | "source": [ 26 | "## Outline\n", 27 | "\n", 28 | "1. [Logging to a GSD file](01-Logging-to-a-GSD-file.ipynb) - What is a Logger? How can I write thermodynamic and other quantities to a GSD file? How can I access that data?\n", 29 | "2. [Saving Array Quantities](02-Saving-Array-Quantities.ipynb) - How can I save per-particle quantities for later analysis? How can I access that data?\n", 30 | "3. [Storing Particle Shape](03-Storing-Particle-Shape.ipynb) - How can I store particle shape for use with visualization tools?\n", 31 | "4. [Writing Formatted Output](04-Writing-Formatted-Output.ipynb) - How do I display status information during a simulation? How can I log user-defined quantities? How can I write formatted output to a text file?" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": { 44 | "nbsphinx": "hidden" 45 | }, 46 | "source": [ 47 | "[Next section](01-Logging-to-a-GSD-file.ipynb)." 48 | ] 49 | } 50 | ], 51 | "metadata": { 52 | "language_info": { 53 | "codemirror_mode": { 54 | "name": "ipython", 55 | "version": 3 56 | }, 57 | "file_extension": ".py", 58 | "mimetype": "text/x-python", 59 | "name": "python", 60 | "nbconvert_exporter": "python", 61 | "pygments_lexer": "ipython3", 62 | "version": "3.9.2" 63 | }, 64 | "record_timing": false 65 | }, 66 | "nbformat": 4, 67 | "nbformat_minor": 4 68 | } 69 | -------------------------------------------------------------------------------- /00-Introducing-HOOMD-blue/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introducing HOOMD-blue\n", 8 | "\n", 9 | "HOOMD-blue is a Python package that performs Molecular Dynamics and hard particle Monte Carlo simulations. HOOMD-blue is general and can be used to model nanoparticles, bead-spring polymers, active mater, and many other types of systems. This tutorial introduces the core concepts in HOOMD-blue using hard particle self-assembly as an example. Later tutorials will expand on these concepts for other types of simulations.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "This tutorial assumes you have some familiarity with the Python programming language." 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "nbsphinx-toctree": { 20 | "maxdepth": 1 21 | } 22 | }, 23 | "source": [ 24 | "## Outline\n", 25 | "\n", 26 | "1. [The Simulation Object](01-The-Simulation-Object.ipynb) - How can I configure and control a simulation? How do I choose which processor to use?\n", 27 | "2. [Performing Hard Particle Monte Carlo Simulations](02-Performing-Hard-Particle-Monte-Carlo-Simulations.ipynb) - What is hard particle Monte Carlo? How do I set up a hard particle Monte Carlo simulation?\n", 28 | "3. [Initializing the System State](03-Initializing-the-System-State.ipynb) - How do I place particles in the initial condition? What units does HOOMD-blue use?\n", 29 | "4. [Randomizing the System](04-Randomizing-the-System.ipynb) - How can I generate a random initial condition?\n", 30 | "5. [Compressing the System](05-Compressing-the-System.ipynb) - How do I compress the system to a target density? What is a volume fraction?\n", 31 | "6. [Equilibrating the System](06-Equilibrating-the-System.ipynb) - What is equilibration? How do I save simulation results?\n", 32 | "7. [Analyzing Trajectories](07-Analyzing-Trajectories.ipynb) - How can I analyze trajectories?\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": { 45 | "nbsphinx": "hidden" 46 | }, 47 | "source": [ 48 | "[Next section](01-The-Simulation-Object.ipynb)." 49 | ] 50 | } 51 | ], 52 | "metadata": { 53 | "language_info": { 54 | "codemirror_mode": { 55 | "name": "ipython", 56 | "version": 3 57 | }, 58 | "file_extension": ".py", 59 | "mimetype": "text/x-python", 60 | "name": "python", 61 | "nbconvert_exporter": "python", 62 | "pygments_lexer": "ipython3", 63 | "version": "3.11.2" 64 | }, 65 | "record_timing": false 66 | }, 67 | "nbformat": 4, 68 | "nbformat_minor": 4 69 | } 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to hoomd-examples 2 | 3 | ## New tutorials 4 | 5 | Contributions are welcomed via [pull requests on GitHub]. Prior to writing a new tutorial, please 6 | review the existing tutorials and the [software carptentries methodologies]. Plan the tutorial to 7 | reduce cognative load. Concept maps and outlines are extremely effective tools for this planning, 8 | you can find the outlines for some of the existing tutorials in the repository. 9 | 10 | [pull requests on GitHub]: https://github.com/glotzerlab/hoomd-examples/pulls 11 | [software carptentries methodologies]: https://carpentries.github.io/instructor-training/ 12 | 13 | We encourage you to [propose your new example in an issue] and discuss your plans with the HOOMD 14 | developer community to ensure that the proposed tutorial meshes well with the directions and 15 | standards of the project. Follow the general guidelines outlined below when writing your example. 16 | 17 | [propose your new example in an issue]: https://github.com/glotzerlab/hoomd-examples/issues/new?assignees=&labels=&template=new_example.md&title= 18 | 19 | ## Changes to an existing example 20 | 21 | [File a GitHub issue] to report problems with or suggest changes to an example. If you are able to 22 | implement the fix yourself, **please do so** and submit a [pull request on GitHub]. Follow the 23 | general guidelines outlined below when modifying examples. 24 | 25 | [File a GitHub issue]: https://github.com/glotzerlab/hoomd-examples/issues/new?assignees=&labels=&template=bug_report.md&title= 26 | 27 | ## General guidelines 28 | 29 | ### Code style 30 | 31 | Code style is enforce by [pre-commit]. You can install [pre-commit] hooks so that it applies on 32 | every commit. 33 | 34 | ### Methodology 35 | 36 | Follow the the [software carptentries methodologies]. 37 | 38 | Especially: 39 | 40 | * Write tutorial content for novice users. 41 | * Minimize cognative load. 42 | * Provide motivation. 43 | * Clearly outline the goals and prerequisites at the start of the tutorial and 44 | each section. 45 | 46 | ### Jupyter notebooks 47 | 48 | Examples should be written as Jupyter notebooks with an appropriate mixture of code and explanatory 49 | Markdown cells. 50 | 51 | ### Use a consistent style 52 | 53 | All examples should follow a consistent style. See the existing notebooks which demonstrate this 54 | style. 55 | 56 | ### Keep the repository size reasonable 57 | 58 | Include notebook output in your commits, but keep the repository size reasonable. HOOMD does not 59 | re-run notebooks when building documentation, so the committed output is exactly what the user will 60 | see. 61 | 62 | * Only commit changes for notebooks that you materially change. 63 | * Captured output should be less than 2 MB per image. 64 | * Feel free to make as many commits as needed during the review process, your pull request will be 65 | squash merged. 66 | * Do not commit GSD files or other output files that the examples produce. 67 | 68 | ### Organize tutorials into sections 69 | 70 | This repository is organized as a list of tutorials, each in its own directory. Each tutorial 71 | consists of an ordered list of sections, each in its own notebook. Each section should be concise 72 | and introduce only a few new concepts. Consecutive sections should be ordered logically to teach all 73 | the concepts necessary for the tutorial's subject. 74 | 75 | Tutorials should list prequisite knowledge in the overview. A series of *Introduction* tutorials 76 | explain the core concepts of HOOMD and later tutorials should include one or more of these as a 77 | prerequisite. 78 | 79 | ### Name notebook files appropriately 80 | 81 | The file naming scheme is `00-Tutorial-Title/00-Section-Title.ipynb` Use `-` instead of spaces. The 82 | leading digits should be chosen so that tutorials and sections are displayed in order by **ls**, 83 | **github**, and **jupyter**. 84 | 85 | ### List tutorials in the outline 86 | 87 | Add new tutorials to the [outline](README.md). 88 | -------------------------------------------------------------------------------- /ContributorAgreement.md: -------------------------------------------------------------------------------- 1 | # HOOMD-blue Contributor Agreement 2 | 3 | These terms apply to your contribution to the HOOMD-blue Open Source Project ("Project") owned or managed by the Regents of the University of Michigan ("Michigan"), and set out the intellectual property rights you grant to Michigan in the contributed materials. If this contribution is on behalf of a company, the term "you" will also mean the company you identify below. If you agree to be bound by these terms, fill in the information requested below and provide your signature. 4 | 5 | 1. The term "contribution" means any source code, object code, patch, tool, sample, graphic, specification, manual, documentation, or any other material posted or submitted by you to a project. 6 | 2. With respect to any worldwide copyrights, or copyright applications and registrations, in your contribution: 7 | * you hereby assign to Michigan joint ownership, and to the extent that such assignment is or becomes invalid, ineffective or unenforceable, you hereby grant to Michigan a perpetual, irrevocable, non-exclusive, worldwide, no-charge, royalty-free, unrestricted license to exercise all rights under those copyrights. This includes, at Michigan's option, the right to sublicense these same rights to third parties through multiple levels of sublicensees or other licensing arrangements; 8 | * you agree that both Michigan and you can do all things in relation to your contribution as if each of us were the sole owners, and if one of us makes a derivative work of your contribution, the one who makes the derivative work (or has it made) will be the sole owner of that derivative work; 9 | * you agree that you will not assert any moral rights in your contribution against us, our licensees or transferees; 10 | * you agree that we may register a copyright in your contribution and exercise all ownership rights associated with it; and 11 | * you agree that neither of us has any duty to consult with, obtain the consent of, pay or render an accounting to the other for any use or distribution of your contribution. 12 | 3. With respect to any patents you own, or that you can license without payment to any third party, you hereby grant to Michigan a perpetual, irrevocable, non-exclusive, worldwide, no-charge, royalty-free license to: 13 | * make, have made, use, sell, offer to sell, import, and otherwise transfer your contribution in whole or in part, alone or in combination with or included in any product, work or materials arising out of the project to which your contribution was submitted; and 14 | * at Michigan's option, to sublicense these same rights to third parties through multiple levels of sublicensees or other licensing arrangements. 15 | 4. Except as set out above, you keep all right, title, and interest in your contribution. The rights that you grant to Michigan under these terms are effective on the date you first submitted a contribution to Michigan, even if your submission took place before the date you sign these terms. Any contribution Michigan makes available under any license will also be made available under a suitable Free Software Foundation or Open Source Initiative approved license. 16 | 5. With respect to your contribution, you represent that: 17 | * it is an original work and that you can legally grant the rights set out in these terms; 18 | * it does not to the best of your knowledge violate any third party's copyrights, trademarks, patents, or other intellectual property rights; and 19 | you are authorized to sign this contract on behalf of your company (if identified below). 20 | 6. The terms will be governed by the laws of the State of Michigan and applicable U.S. Federal Law. Any choice of law rules will not apply. 21 | 22 | **By making contribution, you electronically sign and agree to the terms of the HOOMD-blue Contributor Agreement.** 23 | 24 | ![by-sa.png](https://licensebuttons.net/l/by-sa/3.0/88x31.png) 25 | 26 | Based on the Sun Contributor Agreement - version 1.5. 27 | This document is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License 28 | http://creativecommons.org/licenses/by-sa/3.0/ 29 | -------------------------------------------------------------------------------- /07-Modelling-Patchy-Particles/01-Kern-Frenkel-Model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "7288cb19", 6 | "metadata": {}, 7 | "source": [ 8 | "# Kern–Frenkel Model\n", 9 | "\n", 10 | "## Overview\n", 11 | "\n", 12 | "### Questions\n", 13 | "* What are patchy particles?\n", 14 | "* What is the Kern-Frenkel potential?\n", 15 | "* How can I evaluate the Kern-Frenkel potential between two particles? \n", 16 | "\n", 17 | "### Objectives\n", 18 | "* Describe the Kern–Frenkel model mathematically and graphically." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "id": "e373ba3e", 24 | "metadata": {}, 25 | "source": [ 26 | "## Patchy particles\n", 27 | "In the [Introducing Molecular Dynamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb) tutorial, you simulated a system of particles interacting through the Lennard-Jones pair potential.\n", 28 | "The Lennard-Jones pair potential is spherically symmetric, that is, the pair potential does not depend on the relative orientation of the interacting particles.\n", 29 | "\n", 30 | "Some systems are better represented by anisotropic pair potentials, where there is a dependence on the relative orientation of particles.\n", 31 | "The hard octahedra you simulated in [Intoducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) interacted through a purely repulsive anisotropic pair potential.\n", 32 | "Particles that interact with anisotropic pair potentials where some relative orientations are more attractive than others are known as **patchy particles**, since we can imagine that there are attractive **patches** on the surface of the particles.\n", 33 | "\n", 34 | "\n", 35 | "## The Kern–Frenkel model\n", 36 | "\n", 37 | "One of the simplest patchy particle models is the Kern–Frenkel model, which was introduced in a [2003 Journal of Chemical Physics paper](https://doi.org/10.1063/1.1569473).\n", 38 | "The Kern–Frenkel model contains directional pairwise energetic interactions in addition to hard sphere-like volume exclusion.\n", 39 | "The pair potential $u_{ij}(r_{ij}, \\Omega_i, \\Omega_j)$ between particles $i$ and $j$ at a center-to-center distance $r_{ij}$ and orientations $\\Omega_i$ and $\\Omega_j$ is of the form\n", 40 | "$$\n", 41 | "\\beta u_{ij} = \n", 42 | "\\begin{cases}\n", 43 | "\\infty & r_{ij} < \\sigma_{ij} \\\\\n", 44 | "-\\beta\\varepsilon\\cdot f(\\Omega_1, \\Omega_2) & \\sigma_{ij} \\leq r_{ij} < \\lambda_{ij}\\sigma_{ij} \\\\\n", 45 | "0 & r_{ij} \\geq \\lambda_{ij}\\sigma_{ij}\n", 46 | "\\end{cases}\n", 47 | "$$\n", 48 | "where $\\beta = 1/k_BT$, $\\varepsilon$ is the strength of the patchy interaction, $\\sigma_{ij}$ is sum of the radii of particles $i$ and $j$, $\\lambda$ is the range of the square well attraction, and $f(\\Omega_i, \\Omega_j)$ is an orientational masking function given by\n", 49 | "$$\n", 50 | "f(\\Omega_1, \\Omega_2) = \n", 51 | "\\begin{cases}\n", 52 | "1 & \\hat{e}_i \\cdot \\hat{r}_{ij} > \\cos \\delta \\mathrm{~~and~~} \\hat{e}_j \\cdot \\hat{r}_{ji} > \\cos \\delta \\\\\n", 53 | "0 & \\mathrm{otherwise}\n", 54 | "\\end{cases}\n", 55 | "$$\n", 56 | "where $\\hat{e}_i$ is the **director** of the patch on particle $i$ and $\\delta$ is the half-opening angle of the patch.\n", 57 | "\n", 58 | "Graphically, this pair potential corresponds to the following criterion: two particles interact with energy $\\infty$ if the gray shaded regions on the two particles overlap at all, $-\\varepsilon$ if the blue shaded regions on the two particles overlap, and zero otherwise.\n", 59 | "\n", 60 | "![Kern–Frenkel](kern-frenkel-schematic.svg)" 61 | ] 62 | } 63 | ], 64 | "metadata": { 65 | "celltoolbar": "Raw Cell Format", 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 3 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython3", 76 | "version": "3.11.2" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 5 81 | } 82 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/00-index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Parallel Simulation With MPI\n", 8 | "\n", 9 | "This tutorial explains how to run simulations in parallel using MPI.\n", 10 | "\n", 11 | "**Prerequisites:**\n", 12 | "\n", 13 | "* This tutorial assumes you are familiar with terms relating to high performance computing clusters, specifically *job* and *scheduler*.\n", 14 | "* This tutorial uses molecular dynamics simulations to demonstrate parallel jobs. The tutorial [Introducing Molecular Dynamics](../01-Introducing-Molecular-Dynamics/00-index.ipynb) teaches these concepts.\n", 15 | "* To execute these notebooks locally, you need an MPI enabled version of HOOMD-blue. You can check this by checking that `hoomd.version.mpi_enabled` is `True`:\n" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "data": { 25 | "text/plain": [ 26 | "True" 27 | ] 28 | }, 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "output_type": "execute_result" 32 | } 33 | ], 34 | "source": [ 35 | "import hoomd\n", 36 | "\n", 37 | "hoomd.version.mpi_enabled" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "
\n", 45 | " The HOOMD-blue binaries on conda-forge do not enable MPI due to technical limitations.\n", 46 | "
" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "The system used in *Introducing Molecular Dynamics* is small. \n", 54 | "Replicate the state of that system, as **MPI** parallel simulations require a minimum system size (this requirement is explained in more details in the next section)." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "import hoomd\n", 64 | "\n", 65 | "sim = hoomd.Simulation(device=hoomd.device.CPU())\n", 66 | "sim.create_state_from_gsd(\n", 67 | " filename='../01-Introducing-Molecular-Dynamics/random.gsd')\n", 68 | "sim.state.replicate(3, 3, 3)\n", 69 | "hoomd.write.GSD.write(filename=\"random.gsd\", state=sim.state, mode='wb')" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": { 75 | "nbsphinx-toctree": { 76 | "maxdepth": 1 77 | } 78 | }, 79 | "source": [ 80 | "## Outline\n", 81 | "\n", 82 | "1. [Introduction to MPI](01-Introduction-to-MPI.ipynb) - What is MPI? Why should I run my simulations in parallel? How can I execute scripts in parallel?\n", 83 | "2. [Domain Decomposition](02-Domain-Decomposition.ipynb) - What is a MPI rank? How should I structure my scripts? How does HOOMD-blue divide the simulation among the ranks? What limitations prevent parallel execution?\n", 84 | "3. [Accessing System Configurations With MPI](03-Accessing-System-Configurations-With-MPI.ipynb) - How can I access the state of the simulation in parallel simulations? What are the differences between local and global snapshots?\n", 85 | "4. [Running Multiple Simulations With Partitions](04-Running-Multiple-Simulations-With-Partitions.ipynb) - How can I partition a MPI communicator to run many independent simulations? When are partitions useful?" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "This tutorial is written with [jupyter](https://jupyter.org/). You can download the source from the [hoomd-examples](https://github.com/glotzerlab/hoomd-examples) repository." 93 | ] 94 | } 95 | ], 96 | "metadata": { 97 | "language_info": { 98 | "codemirror_mode": { 99 | "name": "ipython", 100 | "version": 3 101 | }, 102 | "file_extension": ".py", 103 | "mimetype": "text/x-python", 104 | "name": "python", 105 | "nbconvert_exporter": "python", 106 | "pygments_lexer": "ipython3", 107 | "version": "3.10.6" 108 | }, 109 | "record_timing": false 110 | }, 111 | "nbformat": 4, 112 | "nbformat_minor": 4 113 | } 114 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | # Trigger on pull requests. 9 | pull_request: 10 | 11 | # Trigger on pushes to the mainline branches. This prevents building commits twice when the pull 12 | # request source branch is in the same repository. 13 | push: 14 | branches: 15 | - "trunk" 16 | - "trunk-major" 17 | 18 | # Trigger on request. 19 | workflow_dispatch: 20 | 21 | # Weekly builds on the trunk branch to check that the examples continue to work with the latest 22 | # development version of HOOMD. 23 | schedule: 24 | - cron: '0 18 * * 1' 25 | 26 | jobs: 27 | start_action_runners: 28 | name: Start action runners 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: "Use jetstream2-admin/start" 32 | uses: glotzerlab/jetstream2-admin/start@v1.2.2 33 | with: 34 | OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }} 35 | OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }} 36 | number: 1 37 | 38 | execute_notebooks: 39 | name: Execute notebooks 40 | runs-on: [self-hosted,jetstream2,CPU] 41 | container: 42 | image: glotzerlab/software:nompi 43 | options: -u 0 44 | steps: 45 | - name: Clean workspace 46 | run: ( shopt -s dotglob nullglob; rm -rf ./* ) 47 | shell: bash 48 | 49 | # build the HOOMD trunk-minor branch to test the tutorials on the latest version 50 | - name: Checkout 51 | uses: actions/checkout@v3.5.2 52 | with: 53 | path: hoomd 54 | ref: trunk-major 55 | repository: glotzerlab/hoomd-blue 56 | submodules: true 57 | - name: Make build directory 58 | run: mkdir build 59 | working-directory: hoomd 60 | - name: Configure 61 | run: >- 62 | cmake 63 | -DCMAKE_BUILD_TYPE=Release 64 | -DENABLE_GPU=off 65 | -DENABLE_MPI=off 66 | -DENABLE_TBB=off 67 | -DBUILD_TESTING=off 68 | -DENABLE_LLVM=on 69 | ../ 70 | working-directory: hoomd/build 71 | - name: Compile 72 | run: make -j $(($(getconf _NPROCESSORS_ONLN) + 2)) 73 | working-directory: hoomd/build 74 | - name: Display hoomd version 75 | run: python3 -c "import hoomd; print(hoomd.version.version, hoomd.version.git_sha1)" 76 | env: 77 | PYTHONPATH: ${{ github.workspace }}/hoomd/build 78 | 79 | # clone the tutorials and run them 80 | - name: Checkout 81 | uses: actions/checkout@v3.5.2 82 | with: 83 | path: notebooks 84 | - name: List notebooks 85 | run: ls **/*.ipynb 86 | working-directory: notebooks 87 | - name: Execute notebooks 88 | run: 'for i in */; do echo "Running notebooks in: $i" && jupyter nbconvert --execute --inplace $i/*.ipynb || exit 1; done' 89 | working-directory: notebooks 90 | env: 91 | PYTHONPATH: ${{ github.workspace }}/hoomd/build 92 | 93 | # This check is very basic, but we don't want to use `python -W error` as it will also catch any 94 | # warnings generated inside nbconvert and its dependencies. 95 | - name: Check for warnings 96 | run: | 97 | has_warnings=0 98 | for file in **/*.ipynb 99 | do 100 | grep Warning $file && echo "::error file=${file}::Has deprecation warnings" && has_warnings=1 101 | done 102 | exit ${has_warnings} 103 | working-directory: notebooks 104 | 105 | # notify developers if the scheduled check fails 106 | - name: Slack notification 107 | if: ${{ github.event_name == 'schedule' && (failure() || cancelled()) }} 108 | uses: 8398a7/action-slack@v3.15.1 109 | with: 110 | status: ${{ job.status }} 111 | fields: workflow,job,message,commit 112 | mention: channel 113 | if_mention: failure,cancelled 114 | channel: '#dev-hoomd-notifications' 115 | username: Github Action 116 | author_name: '' 117 | job_name: Execute notebooks 118 | env: 119 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 120 | 121 | - name: Clean workspace 122 | run: ( shopt -s dotglob nullglob; rm -rf ./* ) 123 | shell: bash 124 | 125 | - name: Clean HOME 126 | run: ( shopt -s dotglob nullglob; rm -rf $HOME/* ) 127 | shell: bash 128 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/01-What-are-Actions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# What are Actions?\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- What are actions?\n", 14 | "- Why would I want to use custom actions?\n", 15 | "- What categories of actions exist for customization in HOOMD-blue?\n", 16 | "\n", 17 | "### Objectives\n", 18 | "\n", 19 | "- Explain the concept of an action in HOOMD-blue.\n", 20 | "- Discuss potential use cases for custom Python actions.\n", 21 | "- Provide the categories of actions in HOOMD-blue.\n", 22 | "\n", 23 | "## Actions\n", 24 | "\n", 25 | "Actions are the objects that _act_ or operate on a\n", 26 | "`hoomd.Simulation` object. Looking at HOOMD-blue\n", 27 | "from the Python perspective, actions are wrapped\n", 28 | "or composed by operations. For example, objects like\n", 29 | "`hoomd.update.BoxResize` and `hoomd.hpmc.tune.MoveSize` contain\n", 30 | "actions internally. In other words, the actions implement the logic \n", 31 | "repsonsible for implementing the functionality of an operation\n", 32 | "(e.g. resizing the box for `hoomd.update.BoxResize`), while the\n", 33 | "operation handles some logistics like determining when the action\n", 34 | "will run using a `hoomd.Trigger` object.\n", 35 | "\n", 36 | "Actions can be written in Python using `hoomd.custom.Action`.\n", 37 | "Through creating a subclass of `Action` HOOMD-blue's capabilities\n", 38 | "can be augmented and customized.\n", 39 | "\n", 40 | "\n", 41 | "## Why Python Custom Actions?\n", 42 | "\n", 43 | "HOOMD-blue offers Python `Action`s for a variety of reasons.\n", 44 | "\n", 45 | "- Enable more customization in Python.\n", 46 | "- Quicker prototyping of new simulation techniques.\n", 47 | "- Allow use of SciPy libraries for use cases like on-the-fly machine\n", 48 | " learning forcefields.\n", 49 | "- Serve as a foreign function interface from a compiled library through\n", 50 | " Python to HOOMD-blue's run loop. For instance, a Python library that\n", 51 | " uses Rust as a backend could be used in a Python custom action.\n", 52 | " \n", 53 | "Some example use cases are storing simulation data in a user's desired\n", 54 | "format whether that is a database, file format like HDF5, or other\n", 55 | "storage medium, simulating a system under a radiation source, or\n", 56 | "advanced sampling techniques.\n", 57 | " \n", 58 | "## Categories of Actions?\n", 59 | "\n", 60 | "Currently, HOOMD-blue offers three types of actions: updaters,\n", 61 | "writers, and tuners. These categories serve to distinguish how an\n", 62 | "action interacts with a simulation and its `state` attribute.\n", 63 | "\n", 64 | "* **Updaters** modify the simulation state when triggered.\n", 65 | " `hoomd.update.BoxResize` is an example of an updater as\n", 66 | " it changes the simulation box.\n", 67 | "* **Writers** observe the simulation state and write that data to a\n", 68 | " file or some other object (writers should not modify the simulation \n", 69 | " state). `hoomd.write.GSD` is an example of a writer; it does not\n", 70 | " change the simulation state, but writes it out to a GSD file.\n", 71 | "* **Tuners** modify another object's hyperparameters (or object\n", 72 | " attributes). Tuners should not modify state, but can modify another\n", 73 | " object to improve performance. `hoomd.hpmc.tune.MoveSize` is an\n", 74 | " example of this type of action. `MoveSize` tunes the integrator's\n", 75 | " trial move sizes to reach a specific acceptance rate performance but\n", 76 | " does not modify the simulation state.\n", 77 | "\n", 78 | "## Recap\n", 79 | "\n", 80 | "- Actions are objects that _act_ on a `hoomd.Simulation` object.\n", 81 | "- Actions can be written in Python using `hoomd.custom.Action`.\n", 82 | "- There are three categories of actions.\n", 83 | " - Updaters: modify simulation state\n", 84 | " - Writers: doesn't modify simulation state, writes out data\n", 85 | " - Tuners: doesn't modify simulation state, modifies object\n", 86 | " hyperparameters\n", 87 | "\n", 88 | "In the next section we start writing custom actions and using them\n", 89 | "in HOOMD-blue simulations." 90 | ] 91 | } 92 | ], 93 | "metadata": { 94 | "language_info": { 95 | "codemirror_mode": { 96 | "name": "ipython", 97 | "version": 3 98 | }, 99 | "file_extension": ".py", 100 | "mimetype": "text/x-python", 101 | "name": "python", 102 | "nbconvert_exporter": "python", 103 | "pygments_lexer": "ipython3", 104 | "version": "3.9.6" 105 | } 106 | }, 107 | "nbformat": 4, 108 | "nbformat_minor": 4 109 | } 110 | -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/project.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import flow 4 | import hoomd 5 | 6 | # parameters 7 | N_RANKS = 2 8 | N_EQUIL_STEPS = 200000 9 | CLUSTER_JOB_WALLTIME = 1 10 | HOOMD_RUN_WALLTIME_LIMIT = CLUSTER_JOB_WALLTIME * 3600 - 10 * 60 11 | 12 | 13 | def create_simulation(job): 14 | cpu = hoomd.device.CPU() 15 | sim = hoomd.Simulation(device=cpu, seed=job.statepoint.seed) 16 | mc = hoomd.hpmc.integrate.ConvexPolyhedron() 17 | mc.shape['octahedron'] = dict(vertices=[ 18 | (-0.5, 0, 0), 19 | (0.5, 0, 0), 20 | (0, -0.5, 0), 21 | (0, 0.5, 0), 22 | (0, 0, -0.5), 23 | (0, 0, 0.5), 24 | ]) 25 | sim.operations.integrator = mc 26 | 27 | return sim 28 | 29 | 30 | class Project(flow.FlowProject): 31 | pass 32 | 33 | 34 | @Project.pre.true('initialized') 35 | @Project.post.true('randomized') 36 | @Project.operation 37 | def randomize(job): 38 | sim = create_simulation(job) 39 | sim.create_state_from_gsd(filename=job.fn('lattice.gsd')) 40 | sim.run(10e3) 41 | hoomd.write.GSD.write(state=sim.state, 42 | mode='xb', 43 | filename=job.fn('random.gsd')) 44 | job.document['randomized'] = True 45 | 46 | 47 | @Project.pre.after(randomize) 48 | @Project.post.true('compressed_step') 49 | @Project.operation 50 | def compress(job): 51 | sim = create_simulation(job) 52 | sim.create_state_from_gsd(filename=job.fn('random.gsd')) 53 | 54 | a = math.sqrt(2) / 2 55 | V_particle = 1 / 3 * math.sqrt(2) * a**3 56 | 57 | initial_box = sim.state.box 58 | final_box = hoomd.Box.from_box(initial_box) 59 | final_box.volume = (sim.state.N_particles * V_particle 60 | / job.statepoint.volume_fraction) 61 | compress = hoomd.hpmc.update.QuickCompress( 62 | trigger=hoomd.trigger.Periodic(10), target_box=final_box) 63 | sim.operations.updaters.append(compress) 64 | 65 | periodic = hoomd.trigger.Periodic(10) 66 | tune = hoomd.hpmc.tune.MoveSize.scale_solver(moves=['a', 'd'], 67 | target=0.2, 68 | trigger=periodic, 69 | max_translation_move=0.2, 70 | max_rotation_move=0.2) 71 | sim.operations.tuners.append(tune) 72 | 73 | while not compress.complete and sim.timestep < 1e6: 74 | sim.run(1000) 75 | 76 | if not compress.complete: 77 | raise RuntimeError("Compression failed to complete") 78 | 79 | hoomd.write.GSD.write(state=sim.state, 80 | mode='xb', 81 | filename=job.fn('compressed.gsd')) 82 | job.document['compressed_step'] = sim.timestep 83 | 84 | 85 | @Project.pre.after(compress) 86 | @Project.post(lambda job: job.document.get('timestep', 0) - job.document[ 87 | 'compressed_step'] >= N_EQUIL_STEPS) 88 | # Cluster job directives. 89 | @Project.operation(directives=dict(nranks=N_RANKS, 90 | walltime=CLUSTER_JOB_WALLTIME)) 91 | def equilibrate(job): 92 | end_step = job.document['compressed_step'] + N_EQUIL_STEPS 93 | 94 | sim = create_simulation(job) 95 | 96 | sim.operations.integrator.a = job.document.get('a', {}) 97 | sim.operations.integrator.d = job.document.get('d', {}) 98 | 99 | if job.isfile('restart.gsd'): 100 | sim.create_state_from_gsd(filename=job.fn('restart.gsd')) 101 | else: 102 | sim.create_state_from_gsd(filename=job.fn('compressed.gsd')) 103 | 104 | gsd_writer = hoomd.write.GSD(filename=job.fn('trajectory.gsd'), 105 | trigger=hoomd.trigger.Periodic(10_000), 106 | mode='ab') 107 | sim.operations.writers.append(gsd_writer) 108 | 109 | tune = hoomd.hpmc.tune.MoveSize.scale_solver( 110 | moves=['a', 'd'], 111 | target=0.2, 112 | trigger=hoomd.trigger.And([ 113 | hoomd.trigger.Periodic(100), 114 | hoomd.trigger.Before(job.document['compressed_step'] + 5_000) 115 | ])) 116 | sim.operations.tuners.append(tune) 117 | 118 | try: 119 | while sim.timestep < end_step: 120 | sim.run(min(100_000, end_step - sim.timestep)) 121 | 122 | if (sim.device.communicator.walltime + sim.walltime >= 123 | HOOMD_RUN_WALLTIME_LIMIT): 124 | break 125 | finally: 126 | hoomd.write.GSD.write(state=sim.state, 127 | mode='wb', 128 | filename=job.fn('restart.gsd')) 129 | 130 | job.document['timestep'] = sim.timestep 131 | job.document['a'] = sim.operations.integrator.a.to_base() 132 | job.document['d'] = sim.operations.integrator.d.to_base() 133 | 134 | walltime = sim.device.communicator.walltime 135 | sim.device.notice(f'{job.id} ended on step {sim.timestep} ' 136 | f'after {walltime} seconds') 137 | 138 | 139 | # Entrypoint. 140 | if __name__ == '__main__': 141 | Project().main() 142 | -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/project_partitioned.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import flow 4 | import hoomd 5 | 6 | RANKS_PER_PARTITION = 2 7 | JOBS_PER_AGGREGATE = 3 8 | N_EQUIL_STEPS = 200000 9 | CLUSTER_JOB_WALLTIME = 1 10 | HOOMD_RUN_WALLTIME_LIMIT = CLUSTER_JOB_WALLTIME * 3600 - 10 * 60 11 | 12 | 13 | def create_simulation(job, communicator): 14 | cpu = hoomd.device.CPU(communicator=communicator) 15 | sim = hoomd.Simulation(device=cpu, seed=job.statepoint.seed) 16 | mc = hoomd.hpmc.integrate.ConvexPolyhedron() 17 | mc.shape['octahedron'] = dict(vertices=[ 18 | (-0.5, 0, 0), 19 | (0.5, 0, 0), 20 | (0, -0.5, 0), 21 | (0, 0.5, 0), 22 | (0, 0, -0.5), 23 | (0, 0, 0.5), 24 | ]) 25 | sim.operations.integrator = mc 26 | 27 | return sim 28 | 29 | 30 | class Project(flow.FlowProject): 31 | pass 32 | 33 | 34 | @Project.pre.true('initialized') 35 | @Project.post.true('randomized') 36 | @Project.operation 37 | def randomize(job): 38 | sim = create_simulation(job) 39 | sim.create_state_from_gsd(filename=job.fn('lattice.gsd')) 40 | sim.run(10e3) 41 | hoomd.write.GSD.write(state=sim.state, 42 | mode='xb', 43 | filename=job.fn('random.gsd')) 44 | job.document['randomized'] = True 45 | 46 | 47 | @Project.pre.after(randomize) 48 | @Project.post.true('compressed_step') 49 | @Project.operation 50 | def compress(job): 51 | sim = create_simulation(job) 52 | sim.create_state_from_gsd(filename=job.fn('random.gsd')) 53 | 54 | a = math.sqrt(2) / 2 55 | V_particle = 1 / 3 * math.sqrt(2) * a**3 56 | 57 | initial_box = sim.state.box 58 | final_box = hoomd.Box.from_box(initial_box) 59 | final_box.volume = (sim.state.N_particles * V_particle 60 | / job.statepoint.volume_fraction) 61 | compress = hoomd.hpmc.update.QuickCompress( 62 | trigger=hoomd.trigger.Periodic(10), target_box=final_box) 63 | sim.operations.updaters.append(compress) 64 | 65 | periodic = hoomd.trigger.Periodic(10) 66 | tune = hoomd.hpmc.tune.MoveSize.scale_solver(moves=['a', 'd'], 67 | target=0.2, 68 | trigger=periodic, 69 | max_translation_move=0.2, 70 | max_rotation_move=0.2) 71 | sim.operations.tuners.append(tune) 72 | 73 | while not compress.complete and sim.timestep < 1e6: 74 | sim.run(1000) 75 | 76 | if not compress.complete: 77 | raise RuntimeError("Compression failed to complete") 78 | 79 | hoomd.write.GSD.write(state=sim.state, 80 | mode='xb', 81 | filename=job.fn('compressed.gsd')) 82 | job.document['compressed_step'] = sim.timestep 83 | 84 | 85 | def equilibrated(job): 86 | return job.document.get( 87 | 'timestep', 0) - job.document['compressed_step'] >= N_EQUIL_STEPS 88 | 89 | 90 | @Project.pre.true('compressed_step') 91 | @Project.post(lambda *jobs: all(equilibrated(job) for job in jobs)) 92 | @Project.operation(directives=dict( 93 | nranks=lambda *jobs: RANKS_PER_PARTITION * len(jobs), 94 | walltime=CLUSTER_JOB_WALLTIME), 95 | aggregator=flow.aggregator.groupsof(num=JOBS_PER_AGGREGATE)) 96 | def equilibrate(*jobs): 97 | communicator = hoomd.communicator.Communicator( 98 | ranks_per_partition=RANKS_PER_PARTITION) 99 | job = jobs[communicator.partition] 100 | 101 | end_step = job.document['compressed_step'] + N_EQUIL_STEPS 102 | 103 | sim = create_simulation(job) 104 | 105 | sim.operations.integrator.a = job.document.get('a', {}) 106 | sim.operations.integrator.d = job.document.get('d', {}) 107 | 108 | if job.isfile('restart.gsd'): 109 | sim.create_state_from_gsd(filename=job.fn('restart.gsd')) 110 | else: 111 | sim.create_state_from_gsd(filename=job.fn('compressed.gsd')) 112 | 113 | gsd_writer = hoomd.write.GSD(filename=job.fn('trajectory.gsd'), 114 | trigger=hoomd.trigger.Periodic(10_000), 115 | mode='ab') 116 | sim.operations.writers.append(gsd_writer) 117 | 118 | tune = hoomd.hpmc.tune.MoveSize.scale_solver( 119 | moves=['a', 'd'], 120 | target=0.2, 121 | trigger=hoomd.trigger.And([ 122 | hoomd.trigger.Periodic(100), 123 | hoomd.trigger.Before(job.document['compressed_step'] + 5_000) 124 | ])) 125 | sim.operations.tuners.append(tune) 126 | 127 | try: 128 | while sim.timestep < end_step: 129 | sim.run(min(100_000, end_step - sim.timestep)) 130 | 131 | if (sim.device.communicator.walltime + sim.walltime >= 132 | HOOMD_RUN_WALLTIME_LIMIT): 133 | break 134 | finally: 135 | hoomd.write.GSD.write(state=sim.state, 136 | mode='wb', 137 | filename=job.fn('restart.gsd')) 138 | 139 | job.document['timestep'] = sim.timestep 140 | job.document['a'] = sim.operations.integrator.a.to_base() 141 | job.document['d'] = sim.operations.integrator.d.to_base() 142 | 143 | walltime = sim.device.communicator.walltime 144 | sim.device.notice(f'{job.id} ended on step {sim.timestep} ' 145 | f'after {walltime} seconds') 146 | 147 | 148 | if __name__ == '__main__': 149 | Project().main() 150 | -------------------------------------------------------------------------------- /00-Introducing-HOOMD-blue/01-The-Simulation-Object.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The Simulation Object\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* How can I configure and control a simulation?\n", 14 | "* How do I choose which processor to use?\n", 15 | "\n", 16 | "### Objectives\n", 17 | "\n", 18 | "* Explain the parts of the **Simulation** object and how they relate.\n", 19 | "* Explain how the **device** object influences how the simulation executes.\n", 20 | "* Demonstrate the creation of these objects." 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "## Core objects\n", 28 | "\n", 29 | "HOOMD-blue is an object-oriented Python package. First, import the package:" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "import hoomd" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "The **Simulation** object combines all the elements of a simulation together and provides an interface to run the simulation. It consists of the simulation **state** and **operations** which act on that state. The simulation **state** includes the current box, bonds, particle positions, velocities, orientations, and other particle properties. **Operations** examine or modify the state. A simulation has *one* state, and *any number* of operations." 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## Selecting a device\n", 53 | "\n", 54 | "You must specify a **device** when constructing a **Simulation**. The **device** tells the simulation where to store the **state** and what processor to use when executing operations. HOOMD-blue can execute on the CPU:" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "cpu = hoomd.device.CPU()" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "Or the GPU:" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": { 77 | "tags": [ 78 | "raises-exception" 79 | ] 80 | }, 81 | "outputs": [], 82 | "source": [ 83 | "gpu = hoomd.device.GPU()" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "GPUs are highly parallel processors best suited for larger workloads.\n", 91 | "In typical systems with more than 4,000 particles a single GPU performs an order of magnitude faster than an entire CPU (all cores).\n", 92 | "Try your simulation on both to see what hardware can run the same number of time steps in less time." 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## Creating a Simulation\n", 100 | "\n", 101 | "Now, you can instantiate a **Simulation** with the chosen device." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "sim = hoomd.Simulation(device=cpu)" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "A newly constructed **Simulation** has no **state**:" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 5, 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "name": "stdout", 127 | "output_type": "stream", 128 | "text": [ 129 | "None\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "print(sim.state)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "And no integrator, updaters, or writers, which are types of **operations**:" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 6, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "None\n" 154 | ] 155 | } 156 | ], 157 | "source": [ 158 | "print(sim.operations.integrator)" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 7, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "name": "stdout", 168 | "output_type": "stream", 169 | "text": [ 170 | "[]\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "print(sim.operations.updaters[:])" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 8, 181 | "metadata": {}, 182 | "outputs": [ 183 | { 184 | "name": "stdout", 185 | "output_type": "stream", 186 | "text": [ 187 | "[]\n" 188 | ] 189 | } 190 | ], 191 | "source": [ 192 | "print(sim.operations.writers[:])" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "The remaining sections in this tutorial will show you how to populate a **Simulation** with **operations**, initialize the **state**, and run the simulation." 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": { 205 | "nbsphinx": "hidden" 206 | }, 207 | "source": [ 208 | "[Previous section](00-index.ipynb). [Next section](02-Performing-Hard-Particle-Monte-Carlo-Simulations.ipynb)." 209 | ] 210 | } 211 | ], 212 | "metadata": { 213 | "language_info": { 214 | "codemirror_mode": { 215 | "name": "ipython", 216 | "version": 3 217 | }, 218 | "file_extension": ".py", 219 | "mimetype": "text/x-python", 220 | "name": "python", 221 | "nbconvert_exporter": "python", 222 | "pygments_lexer": "ipython3", 223 | "version": "3.10.6" 224 | }, 225 | "record_timing": false 226 | }, 227 | "nbformat": 4, 228 | "nbformat_minor": 4 229 | } 230 | -------------------------------------------------------------------------------- /00-Introducing-HOOMD-blue/02-Performing-Hard-Particle-Monte-Carlo-Simulations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Performing Hard Particle Monte Carlo Simulations\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* What is hard particle Monte Carlo?\n", 14 | "* How do I set up a hard particle Monte Carlo simulation?\n", 15 | "\n", 16 | "### Objectives\n", 17 | "\n", 18 | "* Describe hard particle Monte Carlo simulations, **particle shape**, and **trial moves**.\n", 19 | "* Show how to initialize the **ConvexPolyhedron integrator**.\n", 20 | "* Explain the integrator parameters.\n", 21 | "* Introduce **time steps**.\n", 22 | "\n", 23 | "## Boilerplate code" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import math\n", 33 | "\n", 34 | "import hoomd" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "## Particle shape\n", 42 | "\n", 43 | "A hard particle Monte Carlo (HPMC) simulation represents particles as extended objects which are not allowed to overlap. \n", 44 | "There are no attractive or repulsive forces in the system.\n", 45 | "The **shape** of the particle alone controls how it interacts with other particles.\n", 46 | "Formally, the potential energy of the system is zero when there are no overlaps and infinite when there are.\n", 47 | "Purely hard interactions induce *effective attractions* between particles which can lead to ordered structures.\n", 48 | "For example, [hard regular octahedra will self-assemble into a bcc structure](https://doi.org/10.1038/ncomms14038). \n", 49 | "In this tutorial, you will learn how to run a simulation of hard octahedra and observe this behavior.\n", 50 | "\n", 51 | "![Octahedra self assembly](octahedra_assembly.png)\n", 52 | "\n", 53 | "## The integrator\n", 54 | "\n", 55 | "The **ConvexPolyhedron** **integrator** implements HPMC simulations - Create one:" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 2, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "mc = hoomd.hpmc.integrate.ConvexPolyhedron()" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "Set the `shape` *property* to define the **particle shape**.\n", 72 | "A convex polyhedron is defined by the convex hull of a [set of vertices](https://en.wikipedia.org/wiki/Octahedron):" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 3, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "mc.shape['octahedron'] = dict(vertices=[\n", 82 | " (-0.5, 0, 0),\n", 83 | " (0.5, 0, 0),\n", 84 | " (0, -0.5, 0),\n", 85 | " (0, 0.5, 0),\n", 86 | " (0, 0, -0.5),\n", 87 | " (0, 0, 0.5),\n", 88 | "])" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "## Trial moves\n", 96 | "\n", 97 | "During each **time step**, HPMC attempts `nselect` trial moves on each particle in the system. \n", 98 | "Each **trial move** is drawn from a pseudorandom number stream and may be either a *translation* or *rotation* move.\n", 99 | "*Translation moves* displace a particle a random distance (up to `d`) in a random direction.\n", 100 | "*Rotation moves* rotate the particle by a random angle about a random axis.\n", 101 | "Larger values of `a` lead to larger possible rotation moves.\n", 102 | "\n", 103 | "Any **trial move** whose shape overlaps with another particle is *rejected*, leaving the particle's position and orientation unchanged.\n", 104 | "Any **trial move** whose shape *does not* overlap with any other particle is *accepted*, setting the particle's position or orientation to the new value.\n", 105 | "\n", 106 | "`nselect`, `d`, and `a` are *properties* of the integrator:" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 4, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "mc.nselect = 2\n", 116 | "mc.d['octahedron'] = 0.15\n", 117 | "mc.a['octahedron'] = 0.2" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "## Setting the integrator" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 5, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "cpu = hoomd.device.CPU()\n", 134 | "sim = hoomd.Simulation(device=cpu, seed=1)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "An **integrator** is a type of **operation**. There can only be one **integrator** in a **Simulation** and it operates on the system **state** on every **time step**. Assign the HPMC **integrator** to the **Simulation** to use it:" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 6, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "sim.operations.integrator = mc" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "The `seed` value (passed to the simulation constructor above) selects the sequence of values in the pseudorandom number stream.\n", 158 | "Given the same initial condition and `seed`, HPMC simulations will produce exactly the same results.\n", 159 | "\n", 160 | "All operations that generate psuedorandom numbers use the seed set in the simulation.\n", 161 | "Whenever you add operations that utilize random numbers, you should set the seed to a non-default value." 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "Now you have a **Simulation** and a **ConvexPolyhedron integrator**, but can't run the simulation yet.\n", 169 | "You first need to define the system **state** for the **integrator** to operate on.\n", 170 | "The next section in this tutorial will show you how to initialize the **state**." 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": { 176 | "nbsphinx": "hidden" 177 | }, 178 | "source": [ 179 | "[Previous section](01-The-Simulation-Object.ipynb). [Next section](03-Initializing-the-System-State.ipynb)." 180 | ] 181 | } 182 | ], 183 | "metadata": { 184 | "language_info": { 185 | "codemirror_mode": { 186 | "name": "ipython", 187 | "version": 3 188 | }, 189 | "file_extension": ".py", 190 | "mimetype": "text/x-python", 191 | "name": "python", 192 | "nbconvert_exporter": "python", 193 | "pygments_lexer": "ipython3", 194 | "version": "3.10.6" 195 | }, 196 | "record_timing": false 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 4 200 | } 201 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/02-An-Initial-Custom-Action.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# An Initial Custom Action\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- How do I write a custom action in Python?\n", 14 | "- How do I wrap a custom action object?\n", 15 | "- How do I use a custom action in a simulation?\n", 16 | "\n", 17 | "### Objectives\n", 18 | "\n", 19 | "- Explain the steps in writing a custom action.\n", 20 | "- Demonstrate using a custom action in a simulation.\n", 21 | "\n", 22 | "## Writing a Custom Action\n", 23 | "\n", 24 | "First, import `hoomd`." 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "import hoomd" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "We create a single particle snapshot for initializing a\n", 41 | "simulation's state further down." 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "snap = hoomd.Snapshot()\n", 51 | "snap.particles.N = 1\n", 52 | "snap.particles.position[:] = [0, 0, 0]\n", 53 | "snap.particles.types = ['A']\n", 54 | "snap.particles.typeid[:] = [0]\n", 55 | "snap.configuration.box = [10, 10, 10, 0, 0, 0]" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "Create a custom action as a subclass of\n", 63 | "`hoomd.custom.Action`. Here we will create an action that prints\n", 64 | "the timestep to standard out." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 3, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "class PrintTimestep(hoomd.custom.Action):\n", 74 | "\n", 75 | " def act(self, timestep):\n", 76 | " print(timestep)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "We now have an action that can print out the simulation\n", 84 | "timestep. The logic of the action goes inside the `act` method.\n", 85 | "All actions must define this function, and it must take in\n", 86 | "the simulation timestep; this is passed in when the action is called\n", 87 | "in the HOOMD-blue run loop. (If you are wondering how to access\n", 88 | "simulation data, there is a mechanism for that which we will go over\n", 89 | "in the next section).\n", 90 | "\n", 91 | "Let's go ahead and create a `PrintTimestep` object." 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 4, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "custom_action = PrintTimestep()" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "## Wrapping Custom Actions\n", 108 | "\n", 109 | "To let an `Operations` object know what kind of action our\n", 110 | "custom action is, we must wrap it in a subclass of\n", 111 | "`hoomd.custom.CustomOperation`. We have three options as discussed in\n", 112 | "the previous section: an updater, writer, or tuner. Since our object\n", 113 | "does not modify simulation state or an object's hyperparameters, but\n", 114 | "writes the timestep to standard out, our action is a writer.\n", 115 | "`hoomd.write.CustomWriter` then is the correct class to wrap our custom\n", 116 | "action (`hoomd.update.CustomUpdater` and `hoomd.tune.CustomTuner` are\n", 117 | "for updaters and tuners respectively).\n", 118 | "\n", 119 | "Create a `CustomWriter` operation that will call the custom action when triggered:" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 5, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "custom_op = hoomd.write.CustomWriter(action=custom_action,\n", 129 | " trigger=hoomd.trigger.Periodic(100))" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "Notice that custom operations take triggers like other operations.\n", 137 | "\n", 138 | "## Using Custom Actions\n", 139 | "\n", 140 | "To use a custom opeation we must add it to a `hoomd.Operations` object.\n", 141 | "Thus, the steps to use a custom action in a simuluation are\n", 142 | "1. Instantiate the custom action object.\n", 143 | "2. Wrap the custom action in the appropriate custom operation class.\n", 144 | "3. Add the custom operation object to the appropriate container in a\n", 145 | " `hoomd.Operations` object." 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 6, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "cpu = hoomd.device.CPU()\n", 155 | "sim = hoomd.Simulation(device=cpu)" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "Initialize the state" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 7, 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "sim.create_state_from_snapshot(snap)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "Add the custom action wrapped by a `CustomWriter`:" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 8, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "sim.operations.writers.append(custom_op)" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "We can now run our simulation to see our custom action in work!" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 9, 200 | "metadata": {}, 201 | "outputs": [ 202 | { 203 | "name": "stdout", 204 | "output_type": "stream", 205 | "text": [ 206 | "100\n", 207 | "200\n", 208 | "300\n", 209 | "400\n", 210 | "500\n", 211 | "600\n", 212 | "700\n", 213 | "800\n", 214 | "900\n", 215 | "1000\n" 216 | ] 217 | } 218 | ], 219 | "source": [ 220 | "sim.run(1000)" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "In the next section we discuss some of the features of custom actions,\n", 228 | "before getting into non-trival examples in later sections." 229 | ] 230 | } 231 | ], 232 | "metadata": { 233 | "language_info": { 234 | "codemirror_mode": { 235 | "name": "ipython", 236 | "version": 3 237 | }, 238 | "file_extension": ".py", 239 | "mimetype": "text/x-python", 240 | "name": "python", 241 | "nbconvert_exporter": "python", 242 | "pygments_lexer": "ipython3", 243 | "version": "3.10.6" 244 | } 245 | }, 246 | "nbformat": 4, 247 | "nbformat_minor": 4 248 | } 249 | -------------------------------------------------------------------------------- /02-Logging/03-Storing-Particle-Shape.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Storing Particle Shape\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* How can I store particle shape for use with visualization tools?\n", 14 | "\n", 15 | "### Objectives\n", 16 | "\n", 17 | "* Demonstrate logging **type_shapes** to a **GSD** file.\n", 18 | "* Explain that OVITO can read this information.\n", 19 | "\n", 20 | "## Boilerplate code" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import gsd.hoomd\n", 30 | "import hoomd" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": { 37 | "jupyter": { 38 | "source_hidden": true 39 | }, 40 | "nbsphinx": "hidden", 41 | "tags": [] 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "import os\n", 46 | "\n", 47 | "fn = os.path.join(os.getcwd(), 'trajectory.gsd')\n", 48 | "![ -e \"$fn\" ] && rm \"$fn\"" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "## Particle Shape\n", 56 | "\n", 57 | "HPMC integrators and some anisotropic MD pair potentials model particles that have a well defined shape.\n", 58 | "You can save this shape definition to a **GSD** file for use in analysis and visualization workflows.\n", 59 | "In particular, [OVITO](https://www.ovito.org/) will read this shape information and render particles appropriately." 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Define the Simulation\n", 67 | "\n", 68 | "This section executes the hard particle simulation from a previous tutorial. \n", 69 | "See [*Introducing HOOMD-blue*](../00-Introducing-HOOMD-blue/00-index.ipynb) for a complete description of this code." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 3, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "cpu = hoomd.device.CPU()\n", 79 | "sim = hoomd.Simulation(device=cpu, seed=2)\n", 80 | "mc = hoomd.hpmc.integrate.ConvexPolyhedron()\n", 81 | "mc.shape['octahedron'] = dict(vertices=[\n", 82 | " (-0.5, 0, 0),\n", 83 | " (0.5, 0, 0),\n", 84 | " (0, -0.5, 0),\n", 85 | " (0, 0.5, 0),\n", 86 | " (0, 0, -0.5),\n", 87 | " (0, 0, 0.5),\n", 88 | "])\n", 89 | "sim.operations.integrator = mc\n", 90 | "sim.create_state_from_gsd(\n", 91 | " filename='../00-Introducing-HOOMD-blue/compressed.gsd')\n", 92 | "sim.run(0)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## Logging particle shape to a GSD file\n", 100 | "\n", 101 | "The **type_shapes** loggable quantity is a representation of the particle shape for each type following the [**type_shapes** specification](https://gsd.readthedocs.io/en/stable/shapes.html) for the **GSD** file format.\n", 102 | "In HPMC simulations, the integrator provides **type_shapes**:" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 4, 108 | "metadata": {}, 109 | "outputs": [ 110 | { 111 | "data": { 112 | "text/plain": [ 113 | "{'map_overlaps': 'sequence',\n", 114 | " 'overlaps': 'scalar',\n", 115 | " 'translate_moves': 'sequence',\n", 116 | " 'rotate_moves': 'sequence',\n", 117 | " 'mps': 'scalar',\n", 118 | " 'type_shapes': 'object'}" 119 | ] 120 | }, 121 | "execution_count": 4, 122 | "metadata": {}, 123 | "output_type": "execute_result" 124 | } 125 | ], 126 | "source": [ 127 | "mc.loggables" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 5, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "data": { 137 | "text/plain": [ 138 | "[{'type': 'ConvexPolyhedron',\n", 139 | " 'rounding_radius': 0,\n", 140 | " 'vertices': [[-0.5, 0, 0],\n", 141 | " [0.5, 0, 0],\n", 142 | " [0, -0.5, 0],\n", 143 | " [0, 0.5, 0],\n", 144 | " [0, 0, -0.5],\n", 145 | " [0, 0, 0.5]]}]" 146 | ] 147 | }, 148 | "execution_count": 5, 149 | "metadata": {}, 150 | "output_type": "execute_result" 151 | } 152 | ], 153 | "source": [ 154 | "mc.type_shapes" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "Add the **type_shapes** quantity to a **Logger**." 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 6, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "logger = hoomd.logging.Logger()\n", 171 | "logger.add(mc, quantities=['type_shapes'])" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "Write the simulation trajectory to a **GSD** file along with the logged quantities:" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 7, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "gsd_writer = hoomd.write.GSD(filename='trajectory.gsd',\n", 188 | " trigger=hoomd.trigger.Periodic(10000),\n", 189 | " mode='xb',\n", 190 | " filter=hoomd.filter.All(),\n", 191 | " logger=logger)\n", 192 | "sim.operations.writers.append(gsd_writer)" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "Run the simulation:" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 8, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "sim.run(20000)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "As discussed in the previous section, flush the write buffer (this is not necessary in typical workflows)." 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 9, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "gsd_writer.flush()" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "## Reading logged shapes from a GSD file\n", 232 | "\n", 233 | "You can access the shape from scripts using the `gsd` package:" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 10, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "traj = gsd.hoomd.open('trajectory.gsd', mode='r')" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "**type_shapes** is a special quantity available via `particles.type_shapes` rather than the `log` dictionary:" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 11, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "data": { 259 | "text/plain": [ 260 | "[{'type': 'ConvexPolyhedron',\n", 261 | " 'rounding_radius': 0,\n", 262 | " 'vertices': [[-0.5, 0, 0],\n", 263 | " [0.5, 0, 0],\n", 264 | " [0, -0.5, 0],\n", 265 | " [0, 0.5, 0],\n", 266 | " [0, 0, -0.5],\n", 267 | " [0, 0, 0.5]]}]" 268 | ] 269 | }, 270 | "execution_count": 11, 271 | "metadata": {}, 272 | "output_type": "execute_result" 273 | } 274 | ], 275 | "source": [ 276 | "traj[0].particles.type_shapes" 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "Open the file in OVITO and it will read the shape definition and render particles appropriately." 284 | ] 285 | }, 286 | { 287 | "cell_type": "markdown", 288 | "metadata": {}, 289 | "source": [ 290 | "In this section, you have logged particle shape to a GSD file during a simulation so that visualization and analysis tools can access it.\n", 291 | "The next section shows how to write formatted output." 292 | ] 293 | }, 294 | { 295 | "cell_type": "markdown", 296 | "metadata": { 297 | "nbsphinx": "hidden" 298 | }, 299 | "source": [ 300 | "[Previous section](02-Saving-Array-Quantities.ipynb) / [Next Section](04-Writing-Formatted-Output.ipynb)" 301 | ] 302 | } 303 | ], 304 | "metadata": { 305 | "language_info": { 306 | "codemirror_mode": { 307 | "name": "ipython", 308 | "version": 3 309 | }, 310 | "file_extension": ".py", 311 | "mimetype": "text/x-python", 312 | "name": "python", 313 | "nbconvert_exporter": "python", 314 | "pygments_lexer": "ipython3", 315 | "version": "3.10.6" 316 | }, 317 | "record_timing": false 318 | }, 319 | "nbformat": 4, 320 | "nbformat_minor": 4 321 | } 322 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/03-Custom-Action-Features.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Custom Action Features\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- How do I access simulation state information?\n", 14 | "- How do I create loggable quantities in custom actions?\n", 15 | "- What are other features provided by the custom action/operation\n", 16 | " API?\n", 17 | "\n", 18 | "### Objectives\n", 19 | "\n", 20 | "- Explain how to access simulation state in a custom action.\n", 21 | "- Explain how to expose loggable quantities in a custom action.\n", 22 | "- Demonstrate other miscellaneous features of custom actions.\n", 23 | "\n", 24 | "## Boilerplate Code" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "import hoomd\n", 34 | "\n", 35 | "cpu = hoomd.device.CPU()\n", 36 | "sim = hoomd.Simulation(device=cpu)\n", 37 | "\n", 38 | "snap = hoomd.Snapshot()\n", 39 | "snap.particles.N = 1\n", 40 | "snap.particles.position[:] = [0, 0, 0]\n", 41 | "snap.particles.types = ['A']\n", 42 | "snap.particles.typeid[:] = [0]\n", 43 | "snap.configuration.box = [10, 10, 10, 0, 0, 0]\n", 44 | "\n", 45 | "sim.create_state_from_snapshot(snap)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## How do I access simulation state?\n", 53 | "\n", 54 | "By the time that a custom action will have its `act` method called\n", 55 | "it will have an attribute `_state` accessible to it which is the\n", 56 | "simulation state for the simulation it is associated with. The behavior\n", 57 | "of this is controlled in the `hoomd.custom.Action.attach` method. The\n", 58 | "method takes in a simulation object and performs any necessary set-up\n", 59 | "for the action call `act`. By default, the method stores the simulation\n", 60 | "state in the `_state` attribute.\n", 61 | "\n", 62 | "We will create two custom actions class to show this. In one, we will\n", 63 | "not modify the `attach` method, and in the other we will make `attach`\n", 64 | "method also print out some information." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 2, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "class PrintTimestep(hoomd.custom.Action):\n", 74 | "\n", 75 | " def act(self, timestep):\n", 76 | " print(timestep)\n", 77 | "\n", 78 | "\n", 79 | "class NotifyAttachWithPrint(hoomd.custom.Action):\n", 80 | "\n", 81 | " def attach(self, simulation):\n", 82 | " print(f\"Has '_state' attribute {hasattr(self, '_state')}.\")\n", 83 | " super().attach(simulation)\n", 84 | " print(f\"Has '_state' attribute {hasattr(self, '_state')}.\")\n", 85 | "\n", 86 | " def act(self, timestep):\n", 87 | " print(timestep)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "Like in the previous section these are both writers. We will go ahead\n", 95 | "and wrap them and see what happens when we try to run the simulation." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 3, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "print_timestep = PrintTimestep()\n", 105 | "print_timestep_operation = hoomd.write.CustomWriter(\n", 106 | " action=print_timestep, trigger=hoomd.trigger.Periodic(10))\n", 107 | "sim.operations.writers.append(print_timestep_operation)\n", 108 | "sim.run(0)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 4, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "Has '_state' attribute False.\n", 121 | "Has '_state' attribute True.\n" 122 | ] 123 | } 124 | ], 125 | "source": [ 126 | "sim.operations -= print_timestep_operation\n", 127 | "print_timestep_with_notify = NotifyAttachWithPrint()\n", 128 | "sim.operations.writers.append(\n", 129 | " hoomd.write.CustomWriter(action=print_timestep_with_notify,\n", 130 | " trigger=hoomd.trigger.Periodic(10)))\n", 131 | "sim.run(0)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": {}, 137 | "source": [ 138 | "## Loggable Quantities in Custom Actions\n", 139 | "\n", 140 | "Custom actions can hook into HOOMD-blue's logging subsystem by using\n", 141 | "the `hoomd.logging.log` decorator to document which methods/properties\n", 142 | "of a custom action are loggable. See the documentation on `hoomd.logging.log` and `hoomd.logging.TypeFlags` for complete documenation of the decorator and loggable types.\n", 143 | "\n", 144 | "In general, `log` as a decorator takes optional arguments that control \n", 145 | "whether to make a method a property, what type the loggable quantity is, \n", 146 | "and whether the quantity should be logged by default.\n", 147 | "\n", 148 | "Rather than elaborate, we will use an example to explain these attributes." 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 5, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [ 157 | "class ActionWithLoggables(hoomd.custom.Action):\n", 158 | "\n", 159 | " @hoomd.logging.log\n", 160 | " def scalar_property_loggable(self):\n", 161 | " return 42\n", 162 | "\n", 163 | " @hoomd.logging.log(category='string')\n", 164 | " def string_loggable(self):\n", 165 | " return \"I am a string loggable.\"\n", 166 | "\n", 167 | " def act(self, timestep):\n", 168 | " pass\n", 169 | "\n", 170 | "\n", 171 | "action = ActionWithLoggables()" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 6, 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "data": { 181 | "text/plain": [ 182 | "42" 183 | ] 184 | }, 185 | "execution_count": 6, 186 | "metadata": {}, 187 | "output_type": "execute_result" 188 | } 189 | ], 190 | "source": [ 191 | "action.scalar_property_loggable" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 7, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "'I am a string loggable.'" 203 | ] 204 | }, 205 | "execution_count": 7, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | } 209 | ], 210 | "source": [ 211 | "action.string_loggable" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "## Custom Operation Wrapping\n", 219 | "\n", 220 | "Another feature of the custom action API is that when an object is\n", 221 | "wrapped by a custom operation object (which is necessary to add a\n", 222 | "custom action to a simulation), the action's attributes are \n", 223 | "available through the operation object as if the operation were the\n", 224 | "action. For example, we will wrap `action` from the previous code block\n", 225 | "in a `CustomWriter` and access its attributes that way.\n", 226 | "\n", 227 | "Due to this wrapping the attribute `trigger` should not exist in your\n", 228 | "custom action." 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": 8, 234 | "metadata": {}, 235 | "outputs": [ 236 | { 237 | "data": { 238 | "text/plain": [ 239 | "42" 240 | ] 241 | }, 242 | "execution_count": 8, 243 | "metadata": {}, 244 | "output_type": "execute_result" 245 | } 246 | ], 247 | "source": [ 248 | "custom_op = hoomd.write.CustomWriter(action=action, trigger=100)\n", 249 | "custom_op.scalar_property_loggable" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 9, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "data": { 259 | "text/plain": [ 260 | "'I am a string loggable.'" 261 | ] 262 | }, 263 | "execution_count": 9, 264 | "metadata": {}, 265 | "output_type": "execute_result" 266 | } 267 | ], 268 | "source": [ 269 | "custom_op.string_loggable" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "## Summary\n", 277 | "\n", 278 | "These summarize most of the unique features of custom actions in Python.\n", 279 | "They are\n", 280 | "- Accessing simulation state through `_state`\n", 281 | "- Exposing loggable quantities\n", 282 | "- Accessing action attributes through custom operation wrapper\n", 283 | "\n", 284 | "With this information, you could write almost any action that is\n", 285 | "possible to write in Python for use in HOOMD-blue. The remain tutorial\n", 286 | "sections will focus on concrete examples and show some tricks to get the\n", 287 | "best performance. For the full list of accessible features for custom\n", 288 | "actions see the reference documentation." 289 | ] 290 | } 291 | ], 292 | "metadata": { 293 | "language_info": { 294 | "codemirror_mode": { 295 | "name": "ipython", 296 | "version": 3 297 | }, 298 | "file_extension": ".py", 299 | "mimetype": "text/x-python", 300 | "name": "python", 301 | "nbconvert_exporter": "python", 302 | "pygments_lexer": "ipython3", 303 | "version": "3.10.6" 304 | } 305 | }, 306 | "nbformat": 4, 307 | "nbformat_minor": 4 308 | } 309 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/06-Improving-Performance.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Improving Performance\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- How can I write custom actions to be as efficient as possible?\n", 14 | "\n", 15 | "### Objectives\n", 16 | "\n", 17 | "- Mention common means for improving performance.\n", 18 | "- Demonstrate using the local snapshot API for increased performance.\n", 19 | "\n", 20 | "## Boilerplate Code" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "from numbers import Number\n", 30 | "\n", 31 | "import hoomd\n", 32 | "import hoomd.md as md\n", 33 | "import numpy as np\n", 34 | "\n", 35 | "cpu = hoomd.device.CPU()\n", 36 | "sim = hoomd.Simulation(cpu, seed=1)\n", 37 | "\n", 38 | "# Create a simple cubic configuration of particles\n", 39 | "N = 12 # particles per box direction\n", 40 | "box_L = 50 # box dimension\n", 41 | "\n", 42 | "snap = hoomd.Snapshot(cpu.communicator)\n", 43 | "snap.configuration.box = [box_L] * 3 + [0, 0, 0]\n", 44 | "snap.particles.N = N**3\n", 45 | "x, y, z = np.meshgrid(*(np.linspace(-box_L / 2, box_L / 2, N, endpoint=False),)\n", 46 | " * 3)\n", 47 | "positions = np.array((x.ravel(), y.ravel(), z.ravel())).T\n", 48 | "snap.particles.position[:] = positions\n", 49 | "snap.particles.types = ['A']\n", 50 | "snap.particles.typeid[:] = 0\n", 51 | "\n", 52 | "sim.create_state_from_snapshot(snap)\n", 53 | "\n", 54 | "sim.state.thermalize_particle_momenta(hoomd.filter.All(), 1.)\n", 55 | "\n", 56 | "lj = md.pair.LJ(nlist=md.nlist.Cell(buffer=0.4))\n", 57 | "lj.params[('A', 'A')] = {'epsilon': 1., 'sigma': 1.}\n", 58 | "lj.r_cut[('A', 'A')] = 2.5\n", 59 | "integrator = md.Integrator(\n", 60 | " methods=[md.methods.ConstantVolume(hoomd.filter.All())],\n", 61 | " forces=[lj],\n", 62 | " dt=0.005)\n", 63 | "\n", 64 | "sim.operations += integrator\n", 65 | "\n", 66 | "\n", 67 | "class GaussianVariant(hoomd.variant.Variant):\n", 68 | "\n", 69 | " def __init__(self, mean, std):\n", 70 | " hoomd.variant.Variant.__init__(self)\n", 71 | " self.mean = mean\n", 72 | " self.std = std\n", 73 | "\n", 74 | " def __call__(self, timestep):\n", 75 | " return rng.normal(self.mean, self.std)\n", 76 | "\n", 77 | "\n", 78 | "energy = GaussianVariant(0.1, 0.001)\n", 79 | "sim.run(0)\n", 80 | "rng = np.random.default_rng(1245)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "## General Guidelines\n", 88 | "\n", 89 | "When trying to improve the performance of custom actions, the first\n", 90 | "step is always to profile the class. Python comes with profiling tools\n", 91 | "that can be used to determine bottlenecks in a custom action's\n", 92 | "performance. In addition, there are many external visualization and\n", 93 | "profiling tools available. However, after profiling here are some tips\n", 94 | "that should help improve performance.\n", 95 | "\n", 96 | "* `State.get_snapshot` aggregates data across MPI ranks and is $O(n)$ to\n", 97 | " construct and setting the state to a new snapshot $O(n)$ as well.\n", 98 | " However, `hoomd.State.cpu_local_snaphshot` or\n", 99 | " `hoomd.State.gpu_local_snapshot` are on order $O(1)$ to construct\n", 100 | " and modifying data in a local snapshot is $O(1)$ as well.\n", 101 | "* HOOMD-blue makes use of properties heavily. Since users can\n", 102 | " change the system state in Python at any point, we must recompute\n", 103 | " many of these quantities every time they are queried. If you are\n", 104 | " using something like `hoomd.md.pair.LJ.energies` multiple times,\n", 105 | " it will be more performant to first store the values and\n", 106 | " then use that copy.\n", 107 | "* Avoid for loops for numerical calculation. Try to utilize NumPy\n", 108 | " broadcasting or existing functions in NumPy or Scipy on the CPU\n", 109 | " or CuPy on the GPU.\n", 110 | "\n", 111 | "## Improve InsertEnergyUpdater\n", 112 | "\n", 113 | "As an example, we will improve the performance of the \n", 114 | "`InsertEnergyUpdater`. Specifically we will change to use\n", 115 | "the `cpu_local_snapshot` to update particle velocity. \n", 116 | "We will use the `%%timeit` magic function for timing the\n", 117 | "simulation's run time before and after our optimization.\n", 118 | "To highlight the differnce, we will run the updater every\n", 119 | "timestep." 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 2, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "class InsertEnergyUpdater(hoomd.custom.Action):\n", 129 | "\n", 130 | " def __init__(self, energy):\n", 131 | " self._energy = energy\n", 132 | "\n", 133 | " @property\n", 134 | " def energy(self):\n", 135 | " return self._energy\n", 136 | "\n", 137 | " @energy.setter\n", 138 | " def energy(self, new_energy):\n", 139 | " if isinstance(new_energy, Number):\n", 140 | " self._energy = hoomd.variant.Constant(new_energy)\n", 141 | " elif isinstance(new_energy, hoomd.variant.Variant):\n", 142 | " self._energy = new_energy\n", 143 | " else:\n", 144 | " raise ValueError(\"energy must be a variant or real number.\")\n", 145 | "\n", 146 | " def act(self, timestep):\n", 147 | " snap = self._state.get_snapshot()\n", 148 | " if snap.communicator.rank == 0:\n", 149 | " particle_i = rng.integers(snap.particles.N)\n", 150 | " mass = snap.particles.mass[particle_i]\n", 151 | " direction = self._get_direction()\n", 152 | " magnitude = np.sqrt(2 * self.energy(timestep) / mass)\n", 153 | " velocity = direction * magnitude\n", 154 | " old_velocity = snap.particles.velocity[particle_i]\n", 155 | " new_velocity = old_velocity + velocity\n", 156 | " snap.particles.velocity[particle_i] = velocity\n", 157 | " self._state.set_snapshot(snap)\n", 158 | "\n", 159 | " @staticmethod\n", 160 | " def _get_direction():\n", 161 | " theta, z = rng.random(2)\n", 162 | " theta *= 2 * np.pi\n", 163 | " z = 2 * (z - 0.5)\n", 164 | " return np.array([\n", 165 | " np.sqrt(1 - (z * z)) * np.cos(theta),\n", 166 | " np.sqrt(1 - (z * z)) * np.sin(theta), z\n", 167 | " ])" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 3, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "energy_action = InsertEnergyUpdater(energy)\n", 177 | "energy_operation = hoomd.update.CustomUpdater(action=energy_action, trigger=1)\n", 178 | "sim.operations.updaters.append(energy_operation)" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 4, 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "name": "stdout", 188 | "output_type": "stream", 189 | "text": [ 190 | "83.8 ms ± 1.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" 191 | ] 192 | } 193 | ], 194 | "source": [ 195 | "%%timeit\n", 196 | "sim.run(100)" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "We now show the profile for the optimized code which\n", 204 | "uses the `cpu_local_snapshot` for updating velocities." 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 5, 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "class InsertEnergyUpdater(hoomd.custom.Action):\n", 214 | "\n", 215 | " def __init__(self, energy):\n", 216 | " self._energy = energy\n", 217 | "\n", 218 | " @property\n", 219 | " def energy(self):\n", 220 | " return self._energy\n", 221 | "\n", 222 | " @energy.setter\n", 223 | " def energy(self, new_energy):\n", 224 | " if isinstance(new_energy, Number):\n", 225 | " self._energy = hoomd.variant.Constant(new_energy)\n", 226 | " elif isinstance(new_energy, hoomd.variant.Variant):\n", 227 | " self._energy = new_energy\n", 228 | " else:\n", 229 | " raise ValueError(\"energy must be a variant or real number.\")\n", 230 | "\n", 231 | " def attach(self, simulation):\n", 232 | " self._state = simulation.state\n", 233 | " self._comm = simulation.device.communicator\n", 234 | "\n", 235 | " def detach(self):\n", 236 | " del self._state\n", 237 | " del self._comm\n", 238 | "\n", 239 | " def act(self, timestep):\n", 240 | " part_tag = rng.integers(self._state.N_particles)\n", 241 | " direction = self._get_direction()\n", 242 | " energy = self.energy(timestep)\n", 243 | " with self._state.cpu_local_snapshot as snap:\n", 244 | " # We restrict the computation to the MPI\n", 245 | " # rank containing the particle if applicable.\n", 246 | " # By checking if multiple MPI ranks exist first\n", 247 | " # we can avoid for checking inclusion of a tag id\n", 248 | " # in an array.\n", 249 | " if (self._comm.num_ranks <= 1 or part_tag in snap.particles.tag):\n", 250 | " i = snap.particles.rtag[part_tag]\n", 251 | " mass = snap.particles.mass[i]\n", 252 | " magnitude = np.sqrt(2 * energy / mass)\n", 253 | " velocity = direction * magnitude\n", 254 | " old_velocity = snap.particles.velocity[i]\n", 255 | " new_velocity = old_velocity + velocity\n", 256 | " snap.particles.velocity[i] = new_velocity\n", 257 | "\n", 258 | " @staticmethod\n", 259 | " def _get_direction():\n", 260 | " theta, z = rng.random(2)\n", 261 | " theta *= 2 * np.pi\n", 262 | " z = 2 * (z - 0.5)\n", 263 | " return np.array([\n", 264 | " np.sqrt(1 - (z * z)) * np.cos(theta),\n", 265 | " np.sqrt(1 - (z * z)) * np.sin(theta), z\n", 266 | " ])" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 6, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "# Create and add our modified custom updater\n", 276 | "sim.operations -= energy_operation\n", 277 | "energy_action = InsertEnergyUpdater(energy)\n", 278 | "energy_operation = hoomd.update.CustomUpdater(action=energy_action, trigger=1)\n", 279 | "sim.operations.updaters.append(energy_operation)" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 7, 285 | "metadata": {}, 286 | "outputs": [ 287 | { 288 | "name": "stdout", 289 | "output_type": "stream", 290 | "text": [ 291 | "22.6 ms ± 180 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" 292 | ] 293 | } 294 | ], 295 | "source": [ 296 | "%%timeit\n", 297 | "sim.run(100)" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "As can be seen the simulation with the new\n", 305 | "EnergyInsertUpdater about an order of magnitude faster\n", 306 | "with a system size of $12^3 = 1728$,\n", 307 | "by virtue of the local snapshot modification having\n", 308 | "$O(1)$ time complexity. At larger system sizes this\n", 309 | "change will grow to be even more substantial.\n", 310 | "\n", 311 | "This concludes the tutorial on custom actions in Python. For\n", 312 | "more information see the API documentation." 313 | ] 314 | } 315 | ], 316 | "metadata": { 317 | "language_info": { 318 | "codemirror_mode": { 319 | "name": "ipython", 320 | "version": 3 321 | }, 322 | "file_extension": ".py", 323 | "mimetype": "text/x-python", 324 | "name": "python", 325 | "nbconvert_exporter": "python", 326 | "pygments_lexer": "ipython3", 327 | "version": "3.10.6" 328 | } 329 | }, 330 | "nbformat": 4, 331 | "nbformat_minor": 4 332 | } 333 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/01-Introduction-to-MPI.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction to MPI\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* What is MPI? \n", 14 | "* Why should I run my simulations in parallel? \n", 15 | "* How can I execute scripts in parallel?\n", 16 | "\n", 17 | "### Objectives\n", 18 | "\n", 19 | "* Describe **MPI**.\n", 20 | "* Explain how **MPI** can provide faster **performance** on **HPC** systems.\n", 21 | "* Show how to write a **single program** that can execute in serial or parallel.\n", 22 | "* Demonstrate how to execute that program with **mpirun**.\n", 23 | "* Explain how **strong scaling** enables higher performance at the cost of some **efficiency**." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## Introduction\n", 31 | "\n", 32 | "**MPI** (message passing interface) is a library that enables programs to execute in parallel.\n", 33 | "**MPI** is commonly available on **HPC** (high performance computing) clusters.\n", 34 | "**HOOMD-blue** uses **MPI** to execute on many CPUs and/or GPUs.\n", 35 | "Using more resources in parallel can provide higher **performance** than the same simulation run on one CPU core or one GPU." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## The Simulation Script\n", 43 | "\n", 44 | "This tutorial executes the Lennard-Jones particle simulation from a previous tutorial. \n", 45 | "See [*Introducing Molecular Dyamics*](../01-Introducing-Molecular-Dynamics/00-index.ipynb) for a complete description of this code.\n", 46 | "\n", 47 | "
\n", 48 | " You can also run HPMC and other types of simulations in HOOMD-blue using MPI. \n", 49 | " All operations in HOOMD-blue support MPI unless otherwise noted in their documentation.\n", 50 | "
" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 1, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "data": { 60 | "text/plain": [ 61 | "\u001b[0;32mimport\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m\u001b[0m\n", 62 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 63 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Initialize the simulation.\u001b[0m\u001b[0;34m\u001b[0m\n", 64 | "\u001b[0;34m\u001b[0m\u001b[0mdevice\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCPU\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 65 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSimulation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mseed\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 66 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_state_from_gsd\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'random.gsd'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 67 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 68 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Set the operations for a Lennard-Jones particle simulation.\u001b[0m\u001b[0;34m\u001b[0m\n", 69 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mIntegrator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdt\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.005\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 70 | "\u001b[0;34m\u001b[0m\u001b[0mcell\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnlist\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCell\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbuffer\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 71 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpair\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLJ\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnlist\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 72 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'A'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'A'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mepsilon\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msigma\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 73 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mr_cut\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'A'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'A'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2.5\u001b[0m\u001b[0;34m\u001b[0m\n", 74 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mforces\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 75 | "\u001b[0;34m\u001b[0m\u001b[0mnvt\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mConstantVolume\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", 76 | "\u001b[0;34m\u001b[0m \u001b[0mfilter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAll\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", 77 | "\u001b[0;34m\u001b[0m \u001b[0mthermostat\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthermostats\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mBussi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkT\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1.5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 78 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnvt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 79 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moperations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mintegrator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mintegrator\u001b[0m\u001b[0;34m\u001b[0m\n", 80 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 81 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Run a short time before measuring performance.\u001b[0m\u001b[0;34m\u001b[0m\n", 82 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m100\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 83 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 84 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Run the simulation and print the performance.\u001b[0m\u001b[0;34m\u001b[0m\n", 85 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 86 | "\u001b[0;34m\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnotice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'{sim.tps}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n" 87 | ] 88 | }, 89 | "metadata": {}, 90 | "output_type": "display_data" 91 | } 92 | ], 93 | "source": [ 94 | "%pycat lj_performance.py" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "`lj_performance.py` is a file in the same directory as this notebook. Due to the way **MPI** launches parallel jobs, the code **must** be in a file instead of a notebook cell. `%pycat` is an IPython magic command that displays the contents of a Python file with syntax highlighting.\n", 102 | "\n", 103 | "Compare this script to the one used in [*Introducing Molecular Dyamics*](../01-Introducing-Molecular-Dynamics/00-index.ipynb).\n", 104 | "The only difference is the addition of `device.notice(f'{sim.tps}')` which prints the performance in time steps per second.\n", 105 | "The same script can be run in serial or in parallel on different numbers of CPU cores." 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Run the simulation with MPI\n", 113 | "\n", 114 | "Use the **MPI** launcher `mpirun` to execute this script in parallel on any number of CPU cores given by the `-n` option.\n", 115 | "\n", 116 | "
\n", 117 | " Your HPC cluster may use a different launcher that may take different arguments, such as srun, mpiexec, ibrun, or jsrun.\n", 118 | " See your cluster's documentation to find the right launcher to use.\n", 119 | "
\n", 120 | "\n", 121 | "
\n", 122 | " In Jupyter, the \"!\" magic command is equivalent to typing the given command in a shell.\n", 123 | "
" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 2, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "name": "stdout", 133 | "output_type": "stream", 134 | "text": [ 135 | "336.00885988161735\n" 136 | ] 137 | } 138 | ], 139 | "source": [ 140 | "!mpirun -n 1 python3 lj_performance.py" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 3, 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "notice(2): Using domain decomposition: n_x = 1 n_y = 1 n_z = 2.\n", 153 | "604.2365441053402\n" 154 | ] 155 | } 156 | ], 157 | "source": [ 158 | "!mpirun -n 2 python3 lj_performance.py" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 4, 164 | "metadata": {}, 165 | "outputs": [ 166 | { 167 | "name": "stdout", 168 | "output_type": "stream", 169 | "text": [ 170 | "notice(2): Using domain decomposition: n_x = 1 n_y = 2 n_z = 2.\n", 171 | "1086.799408346402\n" 172 | ] 173 | } 174 | ], 175 | "source": [ 176 | "!mpirun -n 4 python3 lj_performance.py" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "The simulation runs faster on more CPU cores.\n", 184 | "This sequence of simulations demonstrates **strong scaling** where a simulation of a fixed number of particles executes in less time on more parallel resources.\n", 185 | "You can compute the **efficiency** of the scaling by taking the performance on **n** cores divided by the performance on one core divided by **n**.\n", 186 | "**Strong scaling** rarely nears 100% efficiency, so it will use more total resources than executing with `-n 1`.\n", 187 | "However, a `-n 1` simulation may take months to complete while a `-n 64` one takes days - making your research much more productive even it if uses a moderately larger amount of **HPC** resources." 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "## Summary\n", 195 | "\n", 196 | "In this section, you have executed a HOOMD-blue simulation script on 1, 2, and 4 CPU cores using MPI and observed the performance.\n", 197 | "The next section of this tutorial explains how HOOMD-blue splits the domain of the simulation and how you should structure your scripts." 198 | ] 199 | } 200 | ], 201 | "metadata": { 202 | "language_info": { 203 | "codemirror_mode": { 204 | "name": "ipython", 205 | "version": 3 206 | }, 207 | "file_extension": ".py", 208 | "mimetype": "text/x-python", 209 | "name": "python", 210 | "nbconvert_exporter": "python", 211 | "pygments_lexer": "ipython3", 212 | "version": "3.10.6" 213 | }, 214 | "record_timing": false 215 | }, 216 | "nbformat": 4, 217 | "nbformat_minor": 4 218 | } 219 | -------------------------------------------------------------------------------- /05-Organizing-and-Executing-Simulations/01-Organizing-Data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Organizing Data\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* How can I organize data from many simulations?\n", 14 | "* How do I relate the parameters of the simulation to the data?\n", 15 | "\n", 16 | "### Objectives\n", 17 | "\n", 18 | "* Define a **data space** that organizes simulation output into **directories** based on **state point** parameters.\n", 19 | "* Demonstrate how to use **signac** to create a **data space**.\n", 20 | "* **Initialize** a **data space** with hard particle Monte Carlo simulations at a selected volume fractions.\n", 21 | "* Show how to store computed results in the **job document**.\n", 22 | "\n", 23 | "## Boilerplate code" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import itertools\n", 33 | "import math\n", 34 | "\n", 35 | "import gsd.hoomd\n", 36 | "import hoomd\n", 37 | "import numpy" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": { 44 | "jupyter": { 45 | "source_hidden": true 46 | }, 47 | "nbsphinx": "hidden", 48 | "tags": [] 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "import os\n", 53 | "\n", 54 | "fn = os.path.join(os.getcwd(), 'signac.rc')\n", 55 | "![ -e \"$fn\" ] && rm \"$fn\"\n", 56 | "fn = os.path.join(os.getcwd(), 'workspace')\n", 57 | "![ -e \"$fn\" ] && rm -rf \"$fn\"" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "## Research question\n", 65 | "\n", 66 | "The [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) tutorial shows how to execute a *single* simulation of hard octahedra and how they self-assemble into a crystal structure.\n", 67 | "You might want to answer the question \"At what volume fraction is the phase transition from fluid to crystal?\".\n", 68 | "One way to find out is to execute simulations at many volume fractions and examine the resulting equilibrium structures.\n", 69 | "When performing such a study, you may want to explore simulations at different system sizes, repeat the simulation with different random number seeds, or examine the effects of changing other parameters.\n", 70 | "\n", 71 | "The unique set of parameters for each simulation is a **state point** which you can represent in a Python dictionary:" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 3, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "data": { 81 | "text/plain": [ 82 | "{'N_particles': 128, 'volume_fraction': 0.6, 'seed': 20}" 83 | ] 84 | }, 85 | "execution_count": 3, 86 | "metadata": {}, 87 | "output_type": "execute_result" 88 | } 89 | ], 90 | "source": [ 91 | "statepoint = dict(N_particles=128, volume_fraction=0.6, seed=20)\n", 92 | "statepoint" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "In your own research, you will execute different types of simulation with different parameters.\n", 100 | "Follow the example provided in this tutorial and apply the same concepts organize and execute the simulations for your work." 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "## Data space" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Each simulation you execute will generate several output files.\n", 115 | "Store these in a **directory** uniquely assigned to each **state point**.\n", 116 | "The collection of **directories** is a **data space**.\n", 117 | "Use [**signac**](https://docs.signac.io/en/latest/) to automatically name and create the **directories**." 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 4, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "import signac" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "metadata": {}, 132 | "source": [ 133 | "A **signac** project represents the entire **data space** stored on disk with associated metadata.\n", 134 | "The method `init_project` creates a **signac** project in the *current working directory* by placing a `signac.rc` file with the project metadata and a `workspace` directory to hold the **directories** of the **data space**. The `name` argument is required with **signac** 1.x, but the value of the name is used only to populate `signac.rc`.\n", 135 | "\n", 136 | "Create the project:" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 5, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "project = signac.init_project()" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 6, 151 | "metadata": {}, 152 | "outputs": [ 153 | { 154 | "name": "stdout", 155 | "output_type": "stream", 156 | "text": [ 157 | "project = None\n", 158 | "schema_version = 1\n" 159 | ] 160 | } 161 | ], 162 | "source": [ 163 | "!cat signac.rc" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "A **signac job** is a container that holds the **state point**, assigned **directory**, and a **job document**." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 7, 176 | "metadata": {}, 177 | "outputs": [], 178 | "source": [ 179 | "job = project.open_job(statepoint)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 8, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "data": { 189 | "text/plain": [ 190 | "{'N_particles': 128, 'volume_fraction': 0.6, 'seed': 20}" 191 | ] 192 | }, 193 | "execution_count": 8, 194 | "metadata": {}, 195 | "output_type": "execute_result" 196 | } 197 | ], 198 | "source": [ 199 | "job.statepoint" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "The **job document** is a persistent dictionary where you can record the job's status." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 9, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "data": { 216 | "text/plain": [ 217 | "{}" 218 | ] 219 | }, 220 | "execution_count": 9, 221 | "metadata": {}, 222 | "output_type": "execute_result" 223 | } 224 | ], 225 | "source": [ 226 | "job.document" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "The first file for each simulation is the initial condition.\n", 234 | "Here is the initialization code from the [Introducing HOOMD-blue](../00-Introducing-HOOMD-blue/00-index.ipynb) tutorial, encapsulated in a function that takes a **signac job** as an argument:" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 10, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "def init(job):\n", 244 | " # Place a number of particles as indicated by the signac job's state point.\n", 245 | " K = math.ceil(job.statepoint.N_particles**(1 / 3))\n", 246 | " spacing = 1.2\n", 247 | " L = K * spacing\n", 248 | " x = numpy.linspace(-L / 2, L / 2, K, endpoint=False)\n", 249 | " position = list(itertools.product(x, repeat=3))\n", 250 | " position = position[0:job.statepoint.N_particles]\n", 251 | " orientation = [(1, 0, 0, 0)] * job.statepoint.N_particles\n", 252 | "\n", 253 | " frame = gsd.hoomd.Frame()\n", 254 | " frame.particles.N = job.statepoint.N_particles\n", 255 | " frame.particles.position = position\n", 256 | " frame.particles.orientation = orientation\n", 257 | " frame.particles.typeid = [0] * job.statepoint.N_particles\n", 258 | " frame.particles.types = ['octahedron']\n", 259 | " frame.configuration.box = [L, L, L, 0, 0, 0]\n", 260 | "\n", 261 | " # Write `lattice.gsd` to the signac job's directory.\n", 262 | " with gsd.hoomd.open(name=job.fn('lattice.gsd'), mode='x') as f:\n", 263 | " f.append(frame)\n", 264 | "\n", 265 | " # Set the 'initialized' item in the job document.\n", 266 | " job.document['initialized'] = True" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "The `init` function uses `job.statepoint.N_particles` to access the **state point** parameter and `job.fn` to construct a filename in the assigned **directory**. `init` also sets the `'initialized'` item in the **job document** to `True` which will be used in the next section of the tutorial.\n", 274 | "\n", 275 | "Call `init` to initialize **signac jobs** at various volume fractions in the **data space**:" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": 11, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "for volume_fraction in [0.4, 0.5, 0.6]:\n", 285 | " statepoint = dict(N_particles=128, volume_fraction=volume_fraction, seed=20)\n", 286 | " job = project.open_job(statepoint)\n", 287 | " job.init()\n", 288 | " init(job)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "This tutorial **initializes** only three **jobs** in the **data space** to keep the execution time and output short.\n", 296 | "In your own research, **signac** can help you organize and execute as many jobs as you need." 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "**signac** places the **data space** in a directory named `workspace`. Here are the files the loop generated:" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 12, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "workspace/59363805e6f46a715bc154b38dffc4e4:\n", 316 | "lattice.gsd signac_job_document.json signac_statepoint.json\n", 317 | "\n", 318 | "workspace/972b10bd6b308f65f0bc3a06db58cf9d:\n", 319 | "lattice.gsd signac_job_document.json signac_statepoint.json\n", 320 | "\n", 321 | "workspace/c1a59a95a0e8b4526b28cf12aa0a689e:\n", 322 | "lattice.gsd signac_job_document.json signac_statepoint.json\n" 323 | ] 324 | } 325 | ], 326 | "source": [ 327 | "!ls workspace/*" 328 | ] 329 | }, 330 | { 331 | "cell_type": "markdown", 332 | "metadata": {}, 333 | "source": [ 334 | "Each directory now contains the `lattice.gsd` file created by `init` as well as a `signac_statepoint.json` and `signac_job_document.json` files created by **signac**.\n", 335 | "The **directory** assigned to each **signac job** is a hash of the **state point** and is generated automatically by **signac**." 336 | ] 337 | }, 338 | { 339 | "cell_type": "markdown", 340 | "metadata": {}, 341 | "source": [ 342 | "## Summary\n", 343 | "\n", 344 | "In this section of the tutorial, you created a **data space** with **directories** to store the simulation results for a number of **state points**.\n", 345 | "So far, the **directory** for each simulation contains only the initial configuration file **lattice.gsd**." 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "metadata": {}, 351 | "source": [ 352 | "The remaining sections in this tutorial show you how to execute a workflow on this **data space** that randomizes, compresses, and equilibrates each simulation." 353 | ] 354 | }, 355 | { 356 | "cell_type": "markdown", 357 | "metadata": {}, 358 | "source": [ 359 | "This tutorial only teaches the basics of **signac**.\n", 360 | "Read the [signac documentation](https://docs.signac.io/en/latest/) to learn how to loop through all **signac jobs**, search, filter, and much more." 361 | ] 362 | } 363 | ], 364 | "metadata": { 365 | "language_info": { 366 | "codemirror_mode": { 367 | "name": "ipython", 368 | "version": 3 369 | }, 370 | "file_extension": ".py", 371 | "mimetype": "text/x-python", 372 | "name": "python", 373 | "nbconvert_exporter": "python", 374 | "pygments_lexer": "ipython3", 375 | "version": "3.11.2" 376 | }, 377 | "record_timing": false 378 | }, 379 | "nbformat": 4, 380 | "nbformat_minor": 4 381 | } 382 | -------------------------------------------------------------------------------- /02-Logging/04-Writing-Formatted-Output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Writing Formatted Output\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* How do I display status information during a simulation?\n", 14 | "* How can I log user-defined quantities?\n", 15 | "* How can I write formatted output to a text file?\n", 16 | "\n", 17 | "### Objectives\n", 18 | "\n", 19 | "* Demonstrate **user-defined log quantities**.\n", 20 | "* Explain the use of **Table** to display status information during a simulation run.\n", 21 | "* Show that **Table** can write to a file.\n", 22 | "\n", 23 | "## Boilerplate code" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import datetime\n", 33 | "\n", 34 | "import hoomd" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 2, 40 | "metadata": { 41 | "jupyter": { 42 | "source_hidden": true 43 | }, 44 | "nbsphinx": "hidden", 45 | "tags": [] 46 | }, 47 | "outputs": [], 48 | "source": [ 49 | "import os\n", 50 | "\n", 51 | "fn = os.path.join(os.getcwd(), 'log.txt')\n", 52 | "![ -e \"$fn\" ] && rm \"$fn\"" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Define the Simulation\n", 60 | "\n", 61 | "This tutorial executes the Lennard-Jones particle simulation from a previous tutorial. \n", 62 | "See [*Introducing Molecular Dyamics*](../01-Introducing-Molecular-Dynamics/00-index.ipynb) for a complete description of this code." 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "cpu = hoomd.device.CPU()\n", 72 | "sim = hoomd.Simulation(device=cpu, seed=1)\n", 73 | "sim.create_state_from_gsd(\n", 74 | " filename='../01-Introducing-Molecular-Dynamics/random.gsd')\n", 75 | "\n", 76 | "integrator = hoomd.md.Integrator(dt=0.005)\n", 77 | "cell = hoomd.md.nlist.Cell(buffer=0.4)\n", 78 | "lj = hoomd.md.pair.LJ(nlist=cell)\n", 79 | "lj.params[('A', 'A')] = dict(epsilon=1, sigma=1)\n", 80 | "lj.r_cut[('A', 'A')] = 2.5\n", 81 | "integrator.forces.append(lj)\n", 82 | "nvt = hoomd.md.methods.ConstantVolume(\n", 83 | " filter=hoomd.filter.All(),\n", 84 | " thermostat=hoomd.md.methods.thermostats.Bussi(kT=1.5))\n", 85 | "integrator.methods.append(nvt)\n", 86 | "sim.operations.integrator = integrator" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## Formatted output\n", 94 | "\n", 95 | "The **Table** writer formats log quantities in human-readable text and writes them to `stdout` or a file.\n", 96 | "**Table** only supports `scalar` and `string` quantities due to the limitations of this format.\n", 97 | "This section shows you how to use **Table** to display status information during a simulation run." 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Add quantities to a Logger" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "The `categories` argument to **Logger** defines the **categories** that it will accept." 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 4, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "logger = hoomd.logging.Logger(categories=['scalar', 'string'])" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "Log the and simulation `timestep` and `tps` quantities:" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 5, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "logger.add(sim, quantities=['timestep', 'tps'])" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "You can also log user-defined quantities using functions, callable class instances, or class properties.\n", 144 | "For example, this class computes the estimated time remaining:" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 6, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "class Status():\n", 154 | "\n", 155 | " def __init__(self, sim):\n", 156 | " self.sim = sim\n", 157 | "\n", 158 | " @property\n", 159 | " def seconds_remaining(self):\n", 160 | " try:\n", 161 | " return (self.sim.final_timestep - self.sim.timestep) / self.sim.tps\n", 162 | " except ZeroDivisionError:\n", 163 | " return 0\n", 164 | "\n", 165 | " @property\n", 166 | " def etr(self):\n", 167 | " return str(datetime.timedelta(seconds=self.seconds_remaining))" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "Assign the loggable quantity using the tuple `(object, property_name, flag)`, where `flag` is the string name of the flag for this quantity. (The tuple for callable objects would be `(callable, flag)`)." 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 7, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "status = Status(sim)\n", 184 | "logger[('Status', 'etr')] = (status, 'etr', 'string')" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "Represent the namespace of your user-defined quantity with a tuple of strings - `('Status', 'etr'`) above.\n", 192 | "You can use any number of arbitrary strings in the tuple to name your quantity." 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "## Display quantities with Table" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "**Table** is a **Writer** that formats the quantities in a **Logger** into a human readable table.\n", 207 | "Create one that triggers periodically:" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 8, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "table = hoomd.write.Table(trigger=hoomd.trigger.Periodic(period=5000),\n", 217 | " logger=logger)" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "Add it to the simulation:" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 9, 230 | "metadata": {}, 231 | "outputs": [], 232 | "source": [ 233 | "sim.operations.writers.append(table)" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "Run the simulation and see the output:" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": 10, 246 | "metadata": {}, 247 | "outputs": [ 248 | { 249 | "name": "stdout", 250 | "output_type": "stream", 251 | "text": [ 252 | "Simulation.timestep Simulation.tps Status.etr \n", 253 | " 15000 7265.44980 0:00:13.075584 \n", 254 | " 20000 7928.10708 0:00:11.352016 \n", 255 | " 25000 8212.82940 0:00:10.349661 \n", 256 | " 30000 8347.23914 0:00:09.584007 \n", 257 | " 35000 8415.05520 0:00:08.912598 \n", 258 | " 40000 8442.75984 0:00:08.291128 \n", 259 | " 45000 8493.75873 0:00:07.652678 \n", 260 | " 50000 8532.47563 0:00:07.031957 \n", 261 | " 55000 8563.96280 0:00:06.422260 \n", 262 | " 60000 8571.47951 0:00:05.833299 \n", 263 | " 65000 8598.56480 0:00:05.233432 \n", 264 | " 70000 8627.01996 0:00:04.636595 \n", 265 | " 75000 8636.41757 0:00:04.052606 \n", 266 | " 80000 8647.71201 0:00:03.469126 \n", 267 | " 85000 8653.31308 0:00:02.889067 \n", 268 | " 90000 8664.12848 0:00:02.308368 \n", 269 | " 95000 8659.11603 0:00:01.732278 \n", 270 | " 100000 8663.85839 0:00:01.154220 \n", 271 | " 105000 8670.02276 0:00:00.576700 \n", 272 | " 110000 8674.88580 0:00:00 \n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "sim.run(100000)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": {}, 283 | "source": [ 284 | "Later in this notebook, you are going to create another **Table Writer**.\n", 285 | "Remove `table` now to avoid confusing the two:" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 11, 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "sim.operations.writers.remove(table)" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "## Save Table output to a file\n", 302 | "\n", 303 | "**Table** writes to `stdout` by default.\n", 304 | "It can write to a file (or any Python file-like object with `write` and `flush` methods) instead." 305 | ] 306 | }, 307 | { 308 | "cell_type": "markdown", 309 | "metadata": {}, 310 | "source": [ 311 | "Open the file to write:" 312 | ] 313 | }, 314 | { 315 | "cell_type": "code", 316 | "execution_count": 12, 317 | "metadata": {}, 318 | "outputs": [], 319 | "source": [ 320 | "file = open('log.txt', mode='x', newline='\\n')" 321 | ] 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "metadata": {}, 326 | "source": [ 327 | "Create a **Table** **Writer** that outputs to this file and add it to the **Simulation**:" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 13, 333 | "metadata": {}, 334 | "outputs": [], 335 | "source": [ 336 | "table_file = hoomd.write.Table(output=file,\n", 337 | " trigger=hoomd.trigger.Periodic(period=5000),\n", 338 | " logger=logger)\n", 339 | "sim.operations.writers.append(table_file)" 340 | ] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "metadata": {}, 345 | "source": [ 346 | "Run the simulation:" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 14, 352 | "metadata": {}, 353 | "outputs": [], 354 | "source": [ 355 | "sim.run(100000)" 356 | ] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "metadata": {}, 361 | "source": [ 362 | "You can read the file with standard tools:" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 15, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "name": "stdout", 372 | "output_type": "stream", 373 | "text": [ 374 | " 165000 8794.29467 0:00:05.116954 \n", 375 | " 170000 8809.97435 0:00:04.540308 \n", 376 | " 175000 8808.18898 0:00:03.973575 \n", 377 | " 180000 8803.84588 0:00:03.407602 \n", 378 | " 185000 8793.11174 0:00:02.843135 \n", 379 | " 190000 8808.44379 0:00:02.270549 \n", 380 | " 195000 8812.89983 0:00:01.702050 \n", 381 | " 200000 8815.85406 0:00:01.134320 \n", 382 | " 205000 8819.50353 0:00:00.566925 \n", 383 | " 210000 8823.62751 0:00:00 \n" 384 | ] 385 | } 386 | ], 387 | "source": [ 388 | "!tail log.txt" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "metadata": {}, 394 | "source": [ 395 | "In this section, you have displayed **loggable quantities** during a simulation run and saved them to a text file.\n", 396 | "This is the last section in the logging tutorial." 397 | ] 398 | }, 399 | { 400 | "cell_type": "markdown", 401 | "metadata": { 402 | "nbsphinx": "hidden" 403 | }, 404 | "source": [ 405 | "[Previous section](03-Storing-Particle-Shape.ipynb) / [Tutorial index](../README.md)" 406 | ] 407 | } 408 | ], 409 | "metadata": { 410 | "language_info": { 411 | "codemirror_mode": { 412 | "name": "ipython", 413 | "version": 3 414 | }, 415 | "file_extension": ".py", 416 | "mimetype": "text/x-python", 417 | "name": "python", 418 | "nbconvert_exporter": "python", 419 | "pygments_lexer": "ipython3", 420 | "version": "3.11.2" 421 | }, 422 | "record_timing": false 423 | }, 424 | "nbformat": 4, 425 | "nbformat_minor": 4 426 | } 427 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/04-Custom-Updater.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Custom Updater\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- How can I modify the state of a system in a custom updater?\n", 14 | "\n", 15 | "### Objectives\n", 16 | "\n", 17 | "- Show an example of a non-trival custom updater.\n", 18 | "\n", 19 | "## Boilerplate Code" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "from numbers import Number\n", 29 | "\n", 30 | "import hoomd\n", 31 | "import hoomd.md as md\n", 32 | "import numpy as np\n", 33 | "\n", 34 | "cpu = hoomd.device.CPU()\n", 35 | "sim = hoomd.Simulation(device=cpu, seed=1)\n", 36 | "\n", 37 | "# Create a simple cubic configuration of particles\n", 38 | "N = 5 # particles per box direction\n", 39 | "box_L = 20 # box dimension\n", 40 | "\n", 41 | "snap = hoomd.Snapshot(cpu.communicator)\n", 42 | "snap.configuration.box = [box_L] * 3 + [0, 0, 0]\n", 43 | "snap.particles.N = N**3\n", 44 | "x, y, z = np.meshgrid(*(np.linspace(-box_L / 2, box_L / 2, N, endpoint=False),)\n", 45 | " * 3)\n", 46 | "positions = np.array((x.ravel(), y.ravel(), z.ravel())).T\n", 47 | "snap.particles.position[:] = positions\n", 48 | "snap.particles.types = ['A']\n", 49 | "snap.particles.typeid[:] = 0\n", 50 | "\n", 51 | "sim.create_state_from_snapshot(snap)\n", 52 | "rng = np.random.default_rng(1245)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "## Problem\n", 60 | "\n", 61 | "In this section, we will show how to create a custom updater\n", 62 | "that modifies the system state. To show this, we will create a custom\n", 63 | "updater that adds a prescribed amount of energy to a single particle\n", 64 | "simulating the bombardment of radioactive material into our system. For\n", 65 | "this problem, we pick a random particle and modify its velocity\n", 66 | "according to the radiation energy in a random direction." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "class InsertEnergyUpdater(hoomd.custom.Action):\n", 76 | "\n", 77 | " def __init__(self, energy):\n", 78 | " self.energy = energy\n", 79 | "\n", 80 | " def act(self, timestep):\n", 81 | " snap = self._state.get_snapshot()\n", 82 | " if snap.communicator.rank == 0:\n", 83 | " particle_i = rng.integers(snap.particles.N)\n", 84 | " mass = snap.particles.mass[particle_i]\n", 85 | " direction = self._get_direction()\n", 86 | " magnitude = np.sqrt(2 * self.energy / mass)\n", 87 | " velocity = direction * magnitude\n", 88 | " old_velocity = snap.particles.velocity[particle_i]\n", 89 | " new_velocity = old_velocity + velocity\n", 90 | " snap.particles.velocity[particle_i] = velocity\n", 91 | " self._state.set_snapshot(snap)\n", 92 | "\n", 93 | " @staticmethod\n", 94 | " def _get_direction():\n", 95 | " theta, z = rng.random(2)\n", 96 | " theta *= 2 * np.pi\n", 97 | " z = 2 * (z - 0.5)\n", 98 | " return np.array([\n", 99 | " np.sqrt(1 - (z * z)) * np.cos(theta),\n", 100 | " np.sqrt(1 - (z * z)) * np.sin(theta), z\n", 101 | " ])" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "We will now use our custom updater with an `NVE` integrator.\n", 109 | "Particles will interact via a Lennard-Jones potential.\n", 110 | "Using the `Table` writer and a `hoomd.logging.Logger`, we will\n", 111 | "monitor the energy, which should be increasing as we are\n", 112 | "adding energy to the system. We will also thermalize our\n", 113 | "system to a `kT == 1`." 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 3, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "sim.state.thermalize_particle_momenta(filter=hoomd.filter.All(), kT=1.)\n", 123 | "\n", 124 | "lj = md.pair.LJ(nlist=md.nlist.Cell(buffer=0.4))\n", 125 | "lj.params[('A', 'A')] = {'epsilon': 1., 'sigma': 1.}\n", 126 | "lj.r_cut[('A', 'A')] = 2.5\n", 127 | "integrator = md.Integrator(\n", 128 | " methods=[md.methods.ConstantVolume(hoomd.filter.All())],\n", 129 | " forces=[lj],\n", 130 | " dt=0.005)\n", 131 | "\n", 132 | "thermo = md.compute.ThermodynamicQuantities(hoomd.filter.All())\n", 133 | "logger = hoomd.logging.Logger(categories=['scalar'])\n", 134 | "logger.add(thermo, ['kinetic_energy', 'potential_energy'])\n", 135 | "logger['total_energy'] = (\n", 136 | " lambda: thermo.kinetic_energy + thermo.potential_energy, 'scalar')\n", 137 | "\n", 138 | "table = hoomd.write.Table(100, logger, max_header_len=1)\n", 139 | "\n", 140 | "sim.operations += integrator\n", 141 | "sim.operations += thermo\n", 142 | "sim.operations += table\n", 143 | "\n", 144 | "# Create and add our custom updater\n", 145 | "energy_operation = hoomd.update.CustomUpdater(action=InsertEnergyUpdater(10.),\n", 146 | " trigger=100)\n", 147 | "\n", 148 | "sim.operations += energy_operation" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 4, 154 | "metadata": {}, 155 | "outputs": [ 156 | { 157 | "name": "stdout", 158 | "output_type": "stream", 159 | "text": [ 160 | " kinetic_energy potential_energy total_energy \n", 161 | " 189.55469 -0.16021 189.39447 \n", 162 | " 203.13934 -5.51636 197.62298 \n", 163 | " 214.05941 -7.90628 206.15314 \n", 164 | " 219.49181 -8.47534 211.01647 \n", 165 | " 230.38656 -9.71804 220.66852 \n", 166 | " 237.66638 -9.07038 228.59601 \n", 167 | " 245.73067 -10.18110 235.54957 \n", 168 | " 254.95301 -12.21494 242.73808 \n", 169 | " 258.55741 -6.01446 252.54296 \n", 170 | " 269.38334 -8.12332 261.26002 \n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "sim.run(1000)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "As we can see the total energy of the system is indeed increasing.\n", 183 | "The energy isn't increasing by 10 every time since we are adding\n", 184 | "the velocity in a random direction which may be against the current\n", 185 | "velocity.\n", 186 | "\n", 187 | "## Improving upon our Custom Action\n", 188 | "\n", 189 | "Maybe we want to allow for the energy to be from a distribution.\n", 190 | "HOOMD-blue has a concept called a variant which allows for quantities\n", 191 | "that vary over time. Let's change the `InsertEnergyupdater` to use\n", 192 | "variants and create a custom variant that grabs a random number from\n", 193 | "a Gaussian distribution. (If you don't understand the variant code,\n", 194 | "that is fine. We are just using this to showcase how you can iteratively\n", 195 | "improve custom actions).\n", 196 | "\n", 197 | "
\n", 198 | "

Note:

\n", 199 | "

Variant objects model a parameter as a \n", 200 | " function of the timestep, so to get the value for a particular\n", 201 | " timestep we have to call the variant. For more\n", 202 | " information see the documentation for \n", 203 | " hoomd.variant.\n", 204 | "

\n", 205 | "
" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 5, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "class InsertEnergyUpdater(hoomd.custom.Action):\n", 215 | "\n", 216 | " def __init__(self, energy):\n", 217 | " self._energy = energy\n", 218 | "\n", 219 | " @property\n", 220 | " def energy(self):\n", 221 | " \"\"\"A `hoomd.variant.Variant` object.\"\"\"\n", 222 | " return self._energy\n", 223 | "\n", 224 | " @energy.setter\n", 225 | " def energy(self, new_energy):\n", 226 | " if isinstance(new_energy, Number):\n", 227 | " self._energy = hoomd.variant.Constant(new_energy)\n", 228 | " elif isinstance(new_energy, hoomd.variant.Variant):\n", 229 | " self._energy = new_energy\n", 230 | " else:\n", 231 | " raise ValueError(\"energy must be a variant or real number.\")\n", 232 | "\n", 233 | " def act(self, timestep):\n", 234 | " snap = self._state.get_snapshot()\n", 235 | " if snap.communicator.rank == 0:\n", 236 | " particle_i = rng.integers(snap.particles.N)\n", 237 | " mass = snap.particles.mass[particle_i]\n", 238 | " direction = self._get_direction()\n", 239 | " magnitude = np.sqrt(2 * self.energy(timestep) / mass)\n", 240 | " velocity = direction * magnitude\n", 241 | " old_velocity = snap.particles.velocity[particle_i]\n", 242 | " new_velocity = old_velocity + velocity\n", 243 | " snap.particles.velocity[particle_i] = velocity\n", 244 | " self._state.set_snapshot(snap)\n", 245 | "\n", 246 | " @staticmethod\n", 247 | " def _get_direction():\n", 248 | " theta, z = rng.random(2)\n", 249 | " theta *= 2 * np.pi\n", 250 | " z = 2 * (z - 0.5)\n", 251 | " return np.array([\n", 252 | " np.sqrt(1 - (z * z)) * np.cos(theta),\n", 253 | " np.sqrt(1 - (z * z)) * np.sin(theta), z\n", 254 | " ])\n", 255 | "\n", 256 | "\n", 257 | "class GaussianVariant(hoomd.variant.Variant):\n", 258 | "\n", 259 | " def __init__(self, mean, std):\n", 260 | " hoomd.variant.Variant.__init__(self)\n", 261 | " self.mean = mean\n", 262 | " self.std = std\n", 263 | "\n", 264 | " def __call__(self, timestep):\n", 265 | " return rng.normal(self.mean, self.std)" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "We briefly show that the Gaussian Variant works." 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 6, 278 | "metadata": {}, 279 | "outputs": [ 280 | { 281 | "data": { 282 | "text/plain": [ 283 | "'Mean: 10.069550459202723, std. dev. 1.9965744919420398'" 284 | ] 285 | }, 286 | "execution_count": 6, 287 | "metadata": {}, 288 | "output_type": "execute_result" 289 | } 290 | ], 291 | "source": [ 292 | "energy = GaussianVariant(mean=10., std=2.)\n", 293 | "sample_energies = np.array([energy(0) for _ in range(1000)])\n", 294 | "f\"Mean: {sample_energies.mean()}, std. dev. {sample_energies.std()}\"" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "metadata": {}, 300 | "source": [ 301 | "We now use the updated `InsertEnergyUpdater` in the simulation." 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 7, 307 | "metadata": {}, 308 | "outputs": [ 309 | { 310 | "name": "stdout", 311 | "output_type": "stream", 312 | "text": [ 313 | " 279.83085 -11.21077 268.62009 \n", 314 | " 289.14791 -9.62083 279.52708 \n", 315 | " 302.04414 -9.22880 292.81534 \n", 316 | " 312.06778 -11.05103 301.01675 \n", 317 | " 324.69061 -11.95850 312.73211 \n", 318 | " 332.29403 -9.80310 322.49093 \n", 319 | " 345.55571 -14.83428 330.72143 \n", 320 | " 357.07548 -16.49285 340.58263 \n", 321 | " 357.62391 -11.70456 345.91935 \n", 322 | " 372.21959 -15.91459 356.30500 \n" 323 | ] 324 | } 325 | ], 326 | "source": [ 327 | "sim.operations.updaters.remove(energy_operation)\n", 328 | "# Create and add our custom updater\n", 329 | "energy_operation = hoomd.update.CustomUpdater(\n", 330 | " action=InsertEnergyUpdater(energy), trigger=100)\n", 331 | "sim.operations.updaters.append(energy_operation)\n", 332 | "sim.run(1000)" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "metadata": {}, 338 | "source": [ 339 | "We could continue to improve upon this updater and the execution of\n", 340 | "this operation. However, this suffices to showcase the ability of non-trivial updaters to affect the simulation state." 341 | ] 342 | } 343 | ], 344 | "metadata": { 345 | "language_info": { 346 | "codemirror_mode": { 347 | "name": "ipython", 348 | "version": 3 349 | }, 350 | "file_extension": ".py", 351 | "mimetype": "text/x-python", 352 | "name": "python", 353 | "nbconvert_exporter": "python", 354 | "pygments_lexer": "ipython3", 355 | "version": "3.10.6" 356 | } 357 | }, 358 | "nbformat": 4, 359 | "nbformat_minor": 4 360 | } 361 | -------------------------------------------------------------------------------- /07-Modelling-Patchy-Particles/kern-frenkel-schematic.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 2023-03-01T12:20:15.226262 10 | image/svg+xml 11 | 12 | 13 | Matplotlib v3.5.1, https://matplotlib.org/ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 40 | 41 | 42 | 49 | 50 | 51 | 62 | 63 | 64 | 75 | 76 | 77 | 114 | 115 | 116 | 137 | 138 | 139 | 148 | 149 | 150 | 159 | 160 | 161 | 170 | 171 | 172 | 181 | 182 | 183 | 184 | 185 | 186 | 198 | 215 | 228 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | -------------------------------------------------------------------------------- /04-Custom-Actions-In-Python/05-Custom-Writer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Custom Writer\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "- How could I write a custom trajectory writer?\n", 14 | "\n", 15 | "### Objectives\n", 16 | "\n", 17 | "- Show an example custom writer\n", 18 | "\n", 19 | "## Boilerplate Code" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import h5py\n", 29 | "import hoomd\n", 30 | "import hoomd.hpmc as hpmc\n", 31 | "import numpy as np\n", 32 | "\n", 33 | "cpu = hoomd.device.CPU()\n", 34 | "sim = hoomd.Simulation(cpu, seed=1)\n", 35 | "\n", 36 | "# Create a simple cubic configuration of particles\n", 37 | "N = 5 # particles per box direction\n", 38 | "box_L = 20 # box dimension\n", 39 | "\n", 40 | "snap = hoomd.Snapshot(cpu.communicator)\n", 41 | "snap.configuration.box = [box_L] * 3 + [0, 0, 0]\n", 42 | "snap.particles.N = N**3\n", 43 | "x, y, z = np.meshgrid(*(np.linspace(-box_L / 2, box_L / 2, N, endpoint=False),)\n", 44 | " * 3)\n", 45 | "positions = np.array((x.ravel(), y.ravel(), z.ravel())).T\n", 46 | "snap.particles.position[:] = positions\n", 47 | "snap.particles.types = ['A']\n", 48 | "snap.particles.typeid[:] = 0\n", 49 | "\n", 50 | "sim.create_state_from_snapshot(snap)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "## Problem\n", 58 | "\n", 59 | "For this section, we will demonstrate writing a custom trajectory writer using `h5py`.\n", 60 | "We will start by implementing the ability to store positions, timesteps, and box dimensions\n", 61 | "in an HDF5 file." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 2, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "class HDF5Writer(hoomd.custom.Action):\n", 71 | "\n", 72 | " def __init__(self, filename, mode):\n", 73 | " self.filename = filename\n", 74 | " if mode not in {'w', 'w-', 'x', 'a', 'r+'}:\n", 75 | " raise ValueError(\"mode must be writtable\")\n", 76 | " self.file = h5py.File(filename, mode)\n", 77 | " self.write_metadata()\n", 78 | " frames = list(self.file.keys())\n", 79 | " if frames:\n", 80 | " self._cur_frame = max(map(int, frames)) + 1\n", 81 | " else:\n", 82 | " self._cur_frame = 1\n", 83 | "\n", 84 | " def write_metadata(self):\n", 85 | " \"\"\"Write the file metadata that defines the type of hdf5 file\"\"\"\n", 86 | " if 'app' in self.file.attrs:\n", 87 | " if self.file.attrs.app != 'hoomd-v3':\n", 88 | " raise RuntimeError(\n", 89 | " 'HDF5 file metadata \"app\" is not \"hoomd-v3\".')\n", 90 | " else:\n", 91 | " self.file.attrs.app = 'hoomd-v3'\n", 92 | "\n", 93 | " if 'version' not in self.file.attrs:\n", 94 | " self.file.attrs.version = '1.0'\n", 95 | "\n", 96 | " def act(self, timestep):\n", 97 | " \"\"\"Write out a new frame to the trajectory.\"\"\"\n", 98 | " new_frame = self.file.create_group(str(self._cur_frame))\n", 99 | " self._cur_frame += 1\n", 100 | " positions = new_frame.create_dataset('positions',\n", 101 | " (self._state.N_particles, 3),\n", 102 | " dtype='f8')\n", 103 | " snapshot = self._state.get_snapshot()\n", 104 | " positions[:] = snapshot.particles.position\n", 105 | " new_frame.attrs['timestep'] = timestep\n", 106 | " box_array = np.concatenate((self._state.box.L, self._state.box.tilts))\n", 107 | " new_frame.attrs['box'] = box_array\n", 108 | "\n", 109 | " def __del__(self):\n", 110 | " self.file.close()" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": {}, 116 | "source": [ 117 | "Define a function that creates a `HDF5Writer` wrapped in a custom writer." 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "This function will make creating our custom writer easier. We will now add an HPMC sphere\n", 125 | "integrator and our custom writer to our simulation and run for 1000 steps.\n", 126 | "\n", 127 | "(Note that the 'w' mode will truncate any existing file.)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 3, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "h5_writer = hoomd.write.CustomWriter(action=HDF5Writer('traj.h5', 'w'),\n", 137 | " trigger=100)\n", 138 | "integrator = hpmc.integrate.Sphere()\n", 139 | "integrator.shape['A'] = {'diameter': 1.}\n", 140 | "\n", 141 | "sim.operations += integrator\n", 142 | "sim.operations += h5_writer\n", 143 | "\n", 144 | "sim.run(1000)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "We have run the simulation, and our HDF5 file has been written. Lets check the\n", 152 | "groups our file contains now." 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 4, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "data": { 162 | "text/plain": [ 163 | "['1', '10', '2', '3', '4', '5', '6', '7', '8', '9']" 164 | ] 165 | }, 166 | "execution_count": 4, 167 | "metadata": {}, 168 | "output_type": "execute_result" 169 | } 170 | ], 171 | "source": [ 172 | "list(h5_writer.file.keys())" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "Ten frames have been written as expected. Let's check the properties from the last\n", 180 | "frame and compare them to the simulation currently. We will open the file again in\n", 181 | "read only mode to check these properties. First we flush the open HDF5 file to ensure\n", 182 | "the data has been written to the OS buffer at least." 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 5, 188 | "metadata": {}, 189 | "outputs": [], 190 | "source": [ 191 | "h5_writer.file.flush()\n", 192 | "\n", 193 | "with h5py.File('traj.h5', 'r') as traj:\n", 194 | " assert traj['10'].attrs['timestep'] == sim.timestep\n", 195 | " box_array = np.concatenate((sim.state.box.L, sim.state.box.tilts))\n", 196 | " assert np.allclose(traj['10'].attrs['box'], box_array)\n", 197 | " snapshot = sim.state.get_snapshot()\n", 198 | " assert np.allclose(snapshot.particles.position, traj['10']['positions'][:])" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "## Expanding on HDF5Writer\n", 206 | "\n", 207 | "Our `HDF5Writer` class is already sufficient for storing the trajectory.\n", 208 | "However, there are plenty of other features we could add. Examples include\n", 209 | "utilizing the HOOMD-blue logging subsystem to allow logging data to the\n", 210 | "HDF5 file, and adding support for MPI. Also, we could also add support \n", 211 | "for other system properties such as images, velocities, and others.\n", 212 | "We will focus on adding this feature.\n", 213 | "\n", 214 | "We need to decide on a method of specifying properties to write. We will\n", 215 | "use a tuple system where we signify the property we want to store using\n", 216 | "a tuple that nests into a snapshot object. For example to write images\n", 217 | "we will use the tuple `('particles', 'image')` to signify we want to \n", 218 | "store images. We will let an user pass in a list of tuples of any length \n", 219 | "to specify what they want to store. (Positions will always be stored, and \n", 220 | "we will move them to the _particles_ group)." 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 6, 226 | "metadata": {}, 227 | "outputs": [], 228 | "source": [ 229 | "class HDF5Writer(hoomd.custom.Action):\n", 230 | "\n", 231 | " def __init__(self, filename, mode, properties):\n", 232 | " self.filename = filename\n", 233 | " self.properties = set(properties) | {('particles', 'position')}\n", 234 | " if mode not in {'w', 'w-', 'x', 'a', 'r+'}:\n", 235 | " raise ValueError(\"mode must be writtable\")\n", 236 | " self.file = h5py.File(filename, mode)\n", 237 | " self.write_metadata()\n", 238 | " frames = list(self.file.keys())\n", 239 | " if frames:\n", 240 | " self._cur_frame = max(map(int, frames)) + 1\n", 241 | " else:\n", 242 | " self._cur_frame = 1\n", 243 | "\n", 244 | " def write_metadata(self):\n", 245 | " \"\"\"Write the file metadata that defines the type of hdf5 file\"\"\"\n", 246 | " if 'app' in self.file.attrs:\n", 247 | " if self.file.attrs.app != 'hoomd-v3':\n", 248 | " raise RuntimeError(\n", 249 | " 'HDF5 file metadata \"app\" is not \"hoomd-v3\".')\n", 250 | " else:\n", 251 | " self.file.attrs.app = 'hoomd-v3'\n", 252 | "\n", 253 | " if 'version' not in self.file.attrs:\n", 254 | " self.file.attrs.version = '1.0'\n", 255 | "\n", 256 | " def _set_property(self, base_group, prop):\n", 257 | " # Get data array\n", 258 | " data = self._state.get_snapshot()\n", 259 | " for name in prop:\n", 260 | " data = getattr(data, name)\n", 261 | " # Get dataset\n", 262 | " use_group = base_group\n", 263 | " for name in prop[:-1]:\n", 264 | " if name not in use_group:\n", 265 | " use_group = base_group.create_group(name)\n", 266 | " else:\n", 267 | " use_group = base_group[name]\n", 268 | " dataset = use_group.create_dataset(prop[-1],\n", 269 | " data.shape,\n", 270 | " dtype=str(data.dtype))\n", 271 | " dataset[:] = data\n", 272 | "\n", 273 | " def act(self, timestep):\n", 274 | " \"\"\"Write out a new frame to the trajectory.\"\"\"\n", 275 | " new_frame = self.file.create_group(str(self._cur_frame))\n", 276 | " self._cur_frame += 1\n", 277 | " for prop in self.properties:\n", 278 | " self._set_property(new_frame, prop)\n", 279 | " new_frame.attrs['timestep'] = timestep\n", 280 | " box_array = np.concatenate((self._state.box.L, self._state.box.tilts))\n", 281 | " new_frame.attrs['box'] = box_array\n", 282 | "\n", 283 | " def __del__(self):\n", 284 | " self.file.close()" 285 | ] 286 | }, 287 | { 288 | "cell_type": "markdown", 289 | "metadata": {}, 290 | "source": [ 291 | "We will now use our extended trajectory writer to write out particle\n", 292 | "images as well. " 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 7, 298 | "metadata": {}, 299 | "outputs": [], 300 | "source": [ 301 | "h5_writer.file.close()\n", 302 | "sim.operations -= h5_writer\n", 303 | "h5_writer = hoomd.write.CustomWriter(action=HDF5Writer(\n", 304 | " 'traj.h5', 'w', [('particles', 'image')]),\n", 305 | " trigger=100)\n", 306 | "sim.operations.writers.append(h5_writer)\n", 307 | "sim.run(1000)" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": {}, 313 | "source": [ 314 | "To see that this worked we will check the first frame for particle images." 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 8, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "data": { 324 | "text/plain": [ 325 | "array([[-1, 0, 0],\n", 326 | " [-1, 0, 0],\n", 327 | " [-1, -1, 0],\n", 328 | " [-1, 0, 0],\n", 329 | " [-1, -1, 0],\n", 330 | " [ 0, 0, 0],\n", 331 | " [ 0, 0, 0],\n", 332 | " [ 0, 0, 0],\n", 333 | " [ 0, 0, 0],\n", 334 | " [ 0, 0, 0]], dtype=int32)" 335 | ] 336 | }, 337 | "metadata": {}, 338 | "output_type": "display_data" 339 | }, 340 | { 341 | "data": { 342 | "text/plain": [ 343 | "array([[ 7.08916892, -9.1162494 , -9.03166465],\n", 344 | " [ 7.17894751, -7.55565503, -4.1420896 ],\n", 345 | " [ 6.84048969, 9.85875838, -0.14319672],\n", 346 | " [ 9.42302572, -7.66224406, 1.71042043],\n", 347 | " [ 4.04383384, 8.15467659, 9.35673311],\n", 348 | " [-5.21819354, -9.57761671, -7.17922194],\n", 349 | " [-6.56869188, -9.00928178, -7.91171588],\n", 350 | " [-1.41025576, -9.14286987, -3.21326451],\n", 351 | " [-3.29261443, -8.20593309, 2.56455928],\n", 352 | " [-2.02993862, -3.93072604, 4.98365 ]])" 353 | ] 354 | }, 355 | "metadata": {}, 356 | "output_type": "display_data" 357 | } 358 | ], 359 | "source": [ 360 | "h5_writer.file.flush()\n", 361 | "\n", 362 | "with h5py.File('traj.h5', 'r') as traj:\n", 363 | " display(traj['1']['particles']['image'][:10])\n", 364 | " display(traj['1']['particles']['position'][:10])" 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "metadata": {}, 370 | "source": [ 371 | "We could continue add more features such as argument validation\n", 372 | "in the\n", 373 | "constructor, support for the logging subsystem of HOOMD-blue,\n", 374 | "a classmethod, or a number of other things. However, these\n", 375 | "are left as exercises. This section has shown a non-trivial\n", 376 | "application of the custom action feature in HOOMD-blue for\n", 377 | "custom writers." 378 | ] 379 | } 380 | ], 381 | "metadata": { 382 | "language_info": { 383 | "codemirror_mode": { 384 | "name": "ipython", 385 | "version": 3 386 | }, 387 | "file_extension": ".py", 388 | "mimetype": "text/x-python", 389 | "name": "python", 390 | "nbconvert_exporter": "python", 391 | "pygments_lexer": "ipython3", 392 | "version": "3.10.6" 393 | } 394 | }, 395 | "nbformat": 4, 396 | "nbformat_minor": 4 397 | } 398 | -------------------------------------------------------------------------------- /03-Parallel-Simulations-With-MPI/04-Running-Multiple-Simulations-With-Partitions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Running Multiple Simulations With Partitions\n", 8 | "\n", 9 | "## Overview\n", 10 | "\n", 11 | "### Questions\n", 12 | "\n", 13 | "* How can I partition a MPI communicator to run many independent simulations? \n", 14 | "* When are partitions useful?\n", 15 | "\n", 16 | "### Objectives\n", 17 | "\n", 18 | "* Show how to define a custom **Communicator** with more than one **partition**.\n", 19 | "* Demonstrate how to use this to run many independent simulations with one `mpirun`.\n", 20 | "* Explain how this is useful to **aggregate** jobs on HPC systems." 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": { 27 | "jupyter": { 28 | "source_hidden": true 29 | }, 30 | "nbsphinx": "hidden", 31 | "tags": [] 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import os\n", 36 | "\n", 37 | "fn = os.path.join(os.getcwd(), 'trajectory0.gsd')\n", 38 | "![ -e \"$fn\" ] && rm \"$fn\"\n", 39 | "fn = os.path.join(os.getcwd(), 'trajectory1.gsd')\n", 40 | "![ -e \"$fn\" ] && rm \"$fn\"" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "## Partitioning communicators\n", 48 | "\n", 49 | "So far in this tutorial you have seen how executing `mpirun -n 4 python3 script.py` will use domain decomposition to run *1* simulation split across *4* ranks via **domain decomposition**.\n", 50 | "What if you wanted to run *2 different* simulations, each on *2* **ranks** with this command?\n", 51 | "Or *4 different* simulations each on *1* **rank**?\n", 52 | "This is called **partitioning** the **MPI** communicator.\n", 53 | "\n", 54 | "In HOOMD-blue, you can do this by defining a non-default **Communicator** and specifying the ``ranks_per_partition`` argument.\n", 55 | "Then use ``communicator.partition`` as an identifier in your script to change input parameters." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 2, 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "text/plain": [ 66 | "\u001b[0;32mimport\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m\u001b[0m\n", 67 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 68 | "\u001b[0;34m\u001b[0m\u001b[0mcommunicator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcommunicator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCommunicator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mranks_per_partition\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 69 | "\u001b[0;34m\u001b[0m\u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'Hello from partition {communicator.partition} rank {communicator.rank}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n" 70 | ] 71 | }, 72 | "metadata": {}, 73 | "output_type": "display_data" 74 | } 75 | ], 76 | "source": [ 77 | "%pycat hello_partition.py" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 3, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "Hello from partition 0 rank 0\n", 90 | "Hello from partition 0 rank 1\n", 91 | "Hello from partition 1 rank 0\n", 92 | "Hello from partition 1 rank 1\n" 93 | ] 94 | } 95 | ], 96 | "source": [ 97 | "!mpirun -n 4 python3 hello_partition.py" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "In **partitioned** simulations, all ranks *within a given partition* must have the same input file, operations, parameters, and otherwise follow the **single program** guidelines outlined in the previous sections of this tutorial.\n", 105 | "However, the input file, operations and/or their parameters can vary from one **partition** to the next.\n", 106 | "For example, you could run many simulations with different temperatures, different initial configurations, or different random number seeds.\n", 107 | "The following example shows how to set different temperatures and random number seeds based on the **partition** index.\n", 108 | "
\n", 109 | " You MUST pass the partitioned Communicator object to the device constructor:\n", 110 | " hoomd.device.CPU(communicator=communicator) or hoomd.device.GPU(communicator=communicator)\n", 111 | "
" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 4, 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "data": { 121 | "text/plain": [ 122 | "\u001b[0;32mimport\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m\u001b[0m\n", 123 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 124 | "\u001b[0;34m\u001b[0m\u001b[0;31m# kT values to execute at:\u001b[0m\u001b[0;34m\u001b[0m\n", 125 | "\u001b[0;34m\u001b[0m\u001b[0mkT_values\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1.5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", 126 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 127 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Instantiate a Communicator with 2 ranks in each partition.\u001b[0m\u001b[0;34m\u001b[0m\n", 128 | "\u001b[0;34m\u001b[0m\u001b[0mcommunicator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcommunicator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCommunicator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mranks_per_partition\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 129 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 130 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Pass the communicator to the device.\u001b[0m\u001b[0;34m\u001b[0m\n", 131 | "\u001b[0;34m\u001b[0m\u001b[0mdevice\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCPU\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcommunicator\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcommunicator\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 132 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 133 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Initialize the simulation.\u001b[0m\u001b[0;34m\u001b[0m\n", 134 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSimulation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mseed\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 135 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_state_from_gsd\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'random.gsd'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 136 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 137 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Choose system parameters based on the partition\u001b[0m\u001b[0;34m\u001b[0m\n", 138 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mseed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcommunicator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpartition\u001b[0m\u001b[0;34m\u001b[0m\n", 139 | "\u001b[0;34m\u001b[0m\u001b[0mkT\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mkT_values\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mcommunicator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpartition\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\n", 140 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 141 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Set the operations for a Lennard-Jones particle simulation.\u001b[0m\u001b[0;34m\u001b[0m\n", 142 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mIntegrator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdt\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.005\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 143 | "\u001b[0;34m\u001b[0m\u001b[0mcell\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnlist\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCell\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbuffer\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 144 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpair\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLJ\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnlist\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 145 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'A'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'A'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mepsilon\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msigma\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 146 | "\u001b[0;34m\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mr_cut\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'A'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'A'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2.5\u001b[0m\u001b[0;34m\u001b[0m\n", 147 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mforces\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 148 | "\u001b[0;34m\u001b[0m\u001b[0mnvt\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mConstantVolume\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", 149 | "\u001b[0;34m\u001b[0m \u001b[0mfilter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfilter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAll\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", 150 | "\u001b[0;34m\u001b[0m \u001b[0mthermostat\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthermostats\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mBussi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkT\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkT\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 151 | "\u001b[0;34m\u001b[0m\u001b[0mintegrator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmethods\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnvt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 152 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moperations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mintegrator\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mintegrator\u001b[0m\u001b[0;34m\u001b[0m\n", 153 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 154 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Use the partition id in the output file name.\u001b[0m\u001b[0;34m\u001b[0m\n", 155 | "\u001b[0;34m\u001b[0m\u001b[0mgsd_writer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mGSD\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34mf'trajectory{communicator.partition}.gsd'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", 156 | "\u001b[0;34m\u001b[0m \u001b[0mtrigger\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhoomd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrigger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPeriodic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", 157 | "\u001b[0;34m\u001b[0m \u001b[0mmode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'xb'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 158 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moperations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwriters\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgsd_writer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n", 159 | "\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\n", 160 | "\u001b[0;34m\u001b[0m\u001b[0;31m# Run the simulation.\u001b[0m\u001b[0;34m\u001b[0m\n", 161 | "\u001b[0;34m\u001b[0m\u001b[0msim\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n" 162 | ] 163 | }, 164 | "metadata": {}, 165 | "output_type": "display_data" 166 | } 167 | ], 168 | "source": [ 169 | "%pycat lj_partition.py" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 5, 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "name": "stdout", 179 | "output_type": "stream", 180 | "text": [ 181 | "notice(2): Using domain decomposition: n_x = 1 n_y = 1 n_z = 2.\n", 182 | "notice(2): Using domain decomposition: n_x = 1 n_y = 1 n_z = 2.\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "!mpirun -n 4 python3 lj_partition.py" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "Each **partition** is a separate Simulation that produces its own trajectory.\n", 195 | "You must choose filenames or directories so that different **partitions** produce different output files.\n", 196 | "The above example places the **partition** index in the filename:" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 6, 202 | "metadata": {}, 203 | "outputs": [ 204 | { 205 | "name": "stdout", 206 | "output_type": "stream", 207 | "text": [ 208 | "trajectory0.gsd trajectory1.gsd\n" 209 | ] 210 | } 211 | ], 212 | "source": [ 213 | "!ls trajectory?.gsd" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "## Motivation\n", 221 | "\n", 222 | "Why use **partitions** in simulations where you can just write one script with parameters and execute it more than once?\n", 223 | "Some **HPC** systems only allow jobs to use whole nodes, and/or policies prefer fewer large jobs over many smaller jobs in the scheduler.\n", 224 | "On these systems, you can obtain better throughput for your research when you use **partitions** to **aggregate** many independent simulations together into one scheduler job.\n", 225 | "\n", 226 | "While the details are beyond the scope of this tutorial, [signac-flow](https://docs.signac.io/projects/flow) can automate the **aggregation** process." 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "## Summary" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "In this section, you have learned how to run many simulations with different parameters using a single `mpirun` invocation.\n", 241 | "This is the end of the MPI tutorial." 242 | ] 243 | } 244 | ], 245 | "metadata": { 246 | "language_info": { 247 | "codemirror_mode": { 248 | "name": "ipython", 249 | "version": 3 250 | }, 251 | "file_extension": ".py", 252 | "mimetype": "text/x-python", 253 | "name": "python", 254 | "nbconvert_exporter": "python", 255 | "pygments_lexer": "ipython3", 256 | "version": "3.10.6" 257 | }, 258 | "record_timing": false 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 4 262 | } 263 | --------------------------------------------------------------------------------