Magnetic Structures#

Introduction#

AiiDA-atomistic provides comprehensive support for magnetic properties in crystal structures. This enables modeling of magnetic materials with proper magnetic moment assignments and collinear/non-collinear magnetism support.

Basic Magnetic Properties#

Adding Magnetic Moments#

import numpy as np
from aiida_atomistic.data.structure import StructureDataMutable

# Start with a basic iron structure
from ase.build import bulk
fe_atoms = bulk('Fe', 'bcc', a=2.87)

# Create mutable structure
structure = StructureDataMutable.from_ase(fe_atoms)

# Add magnetic moments
for site in structure.sites:
    site.moment = np.array([0.0, 0.0, 2.2])  # 2.2 µB along z-axis

print(f"Added magnetic moments to {len(structure.sites)} sites")

Collinear Magnetism#

For simple collinear magnetic structures:

# Create antiferromagnetic NiO structure
from ase.build import rocksalt
nio_atoms = rocksalt(['Ni', 'O'], a=4.18, factor=2)

structure = StructureDataMutable.from_ase(nio_atoms)

# Assign magnetic moments
for i, site in enumerate(structure.sites):
    if site.symbol == 'Ni':
        # Alternate spin directions for antiferromagnetism
        moment_value = 1.64 if i % 2 == 0 else -1.64
        site.moment = np.array([0.0, 0.0, moment_value])
    else:
        site.moment = np.array([0.0, 0.0, 0.0])  # Oxygen is non-magnetic

print(f"Created antiferromagnetic NiO structure")
print(f"Magnetic sites: {sum(1 for s in structure.sites if np.linalg.norm(s.moment) > 0)}")

Non-Collinear Magnetism#

For complex magnetic structures with arbitrary moment directions:

# Create a frustrated magnetic system
from ase.build import bulk
mn_atoms = bulk('Mn', 'fcc', a=3.89, cubic=True)
mn_atoms *= (2, 2, 1)  # 2x2x1 supercell

structure = StructureDataMutable.from_ase(mn_atoms)

# Define non-collinear magnetic moments (120° configuration)
import math
angle = 2 * math.pi / 3  # 120 degrees

for i, site in enumerate(structure.sites):
    theta = i * angle
    moment_magnitude = 2.0
    site.moment = np.array([
        moment_magnitude * math.cos(theta),
        moment_magnitude * math.sin(theta),
        0.0
    ])

print(f"Created non-collinear magnetic structure")
print(f"Example moment directions:")
for i, site in enumerate(structure.sites[:3]):
    print(f"  Site {i}: {site.moment}")

Working with Magnetic Kinds#

The kinds system intelligently groups atoms with identical magnetic properties:

# Create structure with magnetic kinds
structure = StructureDataMutable.from_ase(nio_atoms, detect_kinds=True)

# Add magnetic moments
for site in structure.sites:
    if site.symbol == 'Ni':
        # All Ni atoms get the same moment initially
        site.moment = np.array([0.0, 0.0, 1.64])

# Convert to immutable to trigger kinds detection
final_structure = StructureData.from_mutable(structure)

print(f"Magnetic kinds detected:")
for kind in final_structure.properties.kinds:
    if hasattr(kind, 'moment') and np.linalg.norm(kind.moment) > 0:
        print(f"  {kind.kind_name}: {kind.symbol} with moment {kind.moment}")

Different Magnetic Environments#

# Create complex magnetic structure with different environments
structure = StructureDataMutable()

# Define unit cell
structure.cell = np.array([
    [5.0, 0.0, 0.0],
    [0.0, 5.0, 0.0],
    [0.0, 0.0, 10.0]
])
structure.pbc = [True, True, True]

# Add sites with different magnetic moments
sites_data = [
    {"symbol": "Fe", "position": [0.0, 0.0, 0.0], "moment": [0.0, 0.0, 2.3]},
    {"symbol": "Fe", "position": [2.5, 2.5, 0.0], "moment": [0.0, 0.0, -2.3]},
    {"symbol": "Fe", "position": [0.0, 2.5, 5.0], "moment": [2.0, 0.0, 0.0]},
    {"symbol": "Fe", "position": [2.5, 0.0, 5.0], "moment": [-2.0, 0.0, 0.0]},
]

for site_data in sites_data:
    structure.append_atom(
        symbol=site_data["symbol"],
        position=site_data["position"],
        moment=site_data["moment"]
    )

# Convert and check kinds
final_structure = StructureData.from_mutable(structure)
print(f"Created {len(final_structure.properties.kinds)} magnetic kinds")

Magnetic Property Analysis#

