Skip to content

Commit 09001c7

Browse files
committed
Add smooth cutoffs to distance tolerances (#44) and improve documentation
## Added - Added smooth cutoffs to distance tolerances - Added smooth cutoffs to 3- and 4-body contributions to `evaluate_point()` - Viability function is now a more continuous surface (abrupt when transitioning below cutoff_min) - Added descriptor generator procedure - Added unit tests for distribution convergence - Added probability density generation procedure - Added `set_method_ratio_default` procedure ## Changed - Changed Python docstrings style to match [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html) - Specified expected types for Python arguments - Converted all custom `modu` calls to `norm2` - Optimised `evaluate` and `viability` modules - Optimised array indexing - Moved `atom_ignore_index` array to boolean inside `basis_type` - Acts as a mask for atoms ## Fixed - Fixed handling of optional method ratio
1 parent 96fd6b4 commit 09001c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3179
-918
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Description
2+
3+
> _Briefly describe the purpose and content of this merge request._
4+
> _E.g. "Add structure generation constraints", or "Fix bug in energy parsing"._
5+
6+
# Checklist
7+
8+
Mark with `x` when complete, or `~` if not applicable.
9+
10+
- [ ] [I have read and followed the **RAFFLE's contribution guidelines.**](https://github.com/ExeQuantCode/RAFFLE/blob/main/CONTRIBUTING.md)
11+
- [ ] **Code is commented** appropriately, and API docstrings follow NumPy or FORD style.
12+
- [ ] **Read*the*Docs** documentation is added/updated (if applicable).
13+
- [ ] **Unit tests** are added/updated (if applicable).
14+
- [ ] **Linked issue** is resolved with a `closes #XXXX` reference (if applicable).

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ repos:
55
- id: trailing-whitespace
66
- id: end-of-file-fixer
77
- repo: https://github.com/nedtaylor/fortran-format-hooks
8-
rev: 1aad19af1f0c87027829f0f872d4bdc6ed9251e2
8+
rev: 749c61fce3a5f29f6d5d3b236ed9b693c6b2bf4e
99
hooks:
1010
- id: check-fortran-indentation
1111
args: [--line-length=80, --ignore-directories=src/wrapper]

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ set(LIB_DIR ${FORTRAN_SRC_DIR}/lib)
7474
set(LIB_FILES
7575
mod_io_utils.F90
7676
mod_constants.f90
77+
mod_cache.f90
7778
mod_misc.f90
7879
mod_tools_infile.f90
7980
mod_misc_maths.f90

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ When submitting your contributions, please ensure the following:
9292
- Reference any related issues or pull requests, if applicable.
9393
- Write unit tests for your contributions
9494
- Ensure all existing tests pass before submitting your changes.
95-
- Update the documentation to reflect your changes, if necessary (i.e. through FORD style commenting).
95+
- Update the documentation to reflect your changes, if necessary (i.e. through FORD style commenting for Fortran and NumPy docstrings style for Python).
9696
- Provide examples and usage instructions, if applicable.
9797

9898
Follow the [Code Style](#code-style) when contributing code to this project to ensure compatibility and a uniform format to the project.
@@ -101,7 +101,7 @@ Follow the [Code Style](#code-style) when contributing code to this project to e
101101
### Code Style
102102
- Follow the existing code style and conventions.
103103
- Use meaningful variable and function names.
104-
- Write clear and concise comments. For the Fortran library, use comments compatible with the [FORD Fortran Documenter](https://forddocs.readthedocs.io/en/stable/). For the Python wrapper, use comments compatible with [pandoc](https://pandoc.org).
104+
- Write clear and concise comments. For the Fortran library, use comments compatible with the [FORD Fortran Documenter](https://forddocs.readthedocs.io/en/stable/). For the Python wrapper, use comments compatible with [pandoc](https://pandoc.org), following the [NumPy style guide](https://numpydoc.readthedocs.io/en/latest/format.html).
105105

106106

107107

docs/source/tutorials/graphene_grain_boundary_tutorial.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ First, we must import the required packages:
3030
from raffle.generator import raffle_generator
3131
from mace.calculators import mace_mp
3232
import numpy as np
33+
from pathlib import Path
34+
35+
script_dir = Path(__file__).resolve().parent
3336
3437
3538
Next, we need to set up the RAFFLE generator and the calculator to calculate the energies of the structures.
@@ -55,14 +58,14 @@ The host is read in from a file, but it can also be generated using the ARTEMIS
5558

5659
.. code-block:: python
5760
58-
host = read("../POSCAR_host_gb")
61+
host = read(script_dir / ".." / "POSCAR_host_gb")
5962
generator.set_host(host)
6063
6164
We then need to set up the RAFFLE generator by creating the descriptor.
6265

6366
.. code-block:: python
6467
65-
graphene = read("../POSCAR_graphene")
68+
graphene = read(script_dir / ".." / "POSCAR_graphene")
6669
h2 = build.molecule("H2")
6770
graphene.calc = calc
6871
C_reference_energy = graphene.get_potential_energy() / len(graphene)

docs/source/tutorials/index.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@ Whilst RAFFLE is a random sturcture search package designed primarlily for inter
3535
aluminium_tutorial
3636
Si-Ge_tutorial
3737
graphene_grain_boundary_tutorial
38+
39+
.. toctree::
40+
:maxdepth: 2
41+
:caption: Visualisation:
42+
43+
visualisation

docs/source/tutorials/parameters_tutorial.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ For a guide on how to build a database, see the :doc:`Databases tutorial </tutor
1414

1515
Initialisation
1616
--------------
17+
1718
RAFFLE is initialised by importing the generator object.
1819
This object is the main interface for the user to interact with the RAFFLE package.
1920

@@ -61,6 +62,54 @@ As such, it is recommended that the user implement their own convergence check i
6162
The convergence criterion is also not enforced even when set, it is simply a check for the user to use.
6263

6364

65+
Placement methods
66+
-----------------
67+
68+
RAFFLE uses a random sampling of five placement methods to place atoms in the host structure.
69+
These methods are:
70+
- ``rand``: Randomly place atoms in the host structure.
71+
- ``void``: Place atoms at the point furthest from the nearest atom in the host structure.
72+
- ``walk``: Start from a random position and walk to a local minimum in the RAFFLE-calculated probability.
73+
- ``grow``: Start from the previously placed atom and walk to a local minimum in the RAFFLE-calculated probability.
74+
- ``min``: Place atoms at the point of highest RAFFLE-calculated probability.
75+
76+
The ratio of these methods can be set by the user.
77+
This can either be done on a per-generate method basis, or as a global setting.
78+
79+
To set the default placement method ratios, the user can use the ``set_method_ratio_default`` method.
80+
81+
.. code-block:: python
82+
83+
# Set default placement method ratios
84+
generator.set_method_ratio_default(
85+
method_ratio = {
86+
'rand': 0.1,
87+
'void': 0.1,
88+
'walk': 0.25,
89+
'grow': 0.25,
90+
'min': 1.0
91+
}
92+
)
93+
94+
These defaults will then be used for all generation methods where the user does not specify a method ratio.
95+
96+
To set the placement method ratios for a specific generation method, the user can define the method ratio ``dict`` in the ``generate`` method.
97+
98+
.. code-block:: python
99+
100+
# Set placement method ratios for a specific generation method
101+
generator.generate(
102+
method_ratio = {
103+
'rand': 0.1,
104+
'void': 0.1,
105+
'walk': 0.25,
106+
'grow': 0.25,
107+
'min': 1.0
108+
},
109+
# Other generation parameters
110+
...
111+
)
112+
64113
65114
Energy references
66115
-----------------
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
.. visualisation:
2+
3+
=============
4+
Visualisation
5+
=============
6+
7+
Here we detail the methods available for outputting the learned RAFFLE descriptor and RAFFLE fingerprints (distribution functions) for individual structures.
8+
9+
10+
Visualising the learned RAFFLE descriptor
11+
-----------------------------------------
12+
13+
RAFFLE generates 2-body, 3-body, and 4-body distribution functions for each atomic species in the system (element pairs for the 2-body function).
14+
These are generated by combining formation energy-weighted (or convex hull-weighted) `n`-body distribution functions for each structure provided in the learning database.
15+
16+
The ``raffle_generator`` object has a method ``get_descriptor()`` that returns the 2-, 3-, and 4-body forms of the learned RAFFLE generalised descriptor.
17+
The output is a list of three numpy arrays, with the first array containing the 2-body descriptor, the second array containing the 3-body descriptor, and the third array containing the 4-body descriptor.
18+
Each `n`-body descriptor is a 2D array, with the first column containing the species index (or element pair index for the 2-body descriptor) and the second column containing the binned descriptor value.
19+
The bin lengths are (``nbins`` component) set either explicitly or determined by the ``cutoff_min``, ``cutoff_max``, and ``width`` components of the generator.
20+
21+
Here is an example of how to use the ``get_descriptor()`` method:
22+
23+
.. code-block:: python
24+
25+
# Initialise RAFFLE generator
26+
from raffle.generator import raffle_generator
27+
28+
generator = raffle_generator()
29+
30+
# Set the host structure
31+
host = Atoms(
32+
# Host structure for the generator
33+
)
34+
generator.set_host(host)
35+
36+
# Set the reference energies (i.e. chemical potential references)
37+
generator.distributions.set_element_energies(
38+
{
39+
# reference energies for all elements in the systems
40+
}
41+
)
42+
43+
# Optional parameters
44+
generator.distributions.set_kBT(0.2)
45+
generator.distributions.set_width([0.04, np.pi/160.0, np.pi/160.0])
46+
generator.distributions.set_cutoff_min([0.5, 0.0, 0.0])
47+
generator.distributions.set_cutoff_max([6.0, np.pi, np.pi])
48+
49+
# Set and learn from the initial database
50+
database = [
51+
# List of structures in the learning database
52+
]
53+
generator.distributions.create(database)
54+
55+
# Retrieve the descriptor
56+
descriptor_init = generator.get_descriptor()
57+
58+
# Print the 2-body descriptor
59+
print("2-body descriptor:")
60+
print(descriptor_init[0])
61+
62+
# Print the 3-body descriptor
63+
print("3-body descriptor:")
64+
print(descriptor_init[1])
65+
66+
# Print the 4-body descriptor
67+
print("4-body descriptor:")
68+
print(descriptor_init[2])
69+
70+
With this, we can now plot the descriptor using any plotting library of your choice.
71+
72+
.. code-block:: python
73+
74+
import matplotlib.pyplot as plt
75+
import numpy as np
76+
77+
# Create a figure with 3 subplots side by side
78+
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
79+
80+
# Plot for each n-body descriptor (2-body, 3-body, 4-body)
81+
for j in range(3):
82+
# Calculate x-axis values
83+
x = np.arange(generator.distributions.cutoff_min[j],
84+
generator.distributions.cutoff_max[j] + generator.distributions.width[j],
85+
generator.distributions.width[j])
86+
87+
# Plot on the respective subplot
88+
for idx in range(len(descriptor_init[j])):
89+
axes[j].plot(x, descriptor_init[j][idx,:])
90+
91+
# Set labels and title for each subplot
92+
axes[j].set_ylabel('Descriptor value')
93+
axes[j].set_title(f'{j+2}-body descriptor')
94+
95+
axes[0].set_xlabel('Distance (Å)')
96+
axes[1].set_xlabel('3-body angle (radians)')
97+
axes[2].set_xlabel('Improper dihedral angle (radians)')
98+
plt.tight_layout()
99+
plt.show()
100+
101+
An example python notebook is provided in :git:`examples/python_pkg/visualisation/descriptor.ipynb <examples/python_pkg/visualisation/descriptor.ipynb>`
102+
103+
We can now use this to compare the initial descriptor with the updated descriptor after generating new structures.
104+
105+
.. code-block:: python
106+
107+
# Generate new structures and update the descriptor
108+
structures = [
109+
# List of structures to be generated
110+
]
111+
generator.distributions.update(structures)
112+
113+
# Retrieve the updated descriptor
114+
descriptor_new = generator.get_descriptor()
115+
116+
# Print the updated descriptor on the plots and compare
117+
...
118+
119+
120+
Visualising a RAFFLE fingerprint
121+
--------------------------------
122+
123+
RAFFLE fingerprints are the distribution functions for each structure in the learning database.
124+
These are then weighted by energy (formation or convex hull) to form the RAFFLE descriptor.
125+
126+
However, the individual fingerprints can also be extracted and visualised.
127+
128+
The `raffle_generator` object has a method `get_fingerprint()` that returns the distribution functions for a provided structure.
129+
The output is a list of three numpy arrays, with the first array containing the 2-body fingerprint, the second array containing the 3-body fingerprint, and the third array containing the 4-body fingerprint.
130+
Each `n`-body fingerprint is a 2D array, with the first column containing the species index (or element pair index for the 2-body fingerprint) and the second column containing the binned fingerprint value.
131+
Like above, the bin lengths are set either explicitly or determined by the `cutoff_min`, `cutoff_max`, and `width` components of the generator.
132+
Here is an example of how to use the `get_fingerprint()` method:
133+
134+
.. code-block:: python
135+
136+
# Initialise RAFFLE generator
137+
from raffle.generator import raffle_generator
138+
139+
generator = raffle_generator()
140+
141+
# Optional parameters
142+
generator.distributions.set_width([0.04, np.pi/160.0, np.pi/160.0])
143+
generator.distributions.set_cutoff_min([0.5, 0.0, 0.0])
144+
generator.distributions.set_cutoff_max([6.0, np.pi, np.pi])
145+
146+
# Structure to obtain the fingerprint for
147+
structure = Atoms(
148+
# Structure to be used for the fingerprint
149+
)
150+
151+
fingerprint = generator.distributions.generate_fingerprint(structure)
152+
153+
This can then be visualised in a similar way to the descriptor.
154+
155+
.. code-block:: python
156+
157+
# Create a figure with 3 subplots side by side
158+
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
159+
160+
# Plot for each n-body function (2-body, 3-body, 4-body)
161+
for j in range(3):
162+
# Calculate x-axis values
163+
x = np.arange(generator.distributions.cutoff_min[j],
164+
generator.distributions.cutoff_max[j] + generator.distributions.width[j],
165+
generator.distributions.width[j])
166+
167+
# Plot on the respective subplot
168+
for idx in range(len(fingerprint[j])):
169+
axes[j].plot(x, fingerprint[j][idx,:])
170+
171+
# Set labels and title for each subplot
172+
axes[j].set_ylabel('Fingerprint value')
173+
axes[j].set_title(f'{j+2}-body fingerprint')
174+
175+
axes[0].set_xlabel('Distance (Å)')
176+
axes[1].set_xlabel('3-body angle (radians)')
177+
axes[2].set_xlabel('Improper dihedral angle (radians)')
178+
plt.tight_layout()
179+
plt.show()
180+
181+
An example python notebook is provided in :git:`examples/python_pkg/visualisation/fingerprint.ipynb <examples/python_pkg/visualisation/fingerprint.ipynb>`.
182+
183+
184+
Visualising RAFFLE probability density
185+
--------------------------------------
186+
187+
RAFFLE probability density is the probability of finding a given element in a given position in the system.
188+
This is calculated by the RAFFLE generator and can be visualised using the `get_probability_density()` method.
189+
The output is a 2D array, with the first column containing the coordinates (spatial and species) the second column containing the binned probability density value.
190+
191+
A structure is provided to the `get_probability_density()`, along with a list of elements to calculate the probability density for.
192+
These elements must be present in the RAFFLE descriptor.
193+
194+
An example of how to use the `get_probability_density()` method is shown below:
195+
196+
.. code-block:: python
197+
198+
# Initialise RAFFLE generator
199+
from raffle.generator import raffle_generator
200+
201+
generator = raffle_generator()
202+
203+
generator.distributions.set_element_energies(
204+
{
205+
# reference energies for all elements in the systems
206+
}
207+
)
208+
209+
database = [
210+
# List of structures in the learning database
211+
]
212+
generator.distributions.create(database)
213+
214+
# Structure to obtain the probability density for
215+
structure = Atoms(
216+
# Structure to be used for the probability density
217+
)
218+
219+
species = 'SiGe'
220+
221+
probability_density, grid = generator.get_probability_density(structure, species, return_grid=True)
222+
223+
The first index of the first column of `probability_density` is the x-coordinate, the second index is the y-coordinate, and the third index is the z-coordinate.
224+
The fourth index is the distance between the position and the nearest atom (i.e. the void value).
225+
The fifth index onwards is the species index (in order of the species list provided).
226+
The second column is the site index.
227+
228+
For a more extensive example, see the `examples/python_pkg/visualisation/probability_density.ipynb` notebook.
229+
This also provides a visualisation of the probability density using the `matplotlib` library.

0 commit comments

Comments
 (0)