from ..Primatom import GAM_Atom , GAM_Bond
import numpy as np
from ase import Atoms
from typing import Tuple, Optional, Dict, Union, Literal , List
import warnings
from ase.visualize import view
from ase.io import write
import matplotlib.pyplot as plt
from ase.io.pov import get_bondpairs
import copy
from ase.build import graphene
from ase.build import mx2
from ase.atoms import Atoms
import copy
try:
import ase
from ase import Atoms
HAS_ASE = True
except ImportError:
HAS_ASE = False
LAYERED_MATERIALS = {
# Group 6 TMDs (most stable in 2H phase)
'mos2': {
'formula': 'MoS2',
'lattice_constant': 3.160,
'thickness': 6.15,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Molybdenum disulfide - semiconductor'
},
'mose2': {
'formula': 'MoSe2',
'lattice_constant': 3.289,
'thickness': 6.46,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Molybdenum diselenide - semiconductor'
},
'mote2': {
'formula': 'MoTe2',
'lattice_constant': 3.518,
'thickness': 6.97,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Molybdenum ditelluride - semiconductor'
},
'ws2': {
'formula': 'WS2',
'lattice_constant': 3.153,
'thickness': 6.18,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Tungsten disulfide - semiconductor'
},
'wse2': {
'formula': 'WSe2',
'lattice_constant': 3.282,
'thickness': 6.49,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Tungsten diselenide - semiconductor'
},
'wte2': {
'formula': 'WTe2',
'lattice_constant': 3.496,
'thickness': 7.04,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Tungsten ditelluride - semimetal'
},
# Group 5 TMDs (can be metallic in 1T phase)
'vs2': {
'formula': 'VS2',
'lattice_constant': 3.160,
'thickness': 5.76,
'preferred_phase': '1T',
'stable_phases': ['1T', '2H'],
'description': 'Vanadium disulfide - metallic'
},
'vse2': {
'formula': 'VSe2',
'lattice_constant': 3.355,
'thickness': 6.10,
'preferred_phase': '1T',
'stable_phases': ['1T', '2H'],
'description': 'Vanadium diselenide - metallic'
},
'nbs2': {
'formula': 'NbS2',
'lattice_constant': 3.313,
'thickness': 5.985,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Niobium disulfide - metallic'
},
'nbse2': {
'formula': 'NbSe2',
'lattice_constant': 3.445,
'thickness': 6.26,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Niobium diselenide - superconductor'
},
'tas2': {
'formula': 'TaS2',
'lattice_constant': 3.314,
'thickness': 5.895,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Tantalum disulfide - metallic/CDW'
},
'tase2': {
'formula': 'TaSe2',
'lattice_constant': 3.436,
'thickness': 6.25,
'preferred_phase': '2H',
'stable_phases': ['2H', '1T'],
'description': 'Tantalum diselenide - superconductor'
},
# Group 4 TMDs
'tis2': {
'formula': 'TiS2',
'lattice_constant': 3.407,
'thickness': 5.695,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Titanium disulfide - metallic'
},
'tise2': {
'formula': 'TiSe2',
'lattice_constant': 3.535,
'thickness': 6.008,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Titanium diselenide - semimetal/CDW'
},
'zrs2': {
'formula': 'ZrS2',
'lattice_constant': 3.658,
'thickness': 5.83,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Zirconium disulfide - semiconductor'
},
'zrse2': {
'formula': 'ZrSe2',
'lattice_constant': 3.77,
'thickness': 6.13,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Zirconium diselenide - semiconductor'
},
'hfs2': {
'formula': 'HfS2',
'lattice_constant': 3.615,
'thickness': 5.85,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Hafnium disulfide - semiconductor'
},
'hfse2': {
'formula': 'HfSe2',
'lattice_constant': 3.743,
'thickness': 6.15,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Hafnium diselenide - semiconductor'
},
# Group 7 TMDs
'res2': {
'formula': 'ReS2',
'lattice_constant': 3.144,
'thickness': 6.05,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Rhenium disulfide - anisotropic semiconductor'
},
'rese2': {
'formula': 'ReSe2',
'lattice_constant': 3.300,
'thickness': 6.34,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Rhenium diselenide - anisotropic semiconductor'
},
# Group 10 TMDs
'pts2': {
'formula': 'PtS2',
'lattice_constant': 3.540,
'thickness': 5.036,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Platinum disulfide - semiconductor'
},
'ptse2': {
'formula': 'PtSe2',
'lattice_constant': 3.728,
'thickness': 5.081,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Platinum diselenide - semimetal'
},
# Metal halides
'cri3': {
'formula': 'CrI3',
'lattice_constant': 6.867,
'thickness': 6.60,
'preferred_phase': '2H',
'stable_phases': ['2H'],
'description': 'Chromium triiodide - 2D ferromagnet'
},
'crbr3': {
'formula': 'CrBr3',
'lattice_constant': 6.30,
'thickness': 6.12,
'preferred_phase': '2H',
'stable_phases': ['2H'],
'description': 'Chromium tribromide - 2D ferromagnet'
},
'vi3': {
'formula': 'VI3',
'lattice_constant': 6.86,
'thickness': 6.74,
'preferred_phase': '2H',
'stable_phases': ['2H'],
'description': 'Vanadium triiodide - 2D ferromagnet'
},
# Other layered materials
'bi2se3': {
'formula': 'Bi2Se3', # Simplified for mx2 function
'lattice_constant': 4.138,
'thickness': 9.54,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Bismuth selenide - topological insulator'
},
'bi2te3': {
'formula': 'Bi2Te3', # Simplified for mx2 function
'lattice_constant': 4.386,
'thickness': 10.15,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Bismuth telluride - topological insulator'
},
'sb2te3': {
'formula': 'Sb2Te3', # Simplified for mx2 function
'lattice_constant': 4.264,
'thickness': 10.33,
'preferred_phase': '1T',
'stable_phases': ['1T'],
'description': 'Antimony telluride - topological insulator'
}
}
# Material parameters database - all designed to work with the graphene function
NANOSHEET_MATERIALS = {
'graphene': {
'formula': 'C2',
'lattice_constant': 2.460,
'thickness': 0.0,
'description': 'Pure graphene - carbon honeycomb lattice'
},
'silicene': {
'formula': 'Si2',
'lattice_constant': 3.86,
'thickness': 0.44,
'description': 'Silicon analogue of graphene with buckling'
},
'germanene': {
'formula': 'Ge2',
'lattice_constant': 4.02,
'thickness': 0.64,
'description': 'Germanium analogue of graphene with buckling'
},
'stanene': {
'formula': 'Sn2',
'lattice_constant': 4.67,
'thickness': 0.85,
'description': 'Tin analogue of graphene with buckling'
},
'plumbene': {
'formula': 'Pb2',
'lattice_constant': 4.97,
'thickness': 0.95,
'description': 'Lead analogue of graphene with buckling'
},
'boron_nitride': {
'formula': 'BN',
'lattice_constant': 2.504,
'thickness': 0.0,
'description': 'Hexagonal boron nitride (white graphene)'
},
'aluminum_nitride': {
'formula': 'AlN',
'lattice_constant': 3.111,
'thickness': 0.0,
'description': 'Aluminum nitride honeycomb structure'
},
'gallium_nitride': {
'formula': 'GaN',
'lattice_constant': 3.189,
'thickness': 0.0,
'description': 'Gallium nitride honeycomb structure'
},
'phosphorene': {
'formula': 'P2', # Simplified to work with graphene function
'lattice_constant': 3.314,
'thickness': 1.05, # Half the actual buckling for the graphene function
'description': 'Black phosphorus (simplified honeycomb approximation)'
},
'arsenene': {
'formula': 'As2',
'lattice_constant': 3.608,
'thickness': 1.4,
'description': 'Arsenic analogue of phosphorene'
},
'antimonene': {
'formula': 'Sb2',
'lattice_constant': 4.120,
'thickness': 1.65,
'description': 'Antimony analogue of phosphorene'
},
'bismuthene': {
'formula': 'Bi2',
'lattice_constant': 4.54,
'thickness': 1.86,
'description': 'Bismuth analogue of phosphorene'
},
'beryllium_oxide': {
'formula': 'BeO',
'lattice_constant': 2.698,
'thickness': 0.0,
'description': 'Beryllium oxide honeycomb structure'
},
'zinc_oxide': {
'formula': 'ZnO',
'lattice_constant': 3.249,
'thickness': 0.6,
'description': 'Zinc oxide honeycomb structure with buckling'
},
'cadmium_sulfide': {
'formula': 'CdS',
'lattice_constant': 4.136,
'thickness': 0.8,
'description': 'Cadmium sulfide honeycomb structure'
}
}
[docs]
class Nano_TwoD_Builder:
"""
Comprehensive builder for 2D nanomaterials such as nanosheets, monolayers, and van der Waals heterostructures.
The `Nano_TwoD_Builder` class provides an advanced atomistic modeling framework
for constructing two-dimensional (2D) nanostructures, including:
- **Nanosheets:** elemental or binary 2D lattices (e.g., graphene, silicene, phosphorene)
- **Transition Metal Dichalcogenides (TMDs):** MXâ‚‚ layered compounds with tunable 1T/2H phases
- **Heterostructures:** stacked multilayer systems with controlled interlayer spacing
The builder supports both *honeycomb-like* (graphene family) and *layered* (TMD) topologies.
It integrates with ASE’s `graphene()` and `mx2()` generators and enables
direct conversion to GAM-compatible formats for downstream simulation pipelines.
Features
--------
- Generate 2D nanosheets and TMD monolayers
- Build van der Waals heterostructures from arbitrary combinations of layers
- Support for custom formulas, lattice constants, and layer thicknesses
- Predefined constructors for common 2D materials (graphene, MoSâ‚‚, WSâ‚‚, etc.)
- ASE-based export and visualization (XYZ, CIF, PDB)
- Structure translation and rotation operations
- GAM-compatible atom and bond representation
Parameters
----------
material : str
Name or symbol of the 2D material (e.g., "graphene", "MoS2", "BN").
structure_type : {'nanosheet', 'tmd'}
Type of 2D nanostructure to build.
Attributes
----------
ASE_atoms : ase.Atoms
Underlying ASE structure representing the generated material.
atoms : list[GAM_Atom]
List of atoms in GAM-compatible format.
bonds : list[GAM_Bond]
Optional list of identified bonds.
layered_materials : dict
Database of transition metal dichalcogenides (TMDs) and related 2D materials.
nanosheet_materials : dict
Database of single-element or binary 2D sheet materials.
meta : dict
Metadata dictionary containing build parameters and structure details.
structure_type : str
Selected structure type ('nanosheet' or 'tmd').
material : str
Material name used for generation.
Methods
-------
create_layered_material(phase='2H', size=(10,10,1), vacuum=10.0, ...)
Build a TMD or layered 2D material (e.g., MoS₂, WSe₂) using ASE’s `mx2()` function.
create_nanosheet(size=(10,10,1), vacuum=10.0, ...)
Build a honeycomb-like nanosheet (e.g., graphene, silicene) using ASE’s `graphene()` function.
create_layered_heterostructure(materials_and_phases, layer_spacing=6.5, ...)
Stack multiple TMD layers with custom interlayer spacing.
create_nanosheet_heterostructure(materials, layer_spacing=3.4, ...)
Build van der Waals heterostructures combining 2D sheets (e.g., graphene/hBN).
create_custom_sheet(formula, lattice_constant, thickness=0.0, ...)
Define a custom 2D lattice from scratch.
list_layered_materials(category=None)
Display all available layered (TMD) materials in the internal database.
list_nanosheet_materials()
Display all available 2D sheet materials and their physical properties.
translate(dx, dy, dz=0.0)
Translate atomic coordinates in 3D space.
rotate(angle_deg, about_center=True)
Rotate the sheet around the z-axis (in-plane rotation).
copy()
Deep copy the atomic structure and metadata.
get_atoms()
Return a deep copy of all atoms as GAM_Atom objects.
get_positions()
Retrieve atomic positions as (x, y, z) tuples.
get_elements()
Return list of atomic symbols.
to_xyz(filename)
Export structure to `.xyz` file format.
to_ase()
Convert to an ASE `Atoms` object for external use.
Notes
-----
- The builder automatically selects between `graphene()` and `mx2()` constructors
based on the specified `structure_type`.
- Both nanosheet and layered materials can be stacked into heterostructures
with adjustable vacuum spacing and interlayer distances.
- Built-in materials include metallic, semiconducting, and superconducting 2D systems:
MoS₂, WS₂, MoSe₂, WSe₂, NbSe₂, TaSe₂, PtS₂, CrI₃, graphene, silicene, BN, phosphorene, etc.
- The `meta` dictionary records all parameters used in construction,
enabling reproducibility and dataset traceability.
- All units are expressed in Ångströms unless otherwise noted.
Examples
--------
>>> from pygamlab import Nano_TwoD_Builder
>>> # 1. Generate a graphene sheet
>>> sheet = Nano_TwoD_Builder(material="graphene", structure_type="nanosheet")
>>> sheet.to_xyz("graphene.xyz")
>>> # 2. Build a MoS2 monolayer in the 2H phase
>>> tmd = Nano_TwoD_Builder(material="MoS2", structure_type="tmd")
>>> tmd.create_layered_material(phase='2H', size=(5,5,1), vacuum=15.0)
>>> tmd.to_xyz("MoS2_2H.xyz")
>>> # 3. Construct a van der Waals heterostructure: Graphene/hBN
>>> het = Nano_TwoD_Builder(material="graphene", structure_type="nanosheet")
>>> het.create_nanosheet_heterostructure(["graphene", "boron_nitride"], layer_spacing=3.35)
>>> het.to_xyz("graphene_hbn_heterostructure.xyz")
>>> # 4. Create a custom 2D material
>>> custom = Nano_TwoD_Builder(material="CustomX", structure_type="nanosheet")
>>> custom.create_custom_sheet(formula="X2", lattice_constant=3.6, thickness=0.2)
>>> custom.to_xyz("CustomX_sheet.xyz")
"""
def __init__(self,material: str, structure_type:str):
self.atoms: List[GAM_Atom] = []
self.bonds: List[GAM_Bond] = []
self.structure_type = structure_type
self.material = material
self.meta={'material':material,
'structure_type': structure_type}
self.layered_materials = LAYERED_MATERIALS.copy()
self.nanosheet_materials = NANOSHEET_MATERIALS.copy()
if structure_type.lower() == "nanosheet":
self.ASE_atoms=self.create_nanosheet()
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
elif structure_type.lower() == "tmd":
self.ASE_atoms=self.create_layered_material()
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
else:
raise ValueError("Invalid structure type. Must be 'nanosheet', 'nanoribbon', or 'nanotube'.")
[docs]
def create_layered_material(self,phase: Optional[str] = None,
size: Tuple[int, int, int] = (10,10, 1),
vacuum: Optional[float] = 10.0,
custom_formula: Optional[str] = None,
custom_lattice_constant: Optional[float] = None,
custom_thickness: Optional[float] = None) -> Atoms:
"""
Create a layered material using the ASE mx2 function.
Parameters
----------
material : str
Material name from database
phase : str, optional
Crystal phase ('2H' or '1T'). Uses preferred phase if None.
size : tuple
Supercell size (nx, ny, nz)
vacuum : float, optional
Vacuum spacing in z-direction
custom_formula : str, optional
Override default formula
custom_lattice_constant : float, optional
Override default lattice constant
custom_thickness : float, optional
Override default thickness
Returns
-------
Atoms
Layered material structure
"""
material_key = self.material.lower().replace(' ', '').replace('-', '')
if material_key not in self.layered_materials:
available = ', '.join(self.layered_materials.keys())
raise ValueError(f"Material '{self.material}' not found. Available: {available}")
props = self.layered_materials[material_key]
# Determine phase
if phase is None:
phase = props['preferred_phase']
elif phase not in props['stable_phases']:
stable = ', '.join(props['stable_phases'])
warnings.warn(f"Phase '{phase}' may not be stable for {self.material}. "
f"Stable phases: {stable}")
# Get parameters (with custom overrides)
formula = custom_formula or props['formula']
a = custom_lattice_constant or props['lattice_constant']
thickness = custom_thickness or props['thickness']
# Create structure using mx2 function
atoms = mx2(
formula=formula,
kind=phase,
a=a,
thickness=thickness,
size=size,
vacuum=vacuum
)
self.meta={
'material':self.material,
'structure_type': self.structure_type,
'phase':phase,
'size':size,
'vacuum': vacuum,
'formula': custom_formula,
'lattice_constant': custom_lattice_constant,
'thickness' : custom_thickness,
}
return atoms
# Convenience methods for common TMDs
[docs]
def mos2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
"""Create MoS2 sheet."""
if self.material.lower()!='mos2':
raise ValueError(f"Your material is {self.material} and you want to create mos2, build this class again.")
#self.ASE_atoms = self.create_layered_material('mos2', phase=phase, size=size, vacuum=vacuum)
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('mos2', phase=phase, size=size, vacuum=vacuum)
[docs]
def ws2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='ws2':
raise ValueError(f"Your material is {self.material} and you want to create ws2, build this class again.")
"""Create WS2 sheet."""
self.ASE_atoms = self.create_layered_material( phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('ws2', phase=phase, size=size, vacuum=vacuum)
[docs]
def mose2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='mose2':
raise ValueError(f"Your material is {self.material} and you want to create mose2, build this class again.")
"""Create MoSe2 sheet."""
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('mose2', phase=phase, size=size, vacuum=vacuum)
[docs]
def wse2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='wse2':
raise ValueError(f"Your material is {self.material} and you want to create wse2, build this class again.")
"""Create WSe2 sheet."""
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('wse2', phase=phase, size=size, vacuum=vacuum)
[docs]
def nbse2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='nbse2':
raise ValueError(f"Your material is {self.material} and you want to create nbse2, build this class again.")
"""Create NbSe2 sheet (superconductor)."""
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('nbse2', phase=phase, size=size, vacuum=vacuum)
[docs]
def tase2_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='tase2':
raise ValueError(f"Your material is {self.material} and you want to create tase2, build this class again.")
"""Create TaSe2 sheet (superconductor)."""
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('tase2', phase=phase, size=size, vacuum=vacuum)
[docs]
def pts2_sheet(self, phase='1T', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='pts2':
raise ValueError(f"Your material is {self.material} and you want to create pts2, build this class again.")
"""Create PtS2 sheet (semiconductor)."""
self.ASE_atoms = self.create_layered_material(phase, size, vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('pts2', phase=phase, size=size, vacuum=vacuum)
[docs]
def cri3_sheet(self, phase='2H', size=(1, 1, 1), vacuum=10.0):
if self.material.lower()!='cris3':
raise ValueError(f"Your material is {self.material} and you want to create cris3, build this class again.")
"""Create CrI3 sheet (2D ferromagnet)."""
self.ASE_atoms = self.create_layered_material(phase=phase, size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
#return self.create_layered_material('cri3', phase=phase, size=size, vacuum=vacuum)
[docs]
def create_layered_heterostructure(self,
materials_and_phases: list,
layer_spacing: float = 6.5,
size: Tuple[int, int, int] = (1, 1, 1),
vacuum: Optional[float] = 10.0) -> GAM_Atom:
"""
Create a heterostructure from multiple layered materials.
Parameters
----------
materials_and_phases : list
List of tuples: [(material, phase), ...] or list of material names
layer_spacing : float
Distance between layers
size : tuple
Supercell size
vacuum : float, optional
Total vacuum spacing
Returns
-------
Atoms
Heterostructure
"""
if len(materials_and_phases) < 2:
raise ValueError("Need at least 2 materials for heterostructure")
layers = []
z_offsets = []
current_z = 0
for i, item in enumerate(materials_and_phases):
# Handle both (material, phase) tuples and simple material names
if isinstance(item, (list, tuple)) and len(item) == 2:
material, phase = item
else:
material, phase = item, None
# Create layer
self.material=material
layer = self.create_layered_material(phase=phase, size=size, vacuum=None)
layers.append(layer)
z_offsets.append(current_z)
# Calculate next layer position
if i < len(materials_and_phases) - 1:
material_key = material.lower().replace(' ', '').replace('-', '')
layer_thickness = self.layered_materials[material_key]['thickness']
current_z += layer_thickness + layer_spacing
# Combine layers
all_positions = []
all_symbols = []
for layer, z_offset in zip(layers, z_offsets):
positions = layer.get_positions()
positions[:, 2] += z_offset
all_positions.extend(positions)
all_symbols.extend(layer.get_chemical_symbols())
# Create combined structure
cell = layers[0].cell.copy()
total_height = current_z + (vacuum if vacuum else 15.0)
cell[2, 2] = total_height
heterostructure = Atoms(
symbols=all_symbols,
positions=all_positions,
cell=cell,
pbc=(1, 1, 0)
)
if vacuum:
heterostructure.center(vacuum, axis=2)
self.ASE_atoms=heterostructure
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
self.meta={'materials_and_phases':materials_and_phases,
'structure_type':self.structure_type,
'layer_spacing':layer_spacing,
'size':size,
'vacuum':vacuum}
#return self.atoms
[docs]
def list_layered_materials(self, category: Optional[str] = None) -> None:
"""
Display available materials.
Parameters
----------
category : str, optional
Filter by category ('tmd', 'halide', 'topological', etc.)
"""
print("Available Layered Materials (using ASE mx2 function):")
print("=" * 65)
for name, props in self.layered_materials.items():
if category and category.lower() not in props['description'].lower():
continue
print(f"{name.upper()}:")
print(f" Formula: {props['formula']}")
print(f" Lattice constant: {props['lattice_constant']:.3f} Ã…")
print(f" Thickness: {props['thickness']:.2f} Ã…")
print(f" Preferred phase: {props['preferred_phase']}")
print(f" Stable phases: {', '.join(props['stable_phases'])}")
print(f" Description: {props['description']}")
print()
#===================== ====================
#===================== ====================
#===================== ====================
#===================== Nnaosheet
#===================== ====================
#===================== ====================
#====================== ====================
[docs]
def create_nanosheet(self,
size: Tuple[int, int, int] = (10, 10, 1),
vacuum: Optional[float] = 10.0,
custom_formula: Optional[str] = None,
custom_lattice_constant: Optional[float] = None,
custom_thickness: Optional[float] = None) -> Atoms:
"""
Create a nanosheet using the ASE graphene function with modified parameters.
Parameters
----------
material : str
Material name from the database
size : tuple
Supercell size (nx, ny, nz)
vacuum : float, optional
Vacuum spacing in z-direction
custom_formula : str, optional
Override the default formula
custom_lattice_constant : float, optional
Override the default lattice constant
custom_thickness : float, optional
Override the default thickness
Returns
-------
Atoms
ASE Atoms object of the nanosheet
"""
material_key = self.material.lower().replace(' ', '_').replace('-', '_')
if material_key not in self.nanosheet_materials:
available = ', '.join(self.nanosheet_materials.keys())
raise ValueError(f"Material '{self.material}' not found. Available: {available}")
# Get material properties
props = self.nanosheet_materials[material_key]
# Use custom parameters if provided
formula = custom_formula or props['formula']
a = custom_lattice_constant or props['lattice_constant']
thickness = custom_thickness or props['thickness']
# Call the original graphene function with modified parameters
atoms = graphene(
formula=formula,
a=a,
thickness=thickness,
size=size,
vacuum=vacuum
)
self.meta={'material':self.material,
'structure_type':self.structure_type,
'size':size,
'vacuum': vacuum,
'formula':custom_formula,
'lattice_constant':custom_lattice_constant,
'thickness':custom_thickness}
#self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
return atoms
[docs]
def graphene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create graphene nanosheet."""
self.ASE_atoms=self.create_nanosheet('graphene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def silicene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create silicene nanosheet."""
self.ASE_atoms=self.create_nanosheet('silicene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def germanene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create germanene nanosheet."""
self.ASE_atoms= self.create_nanosheet('germanene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def stanene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create stanene nanosheet."""
self.ASE_atoms= self.create_nanosheet('stanene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def boron_nitride_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create hexagonal boron nitride nanosheet."""
self.ASE_atoms= self.create_nanosheet('boron_nitride', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def phosphorene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create phosphorene nanosheet (honeycomb approximation)."""
self.ASE_atoms= self.create_nanosheet('phosphorene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def arsenene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create arsenene nanosheet."""
self.ASE_atoms= self.create_nanosheet('arsenene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def antimonene_sheet(self, size=(1, 1, 1), vacuum=10.0):
"""Create antimonene nanosheet."""
self.ASE_atoms= self.create_nanosheet('antimonene', size=size, vacuum=vacuum)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def create_custom_sheet(self,
formula: str,
lattice_constant: float,
thickness: float = 0.0,
size: Tuple[int, int, int] = (1, 1, 1),
vacuum: Optional[float] = 10.0) -> GAM_Atom:
"""
Create a custom nanosheet with specified parameters.
Parameters
----------
formula : str
Chemical formula (e.g., 'C2', 'Si2', 'BN')
lattice_constant : float
Lattice constant in Angstrom
thickness : float
Thickness/buckling parameter
size : tuple
Supercell size
vacuum : float, optional
Vacuum spacing
Returns
-------
Atoms
Custom nanosheet structure
"""
self.ASE_atoms=graphene(
formula=formula,
a=lattice_constant,
thickness=thickness,
size=size,
vacuum=vacuum
)
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
#return self.atoms
[docs]
def create_nanosheet_heterostructure(self,
materials: list,
layer_spacing: float = 3.4,
size: Tuple[int, int, int] = (1, 1, 1),
vacuum: Optional[float] = 10.0) -> GAM_Atom:
"""
Create a heterostructure by stacking different materials.
Parameters
----------
materials : list
List of material names to stack
layer_spacing : float
Distance between layers in Angstrom
size : tuple
Supercell size for each layer
vacuum : float, optional
Total vacuum spacing
Returns
-------
Atoms
Heterostructure combining multiple materials
"""
if len(materials) < 2:
raise ValueError("Need at least 2 materials for heterostructure")
layers = []
z_offsets = []
current_z = 0
for i, material in enumerate(materials):
# Create layer without vacuum
layer = self.create_nanosheet(material, size=size, vacuum=None)
layers.append(layer)
z_offsets.append(current_z)
# Calculate next layer position
if i < len(materials) - 1:
layer_thickness = self.nanosheet_materials[material.lower().replace(' ', '_').replace('-', '_')]['thickness']
current_z += max(layer_thickness, 0.1) + layer_spacing
# Combine all layers
all_positions = []
all_symbols = []
for layer, z_offset in zip(layers, z_offsets):
positions = layer.get_positions()
positions[:, 2] += z_offset
all_positions.extend(positions)
all_symbols.extend(layer.get_chemical_symbols())
# Create new cell with appropriate z-dimension
cell = layers[0].cell.copy()
total_height = current_z + (vacuum if vacuum else 10.0)
cell[2, 2] = total_height
heterostructure = Atoms(
symbols=all_symbols,
positions=all_positions,
cell=cell,
pbc=(1, 1, 0)
)
if vacuum:
heterostructure.center(vacuum, axis=2)
self.ASE_atoms = heterostructure
self.atoms=self._to_GAM_Atoms(self.ASE_atoms)
self.meta = {
'materials': materials,
'structure_type': self.structure_type,
'layer_spacing': layer_spacing,
'size': size,
'vacuum': vacuum
}
#return self.atoms
[docs]
def list_nanosheet_materials(self) -> None:
"""Display all available materials and their properties."""
print("Available 2D Materials (using ASE graphene function):")
print("=" * 60)
for name, props in self.nanosheet_materials.items():
print(f"{name.replace('_', ' ').title()}:")
print(f" Formula: {props['formula']}")
print(f" Lattice constant: {props['lattice_constant']:.3f} Ã…")
print(f" Thickness/Buckling: {props['thickness']:.3f} Ã…")
print(f" Description: {props['description']}")
print()
[docs]
def translate(self, dx: float, dy: float, dz: float = 0.0):
"""Translate all atoms by (dx, dy, dz)."""
for atom in self.atoms:
atom.x += dx
atom.y += dy
atom.z += dz
# Update ASE atoms positions as well
for i, atom in enumerate(self.ASE_atoms):
atom.position += np.array([dx, dy, dz])
# Update origin in metadata
ox, oy = self.origin
self.origin = (ox + dx, oy + dy)
self.meta["origin"] = self.origin
#return self.atoms
[docs]
def rotate(self, angle_deg: float, about_center: bool = True):
"""Rotate structure about z-axis."""
angle_rad = np.radians(angle_deg)
cos_a, sin_a = np.cos(angle_rad), np.sin(angle_rad)
# Get center of mass
positions = np.array([(atom.x, atom.y, atom.z) for atom in self.atoms])
center = np.mean(positions, axis=0)
if about_center:
# Rotate about center of mass
for atom in self.atoms:
x_rel = atom.x - center[0]
y_rel = atom.y - center[1]
atom.x = center[0] + x_rel * cos_a - y_rel * sin_a
atom.y = center[1] + x_rel * sin_a + y_rel * cos_a
else:
# Rotate about (0, 0)
for atom in self.atoms:
x_old, y_old = atom.x, atom.y
atom.x = x_old * cos_a - y_old * sin_a
atom.y = x_old * sin_a + y_old * cos_a
# Update ASE atoms positions as well
for i, atom in enumerate(self.ASE_atoms):
atom.position[0] = self.atoms[i].x
atom.position[1] = self.atoms[i].y
atom.position[2] = self.atoms[i].z
# Update rotation in metadata
self.rotation = (self.rotation + angle_deg) % 360
self.meta["rotation"] = self.rotation
#return self.atoms
[docs]
def copy(self):
"""Create a deep copy of the structure."""
return copy.deepcopy(self.atoms)
[docs]
def get_atoms(self):
"""
Return a copy of the atoms in this molecule.
Returns
-------
List[str]
A list of atom symbols.
"""
return copy.deepcopy(self.atoms)
[docs]
def get_positions(self) -> List[Tuple[float, float, float]]:
"""Return positions of all atoms as a list of (x, y, z) tuples."""
return [atom.get_position() for atom in self.atoms]
[docs]
def get_elements(self) -> List[str]:
"""Return list of element symbols in the molecule."""
return [atom.element for atom in self.atoms]
def to_xyz(self, filename: str) -> None:
"""
Save the structure to a file.
Parameters
----------
atoms : Atoms
ASE Atoms object to save
filename : str
Output filename
format : str
Output format 'xyz'
"""
write(filename, self.ASE_atoms, format='xyz')
[docs]
def to_xyz(self, path: str) -> None:
"""Build (if needed) and save structure directly to XYZ file."""
# Build only when saving
if not self.meta.get('built', False):
self.build()
# Prepare XYZ string directly
lines = [str(len(self.atoms))]
'''
comment_parts = []
for key, value in self.meta.items():
comment_parts.append(f"{key}={value}")
lines.append("Silicene structure: " + ", ".join(comment_parts))
'''
lines.append('Generated by PyGamlab')
for atom in self.atoms:
lines.append(f"{atom.element:2s} {atom.x:12.6f} {atom.y:12.6f} {atom.z:12.6f}")
# Save directly
with open(path, 'w') as f:
f.write("\n".join(lines))
[docs]
def to_ase(self) -> Optional['ase.Atoms']:
"""
Export structure to ASE Atoms object (if ASE is available).
Returns:
ASE Atoms object or None if ASE not available
"""
if not HAS_ASE:
print("ASE not available. Install with: pip install ase")
return None
if not self.atoms:
return Atoms()
symbols = [atom.element for atom in self.atoms]
positions = [atom.position for atom in self.atoms]
atoms_obj = Atoms(symbols=symbols, positions=positions)
# Add metadata
atoms_obj.info.update(self.meta)
#or
#Just
#return self.ASE_atoms
return atoms_obj
def _to_GAM_Atoms(self,atoms):
return [
GAM_Atom(
id=atom.index,
element=atom.symbol,
x=atom.position[0],
y=atom.position[1],
z=atom.position[2]
)
for atom in atoms
]