Computing Magnetic Properties#

# Analyze magnetic structure
def analyze_magnetism(structure):
    """Analyze magnetic properties of a structure."""
    magnetic_sites = [s for s in structure.sites
                     if hasattr(s, 'moment') and np.linalg.norm(s.moment) > 0]

    if not magnetic_sites:
        print("No magnetic sites found")
        return

    # Total magnetic moment
    total_moment = sum(s.moment for s in magnetic_sites)
    total_magnitude = np.linalg.norm(total_moment)

    print(f"Magnetic analysis:")
    print(f"  Magnetic sites: {len(magnetic_sites)}/{len(structure.sites)}")
    print(f"  Total moment: {total_moment}")
    print(f"  Total magnitude: {total_magnitude:.3f} µB")

    # Average moment magnitude
    moments = [np.linalg.norm(s.moment) for s in magnetic_sites]
    avg_moment = np.mean(moments)
    print(f"  Average moment magnitude: {avg_moment:.3f} µB")

    return {
        'total_moment': total_moment,
        'magnetic_sites': len(magnetic_sites),
        'average_magnitude': avg_moment
    }

# Analyze our structures
analyze_magnetism(final_structure)

Magnetic Moment Directions#

def magnetic_analysis_detailed(structure):
    """Detailed magnetic moment analysis."""
    magnetic_sites = [s for s in structure.sites
                     if hasattr(s, 'moment') and np.linalg.norm(s.moment) > 0]

    print("Detailed magnetic moment analysis:")

    # Group by moment direction (collinear check)
    moment_directions = {}
    for site in magnetic_sites:
        moment = site.moment
        direction = moment / np.linalg.norm(moment)

        # Round to avoid floating point issues
        direction_key = tuple(np.round(direction, 3))

        if direction_key not in moment_directions:
            moment_directions[direction_key] = []
        moment_directions[direction_key].append(site)

    print(f"  Found {len(moment_directions)} unique moment directions:")
    for direction, sites in moment_directions.items():
        print(f"    Direction {direction}: {len(sites)} sites")
        moments = [np.linalg.norm(s.moment) for s in sites]
        print(f"      Moment magnitudes: {moments}")

magnetic_analysis_detailed(final_structure)

Advanced Magnetic Features#

Spin-Orbit Coupling Effects#

# Structure with spin-orbit coupling considerations
structure = StructureDataMutable()

# Add sites with complex magnetic anisotropy
sites_with_anisotropy = [
    {
        "symbol": "Co",
        "position": [0.0, 0.0, 0.0],
        "moment": [0.1, 0.0, 1.6],  # Small xy component due to SOC
        "kind_name": "Co_anisotropic"
    }
]

for site_data in sites_with_anisotropy:
    structure.append_atom(**site_data)

print("Added sites with magnetic anisotropy effects")

Magnetic File I/O#

# Save magnetic structure
magnetic_structure = StructureData.from_mutable(structure)
magnetic_structure.store()

# The magnetic information is preserved in the database
loaded = orm.load_node(magnetic_structure.pk)

print("Magnetic information preserved:")
for site in loaded.sites:
    if hasattr(site, 'moment') and np.linalg.norm(site.moment) > 0:
        print(f"  {site.symbol}: {site.moment}")

Integration with Calculations#

Preparing for Quantum ESPRESSO#

# Structure optimized for QE calculations
def prepare_for_qe(structure, magnetic_setup='collinear'):
    """Prepare magnetic structure for Quantum ESPRESSO."""

    # Get starting magnetization for each kind
    starting_magnetization = {}

    for kind in structure.properties.kinds:
        if hasattr(kind, 'moment') and np.linalg.norm(kind.moment) > 0:
            if magnetic_setup == 'collinear':
                # Use z-component for collinear
                mag_value = kind.moment[2]
            else:
                # Use magnitude for non-collinear
                mag_value = np.linalg.norm(kind.moment)

            starting_magnetization[kind.kind_name] = mag_value

    print(f"QE starting magnetization:")
    for kind, mag in starting_magnetization.items():
        print(f"  {kind}: {mag:.3f}")

    return starting_magnetization

# Example usage
qe_mag_params = prepare_for_qe(final_structure, 'collinear')

Summary#

Collinear & Non-collinear: Support for both simple and complex magnetism ✅ Kinds Integration: Automatic grouping of magnetically equivalent sites ✅ Property Analysis: Tools for analyzing magnetic configurations ✅ Database Storage: Magnetic information preserved in AiiDA database ✅ Calculation Ready: Easy integration with DFT codes

Next Steps#

Learn how to run calculations with your magnetic structures!