diff --git a/other/materials_designer/Introduction.ipynb b/other/materials_designer/Introduction.ipynb
index c54e30a7..8568b4c3 100644
--- a/other/materials_designer/Introduction.ipynb
+++ b/other/materials_designer/Introduction.ipynb
@@ -62,6 +62,7 @@
"\n",
"### 3.3. Planar Defects\n",
"#### [3.3.1. Grain Boundary in a 3D Crystal](create_grain_boundary_crystal.ipynb)\n",
+ "#### [3.3.2. Grain Boundary in a 2D Material](create_grain_boundary_film.ipynb)\n",
"\n",
"\n",
"## 4. Passivation.\n",
diff --git a/other/materials_designer/create_grain_boundary_film.ipynb b/other/materials_designer/create_grain_boundary_film.ipynb
new file mode 100644
index 00000000..cab9e8c7
--- /dev/null
+++ b/other/materials_designer/create_grain_boundary_film.ipynb
@@ -0,0 +1,322 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Create a 2D Surface Grain Boundary in a film\n",
+ "\n",
+ "Use commensurate lattice matching algorithm to create grain boundaries in 2D materials by finding appropriate twist angles between two orientations of the same material.\n",
+ "\n",
+ "
Usage
\n",
+ "\n",
+ "1. Make sure to select Input Material (in the outer runtime) before running the notebook.\n",
+ "1. Set notebook parameters in cell 1.1. below (or use the default values).\n",
+ "1. Click \"Run\" > \"Run All\" to run all cells.\n",
+ "1. Wait for the run to complete (depending on the parameters can take a few min).\n",
+ "1. Scroll down to view results.\n",
+ "\n",
+ "## Notes\n",
+ "\n",
+ "1. We perform commensurate lattice matching to find valid supercells that achieve the desired twist angle.\n",
+ "1. When the matching is finished, grain boundaries with angles close to the target are presented.\n",
+ "1. The algorithm searches for supercell matrices within specified size limits.\n",
+ "2. The two orientations are placed next to each other in the x-direction with a gap in between.\n",
+ "1. Atoms on the edge of the left orientation are handled to overlap with the right orientation in the interfacial region.\n",
+ "1. For more information, see the [Introduction](Introduction.ipynb) notebook.\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "415ed707e27a6c8e"
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 1. Prepare the Environment\n",
+ "### 1.1. Set up the notebook\n",
+ "Set the following flags to control the notebook behavior\n",
+ "For more information on the parameters and algorithm, refer to [Grain Boundary Builder Source](https://github.com/Exabyte-io/made/blob/35b9f318f5d667e0f5af023f3178bc4404317ab0/src/py/mat3ra/made/tools/build/grain_boundary/builders.py#L103)\n",
+ "`EDGE_INCLUSION_TOLERANCE` is a fine-tuning parameter that controls the inclusion of the edge atoms for both orientations in the gap.\n",
+ "For example of Graphene at 17.9 degrees: orange and green atoms are present with the value of 0.5 Angstroms, with value of 0, they will not be included.\n",
+ "\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "a080006df3785cc5"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "# Material selection\n",
+ "MATERIAL_INDEX = 0 # Index in the list of materials\n",
+ "\n",
+ "# Grain boundary parameters\n",
+ "TARGET_TWIST_ANGLE = 17.9 # in degrees\n",
+ "BOUNDARY_GAP = 2.0 # Gap between two orientations in X direction, in Angstroms\n",
+ "XY_SUPERCELL_MATRIX = [[1, 0], [0, 1]] # Supercell matrix to be applied to each of the orientations before matching\n",
+ "\n",
+ "# Search algorithm parameters\n",
+ "MAX_REPETITION = 6 # Maximum supercell matrix element value\n",
+ "ANGLE_TOLERANCE = 2.5 # in degrees\n",
+ "RETURN_FIRST_MATCH = True # If True, returns first solution within tolerance\n",
+ "\n",
+ "# Distance tolerance for two atoms to be considered too close. \n",
+ "# Used when merging two orientations to remove the atoms of the first one. \n",
+ "# Should be less than the expected bond length\n",
+ "DISTANCE_TOLERANCE = 1.2 # in Angstroms\n",
+ "\n",
+ "# How much to expand inclusion of the edge atoms for both orientations and fill in the gap region.\n",
+ "# A fine-tuning parameter\n",
+ "EDGE_INCLUSION_TOLERANCE = 0.5 # in Angstroms\n",
+ "\n",
+ "# Visualization parameters\n",
+ "SHOW_INTERMEDIATE_STEPS = True\n",
+ "CELL_REPETITIONS_FOR_VISUALIZATION = [3, 3, 1]"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "338ee3c51155e086",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### 1.2. Install packages\n",
+ "The step executes only in Pyodide environment. For other environments, the packages should be installed via `pip install`."
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "6463f9bbcd3be7c7"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "\n",
+ "if sys.platform == \"emscripten\":\n",
+ " import micropip\n",
+ "\n",
+ " await micropip.install('mat3ra-api-examples', deps=False)\n",
+ " from utils.jupyterlite import install_packages\n",
+ "\n",
+ " await install_packages(\"\", \"../../config.yml\")"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "7e22d1f4da825575",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### 1.3. Load and preview input material"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "4a1cfe15caa44c3e"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "from utils.jupyterlite import get_materials\n",
+ "\n",
+ "materials = get_materials(globals())"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "a1635c31132962f6",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 2. Prepare Material\n",
+ "### 2.1. Select and visualize initial material"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "32b3ad775543b06f"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "from utils.visualize import visualize_materials\n",
+ "\n",
+ "material = materials[MATERIAL_INDEX]\n",
+ "\n",
+ "if SHOW_INTERMEDIATE_STEPS:\n",
+ " visualize_materials(material, repetitions=CELL_REPETITIONS_FOR_VISUALIZATION)"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "61f0870d8104cd21",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 3. Generate Surface Grain Boundary\n",
+ "### 3.1. Set up grain boundary configuration and builder\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "34d6c7a337f1e40b"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "from mat3ra.made.tools.build.grain_boundary import (\n",
+ " SurfaceGrainBoundaryConfiguration,\n",
+ " SurfaceGrainBoundaryBuilderParameters,\n",
+ " SurfaceGrainBoundaryBuilder\n",
+ ")\n",
+ "\n",
+ "config = SurfaceGrainBoundaryConfiguration(\n",
+ " film=material,\n",
+ " twist_angle=TARGET_TWIST_ANGLE,\n",
+ " distance_z=BOUNDARY_GAP,\n",
+ " gap=BOUNDARY_GAP,\n",
+ " xy_supercell_matrix=XY_SUPERCELL_MATRIX\n",
+ ")\n",
+ "\n",
+ "params = SurfaceGrainBoundaryBuilderParameters(\n",
+ " max_supercell_matrix_int=MAX_REPETITION,\n",
+ " angle_tolerance=ANGLE_TOLERANCE,\n",
+ " return_first_match=RETURN_FIRST_MATCH,\n",
+ " edge_inclusion_tolerance=EDGE_INCLUSION_TOLERANCE,\n",
+ " distance_tolerance=DISTANCE_TOLERANCE\n",
+ ")\n",
+ "\n",
+ "builder = SurfaceGrainBoundaryBuilder(build_parameters=params)"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "33a2c8a9be436745",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### 3.2. Generate and analyze grain boundaries\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "79e9378bf5e144d4"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "from utils.plot import plot_twisted_interface_solutions\n",
+ "\n",
+ "grain_boundaries = builder.get_materials(config)\n",
+ "\n",
+ "print(f\"\\nFound {len(grain_boundaries)} possible structures\")\n",
+ "for i, gb in enumerate(grain_boundaries):\n",
+ " actual_angle = gb.metadata.get(\"actual_twist_angle\", \"unknown\")\n",
+ " print(f\"\\nGrain Boundary {i + 1}:\")\n",
+ " print(f\"Actual twist angle: {actual_angle}°\")\n",
+ " print(f\"Number of atoms: {len(gb.basis.elements.ids)}\")\n",
+ "\n",
+ "if len(grain_boundaries) > 0:\n",
+ " plot_twisted_interface_solutions(grain_boundaries)"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "d7007fe825463e5a",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## 4. Preview the selected grain boundary\n",
+ "By default, the first grain boundary is selected. You can change the selection by changing the `selected_structure` index."
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "8b2f0574a20089a5"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "selected_structure = grain_boundaries[0]\n",
+ "actual_angle = selected_structure.metadata.get(\"build\").get(\"configuration\").get(\"actual_twist_angle\")\n",
+ "print(f\"Target angle: {TARGET_TWIST_ANGLE}°\")\n",
+ "print(f\"Actual angle: {actual_angle}°\")\n",
+ "print(f\"Number of atoms: {len(selected_structure.basis.elements.ids)}\")\n",
+ "\n",
+ "visualize_materials(selected_structure, repetitions=[1, 1, 1])\n",
+ "visualize_materials(selected_structure, repetitions=[1, 1, 1], rotation=\"-90x\")"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "7f558a8e9d417cef",
+ "execution_count": null
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "### 5. Pass data to the outside runtime\n"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "afcc004c5878b56f"
+ },
+ {
+ "cell_type": "code",
+ "outputs": [],
+ "source": [
+ "from utils.jupyterlite import set_materials\n",
+ "\n",
+ "set_materials(selected_structure)"
+ ],
+ "metadata": {
+ "collapsed": false
+ },
+ "id": "20e46167358d63",
+ "execution_count": null
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}