---
# System prepended metadata

title: '[ABCD\_J: Autonomous Basin Climbing Dynamics in Julia](https://github.com/ch-tung/ABCD_J.git)'
tags: [Github Readme, ABC]

---

# [ABCD\_J: Autonomous Basin Climbing Dynamics in Julia](https://github.com/ch-tung/ABCD_J.git)

This project incorporates Julia into a new metadynamics molecular simulation program. The simulation framework is developed using the Julia packages [Molly.jl](https://github.com/JuliaMolSim/Molly.jl), [AtomsBase.jl](https://github.com/JuliaMolSim/AtomsBase.jl), and [AtomsCalculators.jl](https://github.com/JuliaMolSim/AtomsCalculators.jl). The Julia LAMMPS wrapper [LAMMPS.jl](https://github.com/cesmix-mit/LAMMPS.jl) is also utilized to facilitate early-stage development.

## `src/`: Julia sourcecodes

### `juliaEAM.jl`: Julia based EAM calculator.

* `read_potential!` Reads the potential data from a file and populate the fields of the `calculator` object.
* `calculate_energy`, `calculate_energy_LAMMPS` Calculates the total energy of a system using the Embedded Atom Method (EAM).
* `calculate_forces`, `calculate_forces_LAMMPS` Calculate the forces on particles using the Embedded Atom Method (EAM).
* `calculate_atomstress` Calculate the atomic level stresses around the particles.

### `lammpsIO.jl`: Read/Write LAMMPS dump file

* `lmpDumpReader` Reads a LAMMPS dump file and extracts the number of atoms, box size, and atomic coordinates.
* `lmpDumpWriter` Write system information to a LAMMPS dump file.
* `lmpDataWriter` Write LAMMPS data file.
* `lmpDumpWriter_prop` Write atomic data to a LAMMPS dump file with additional property.
* `lmpDataWriter_prop` Write atomic data to a LAMMPS data file with additional property.
* `lmpFinalWriter` Write outputs for NEB calculations.

### `simulator.jl`: Define the ABC simulator

* `ABCSimulator` Defines the ABCSimulator class.
* `simulate!` Updates the system according to ABC algorithm.

### `minimize.jl`: Define the minimization algorithms

**Example:**
```julia
## load the necessary packages
cd(@__DIR__)
ENV["CELLLISTMAP_8.3_WARNING"] = "false"
include("src/juliaEAM.jl")
include("src/lammpsIO.jl")

using Pkg
Pkg.activate(".")

using Printf
using AtomsCalculators
using Unitful: Å, nm
using StaticArrays: SVector
using Molly
using LinearAlgebra
using DelimitedFiles
using UnitfulAtomic
import PeriodicTable
using ProgressMeter
using Random

## Define Copper
Cu_LatConst = 3.615/10 # nm
atom_mass = 63.546u"u"  # Atomic mass of aluminum in grams per mole

eam = EAM() # from juliaEAM.jl
fname = "Cu_Zhou04.eam.alloy"
read_potential!(eam, fname)

## Define customized interaction type in `AtomsCalculators`
struct EAMInteractionJulia
    calculator::Any  # Holds the ASE EAM calculator reference
    f_energy::Any    # Holds the energy function
    f_forces::Any    # Holds the forces function
    f_atomstress::Any  # Holds the atomic level stresses function
    pair_coeff_string::String  # Holds the pair coefficient string
end

## Initialize system
function initialize_system_dump(;loggers=(coords=CoordinateLogger(1),),filename_dump="")
    n_atoms, box_size, coords_molly, attypes = lmpDumpReader(filename_dump)
    molly_atoms = [Molly.Atom(index=i, charge=0, mass=atom_mass, 
                    #   σ=2.0u"Å" |> x -> uconvert(u"nm", x), ϵ=ϵ_kJ_per_mol
                    ) for i in 1:length(coords_molly)]
    # Specify boundary condition
    boundary_condition = Molly.CubicBoundary(box_size[1],box_size[2],box_size[3])

    atom_positions_init = copy(coords_molly)
    molly_atoms_init = copy(molly_atoms)

    DNF = DistanceNeighborFinder(
        eligible=trues(length(molly_atoms_init), length(molly_atoms_init)),
        n_steps=1e3,
        dist_cutoff=7u"Å")
    TNF = TreeNeighborFinder(
        eligible=trues(length(molly_atoms_init), length(molly_atoms_init)), 
        n_steps=1e3,
        dist_cutoff=7u"Å")

    # Initialize the system with the initial positions and velocities
    system_init = Molly.System(
    atoms=molly_atoms_init,
    atoms_data = [AtomData(element="Cu", atom_type=string(attypes[ia])) for (ia,a) in enumerate(molly_atoms_init)],
    coords=atom_positions_init,  # Ensure these are SVector with correct units
    boundary=boundary_condition,
    # loggers=Dict(:kinetic_eng => Molly.KineticEnergyLogger(100), :pot_eng => Molly.PotentialEnergyLogger(100)),
    neighbor_finder = DNF,
    loggers=loggers,
    energy_units=u"eV",  # Ensure these units are correctly specified
    force_units=u"eV/Å"  # Ensure these units are correctly specified
    )
    return system_init
end

filename_dump = "./out_surface.dump"
molly_system = initialize_system_dump(filename_dump = filename_dump)
neighbors_all = get_neighbors_all(molly_system)

## Define the interaction and load simulator module
eamJulia = EAMInteractionJulia(eam,calculate_energy,calculate_forces_LAMMPS,calculate_atomstress,"pair_coeff * * Cu_Zhou04.eam.alloy Cu")
include("src/simulator.jl")

## Define a Molly-style forces function
function Molly.forces(sys::System, interaction::EAMInteractionJulia, penalty_coords, sigma::typeof(1.0u"Å"), W::typeof(1.0u"eV"), neighbors_all::Vector{Vector{Int}};
    n_threads::Integer=Threads.nthreads(), nopenalty_atoms=[]) 
    
    fs = interaction.f_forces(sys, interaction.pair_coeff_string)

    # Add penalty term to forces
    if penalty_coords != nothing
        fs += penalty_forces(sys, penalty_coords, sigma, W, nopenalty_atoms=nopenalty_atoms) # ev/Å
        # print(maximum(norm.(penalty_forces(sys, penalty_coords, sigma, W))),"\n")
    end
    return fs
end

## Run energy minimization
z_coords = [coords[3] for coords in molly_system.coords]
frozen_atoms = [index for (index, z) in enumerate(z_coords) if z ≤ 1u"Å"]

simulator = ABCSimulator(sigma=1.0*u"Å", W=1.0*u"eV", max_steps=1, max_steps_minimize=20, step_size_minimize=5e-3u"ps", tol=1e-6u"eV/Å")
Minimize_FIRE!(molly_system, simulator, eamJulia, nothing, neighbors_all;
         n_threads=1, frozen_atoms=frozen_atoms, neig_interval=5, print_nsteps=true,
         mass=atom_mass)
neighbors_all = get_neighbors_all(molly_system)

## run ABC
# frozen_atoms = []
z_coords = [coords[3] for coords in molly_system.coords]
frozen_atoms = [index for (index, z) in enumerate(z_coords) if z ≤ 1u"Å"]

nopenalty_atoms = []
N_free = length(molly_system.coords)-length(nopenalty_atoms)
print("exclude ",length(nopenalty_atoms)," atoms from E_phi calculation\n")
print("using ",N_free," atoms for E_phi calculation\n")

# sigma = sqrt(0.006*3*N_free)
sigma = sqrt(0.18)
W = 0.1
@printf("sigma^2 = %e, %e Å/dof^1/2\n W = %e eV\n",ustrip(sigma^2), ustrip(sigma/sqrt(3*N_free)),ustrip(W))

simulator = ABCSimulator(sigma=sigma*u" Å", W=W*u"eV", 
                         max_steps=100, max_steps_minimize=30, step_size_minimize=5e-3u"ps", tol=1e-3u"eV/Å")

simulate!(molly_system, simulator, eamJulia, n_threads=1, 
         #   fname="output_stress_cube8.txt", fname_dump="stress_cube8.dump", fname_min_dump="min_stress_cube8.dump",
         fname="test.txt", fname_dump="test.dump", fname_min_dump="test.dump", # for speed test
         neig_interval=30, loggers_interval=10, dump_interval=10, start_dump=0,
         minimize_only=false, 
         d_boost=1e-6u"Å", 
         frozen_atoms=frozen_atoms, nopenalty_atoms=nopenalty_atoms, 
         p_drop = 1-1/32, p_keep=0, n_memory=0, n_search=100,
         p_stress = 1-192/7180, n_stress=12)
```

## Benchmarks on energy and forces calculations
```bash
Calculating potential energy using ASE EAM calculator
  0.095757 seconds (1.43 M allocations: 66.707 MiB, 40.00% gc time)
Calculating potential energy using my EAM calculator
  0.018727 seconds (28.82 k allocations: 13.442 MiB)
ASE EAM calculator: -3.187108e+04 eV
My EAM calculator: -3.187108e+04 eV
Difference: 1.091394e-11 eV
time/atom/step by ASE EAM calculator: 5.454806508701079e-6 seconds
time/atom/step by my EAM calculator: 1.814375071708839e-6 seconds

Calculating forces using ASE EAM calculator
  0.056560 seconds (1.43 M allocations: 67.147 MiB)
Calculating forces using my EAM calculator
  0.047702 seconds (86.48 k allocations: 45.297 MiB, 26.39% gc time)
Sum of forces by ASE EAM calculator: [-2.368150e-12 -2.400567e-12 2.278473e-14] eV/Å
Sum of forces by my EAM calculator: [6.916488e-14 -9.799304e-15 2.103179e-14] eV/Å
Max force error: 1.862710e-06 eV/Å
time/atom/step by ASE EAM calculator: 5.650160250819211e-6 seconds
time/atom/step by my EAM calculator: 4.7440777693101735e-6 seconds
```

Tested on an AMD EPYC 9334 2.7 GHz CPU on `analysis.sns.gov` cluster. For reference, the serial LAMMPS EAM calculator takes 1.85e-6 CPU seconds per atom per step for energy calculation on 3.47 GHz Intel Xeon processors. ([reference](frozen_atoms))