From 880fb64331224204d88c035b317edf3533b3de70 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:32:18 +0200 Subject: [PATCH 1/9] utility functions --- src/LAMMPS.jl | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index a9620d5..b25397e 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -375,5 +375,52 @@ function gather_atoms(lmp::LMP, name, T, count) check(lmp) return data end +function _get_count(lmp::LMP, name::String) + # values taken from: https://docs.lammps.org/Classes_atom.html#_CPPv4N9LAMMPS_NS4Atom7extractEPKc + + if startswith(name, r"[f,c]_") + if name[1] == 'c' + API.lammps_has_id(lmp, "compute", name[3:end]) != 1 && error("Unknown per atom compute $name") + + count_ptr = API.lammps_extract_compute(lmp::LMP, name[3:end], API.LMP_STYLE_ATOM, API.LMP_SIZE_COLS) + else + API.lammps_has_id(lmp, "fix", name[3:end]) != 1 && error("Unknown per atom fix $name") + + count_ptr = API.lammps_extract_fix(lmp::LMP, name[3:end], API.LMP_STYLE_ATOM, API.LMP_SIZE_COLS, 0, 0) + end + check(lmp) + + count_ptr = reinterpret(Ptr{Cint}, count_ptr) + count = unsafe_load(count_ptr) + + return count == 0 ? 1 : count + elseif name in ("mass", "id", "type", "mask", "image", "molecule", "q", "radius", "rmass", "ellipsoid", "line", "tri", "body", "temperature", "heatflow") + return 1 + elseif name in ("x", "v", "f", "mu", "omega", "angmom", "torque") + return 3 + elseif name == "quat" + return 4 + else + error("Unknown per atom property $name") + end +end + +function _get_T(lmp::LMP, name::String) + if startswith(name, r"[f,c]_") + return missing # As far as I know, it's not possible to determine the datatype of computes or fixes at runtime + end + + type = API.lammps_extract_atom_datatype(lmp, name) + check(lmp) + + if type in (API.LAMMPS_INT, API.LAMMPS_INT_2D) + return Int32 + elseif type in (API.LAMMPS_DOUBLE, API.LAMMPS_DOUBLE_2D) + return Float64 + else + error("Unkown per atom property $name") + end + +end end # module From 57710f1b796b2f272f16ab2d520f10306689a2db Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:32:55 +0200 Subject: [PATCH 2/9] gather/scatter! functions --- src/LAMMPS.jl | 93 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index b25397e..641ffea 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -361,20 +361,95 @@ function extract_variable(lmp::LMP, name::String, group=nothing) end end -function gather_atoms(lmp::LMP, name, T, count) - if T === Int32 - dtype = 0 - elseif T === Float64 - dtype = 1 +""" + gather(lmp::LMP, name::String, T::Union{Type{Int32}, Type{Float64}}, ids::Union{Nothing, Array{Int32}}=nothing) + +Gather the named per-atom, per-atom fix, per-atom compute, or fix property/atom-based entities from all processes. +By default (when `ids=nothing`), this method collects data from all atoms in consecutive order according to their IDs. +The optional parameter `ids` determines for which subset of atoms the requested data will be gathered. The returned data will then be ordered according to `ids` + +Compute entities have the prefix `c_`, fix entities use the prefix `f_`, and per-atom entites have no prefix. + +The returned Array is decoupled from the internal state of the LAMMPS instance. + +!!! warning "Type Verification" + Due to how the underlying C-API works, it's not possible to verify the element data-type of fix or compute style data. + Supplying the wrong data-type will not throw an error but will result in nonsensical output + +!!! warning "ids" + The optional parameter `ids` only works, if there is a map defined. For example by doing: + `command(lmp, "atom_modify map yes")` + However, LAMMPS only issues a warning if that's the case, which unfortuately cannot be detected through the underlying API. + Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. +""" +function gather(lmp::LMP, name::String, T::Union{Type{Int32}, Type{Float64}}, ids::Union{Nothing, Array{Int32}}=nothing) + count = _get_count(lmp, name) + _T = _get_T(lmp, name) + + @assert ismissing(_T) || _T == T "Expected data type $_T got $T instead." + + dtype = (T === Float64) + natoms = get_natoms(lmp) + ndata = isnothing(ids) ? natoms : length(ids) + data = Matrix{T}(undef, (count, ndata)) + + if isnothing(ids) + API.lammps_gather(lmp, name, dtype, count, data) else - error("Only Int32 or Float64 allowed as T, got $T") + @assert all(1 <= id <= natoms for id in ids) + API.lammps_gather_subset(lmp, name, dtype, count, ndata, ids, data) end - natoms = get_natoms(lmp) - data = Array{T, 2}(undef, (count, natoms)) - API.lammps_gather_atoms(lmp, name, dtype, count, data) + check(lmp) return data end + +""" + scatter!(lmp::LMP, name::String, data::VecOrMat{T}, ids::Union{Nothing, Array{Int32}}=nothing) where T<:Union{Int32, Float64} + +Scatter the named per-atom, per-atom fix, per-atom compute, or fix property/atom-based entity in data to all processes. +By default (when `ids=nothing`), this method scatters data to all atoms in consecutive order according to their IDs. +The optional parameter `ids` determines to which subset of atoms the data will be scattered. + +Compute entities have the prefix `c_`, fix entities use the prefix `f_`, and per-atom entites have no prefix. + +!!! warning "Type Verification" + Due to how the underlying C-API works, it's not possible to verify the element data-type of fix or compute style data. + Supplying the wrong data-type will not throw an error but will result in nonsensical date being supplied to the LAMMPS instance. + +!!! warning "ids" + The optional parameter `ids` only works, if there is a map defined. For example by doing: + `command(lmp, "atom_modify map yes")` + However, LAMMPS only issues a warning if that's the case, which unfortuately cannot be detected through the underlying API. + Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. +""" +function scatter!(lmp::LMP, name::String, data::VecOrMat{T}, ids::Union{Nothing, Array{Int32}}=nothing) where T<:Union{Int32, Float64} + count = _get_count(lmp, name) + _T = _get_T(lmp, name) + + @assert ismissing(_T) || _T == T "Expected data type $_T got $T instead." + + dtype = (T === Float64) + natoms = get_natoms(lmp) + ndata = isnothing(ids) ? natoms : length(ids) + + if data isa Vector + @assert count == 1 + @assert ndata == lenght(data) + else + @assert count == size(data,1) + @assert ndata == size(data,2) + end + + if isnothing(ids) + API.lammps_scatter(lmp, name, dtype, count, data) + else + @assert all(1 <= id <= natoms for id in ids) + API.lammps_scatter_subset(lmp, name, dtype, count, ndata, ids, data) + end + + check(lmp) +end function _get_count(lmp::LMP, name::String) # values taken from: https://docs.lammps.org/Classes_atom.html#_CPPv4N9LAMMPS_NS4Atom7extractEPKc From 1d19468cd25714e27ce1c29881ac350bba20c170 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:33:14 +0200 Subject: [PATCH 3/9] deprecate gater_atoms --- src/LAMMPS.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index 641ffea..fb281fa 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -361,6 +361,9 @@ function extract_variable(lmp::LMP, name::String, group=nothing) end end +@deprecate gather_atoms(lmp::LMP, name, T, count) gather(lmp, name, T) + + """ gather(lmp::LMP, name::String, T::Union{Type{Int32}, Type{Float64}}, ids::Union{Nothing, Array{Int32}}=nothing) From 81563f1312454af2cd78c4f247f9f33037d62567 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:34:20 +0200 Subject: [PATCH 4/9] export gater/scatter! --- src/LAMMPS.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index fb281fa..486a37b 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -3,7 +3,7 @@ import MPI include("api.jl") export LMP, command, get_natoms, extract_atom, extract_compute, extract_global, - gather_atoms + gather, scatter! using Preferences From 354bc043d94718f5afb61a0998725b586c4c3a67 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:35:09 +0200 Subject: [PATCH 5/9] whitespace --- src/LAMMPS.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index 486a37b..94b3a10 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -453,6 +453,7 @@ function scatter!(lmp::LMP, name::String, data::VecOrMat{T}, ids::Union{Nothing, check(lmp) end + function _get_count(lmp::LMP, name::String) # values taken from: https://docs.lammps.org/Classes_atom.html#_CPPv4N9LAMMPS_NS4Atom7extractEPKc From 300347af794b4bab6feb27042fc81824b40e6a1c Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:35:52 +0200 Subject: [PATCH 6/9] add tests gather/scatter --- test/runtests.jl | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 061cc69..01d1dd9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,4 +41,64 @@ end end end +@testset "gather/scatter" begin + LMP(["-screen", "none"]) do lmp + # setting up example data + command(lmp, "atom_modify map yes") + command(lmp, "region cell block 0 3 0 3 0 3") + command(lmp, "create_box 1 cell") + command(lmp, "lattice sc 1") + command(lmp, "create_atoms 1 region cell") + command(lmp, "mass 1 1") + + command(lmp, "compute pos all property/atom x y z") + command(lmp, "fix pos all ave/atom 10 1 10 c_pos[1] c_pos[2] c_pos[3]") + + command(lmp, "run 10") + data = zeros(Float64, 3, 27) + subset = Int32.([2,5,10, 5]) + data_subset = ones(Float64, 3, 4) + + subset_bad1 = Int32.([28]) + subset_bad2 = Int32.([0]) + subset_bad_data = ones(Float64, 3,1) + + @test_throws AssertionError gather(lmp, "x", Int32) + @test_throws AssertionError gather(lmp, "id", Float64) + + @test_throws ErrorException gather(lmp, "nonesense", Float64) + @test_throws ErrorException gather(lmp, "c_nonsense", Float64) + @test_throws ErrorException gather(lmp, "f_nonesense", Float64) + + @test_throws AssertionError gather(lmp, "x", Float64, subset_bad1) + @test_throws AssertionError gather(lmp, "x", Float64, subset_bad2) + + @test_throws ErrorException scatter!(lmp, "nonesense", data) + @test_throws ErrorException scatter!(lmp, "c_nonsense", data) + @test_throws ErrorException scatter!(lmp, "f_nonesense", data) + + @test_throws AssertionError scatter!(lmp, "x", subset_bad_data, subset_bad1) + @test_throws AssertionError scatter!(lmp, "x", subset_bad_data, subset_bad2) + + @test gather(lmp, "x", Float64) == gather(lmp, "c_pos", Float64) == gather(lmp, "f_pos", Float64) + + @test gather(lmp, "x", Float64)[:,subset] == gather(lmp, "x", Float64, subset) + @test gather(lmp, "c_pos", Float64)[:,subset] == gather(lmp, "c_pos", Float64, subset) + @test gather(lmp, "f_pos", Float64)[:,subset] == gather(lmp, "f_pos", Float64, subset) + + scatter!(lmp, "x", data) + scatter!(lmp, "f_pos", data) + scatter!(lmp, "c_pos", data) + + @test gather(lmp, "x", Float64) == gather(lmp, "c_pos", Float64) == gather(lmp, "f_pos", Float64) == data + + scatter!(lmp, "x", data_subset, subset) + scatter!(lmp, "c_pos", data_subset, subset) + scatter!(lmp, "f_pos", data_subset, subset) + + @test gather(lmp, "x", Float64, subset) == gather(lmp, "c_pos", Float64, subset) == gather(lmp, "f_pos", Float64, subset) == data_subset + + end +end + @test success(pipeline(`$(MPI.mpiexec()) -n 2 $(Base.julia_cmd()) mpitest.jl`, stderr=stderr, stdout=stdout)) From f43283fdffec0ed47c4e742e3c7e102ddef5390d Mon Sep 17 00:00:00 2001 From: Joroks Date: Sat, 22 Jun 2024 21:48:04 +0200 Subject: [PATCH 7/9] explicitly disallow gathering/scattering mass --- src/LAMMPS.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index 94b3a10..47e28a8 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -386,6 +386,8 @@ The returned Array is decoupled from the internal state of the LAMMPS instance. Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. """ function gather(lmp::LMP, name::String, T::Union{Type{Int32}, Type{Float64}}, ids::Union{Nothing, Array{Int32}}=nothing) + @assert name != "mass" "scattering/gathering mass is currently not supported! Use `extract_atom()` instead." + count = _get_count(lmp, name) _T = _get_T(lmp, name) @@ -427,6 +429,8 @@ Compute entities have the prefix `c_`, fix entities use the prefix `f_`, and per Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. """ function scatter!(lmp::LMP, name::String, data::VecOrMat{T}, ids::Union{Nothing, Array{Int32}}=nothing) where T<:Union{Int32, Float64} + @assert name != "mass" "scattering/gathering mass is currently not supported! Use `extract_atom()` instead." + count = _get_count(lmp, name) _T = _get_T(lmp, name) From b4e93d58d20b2d01d9304adda6455be6e37dca6f Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 23 Jun 2024 01:37:36 +0200 Subject: [PATCH 8/9] use error instead of assertion --- src/LAMMPS.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index 47e28a8..48fbcfa 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -386,7 +386,7 @@ The returned Array is decoupled from the internal state of the LAMMPS instance. Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. """ function gather(lmp::LMP, name::String, T::Union{Type{Int32}, Type{Float64}}, ids::Union{Nothing, Array{Int32}}=nothing) - @assert name != "mass" "scattering/gathering mass is currently not supported! Use `extract_atom()` instead." + name == "mass" && error("scattering/gathering mass is currently not supported! Use `extract_atom()` instead.") count = _get_count(lmp, name) _T = _get_T(lmp, name) @@ -429,7 +429,7 @@ Compute entities have the prefix `c_`, fix entities use the prefix `f_`, and per Starting form LAMMPS version `17 Apr 2024` this should no longer be an issue, as LAMMPS then throws an error instead of a warning. """ function scatter!(lmp::LMP, name::String, data::VecOrMat{T}, ids::Union{Nothing, Array{Int32}}=nothing) where T<:Union{Int32, Float64} - @assert name != "mass" "scattering/gathering mass is currently not supported! Use `extract_atom()` instead." + name == "mass" && error("scattering/gathering mass is currently not supported! Use `extract_atom()` instead.") count = _get_count(lmp, name) _T = _get_T(lmp, name) From 05ebf59437ff721b81681378b0b7a4b61211bcda Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 23 Jun 2024 01:40:14 +0200 Subject: [PATCH 9/9] add comment explaining count logic --- src/LAMMPS.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LAMMPS.jl b/src/LAMMPS.jl index 48fbcfa..c829086 100644 --- a/src/LAMMPS.jl +++ b/src/LAMMPS.jl @@ -476,6 +476,7 @@ function _get_count(lmp::LMP, name::String) count_ptr = reinterpret(Ptr{Cint}, count_ptr) count = unsafe_load(count_ptr) + # a count of 0 indicates that the entity is a vector. In order to perserve type stability we just treat that as a 1xN Matrix. return count == 0 ? 1 : count elseif name in ("mass", "id", "type", "mask", "image", "molecule", "q", "radius", "rmass", "ellipsoid", "line", "tri", "body", "temperature", "heatflow") return 1