Loading...
Searching...
No Matches
IRACompare.cpp
Go to the documentation of this file.
1/*
2 * This file is part of eOn.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 * Copyright (c) 2010--present, eOn Development Team
7 * All rights reserved.
8 *
9 * Repo:
10 * https://github.com/TheochemUI/eOn
11 */
12#include "eon/IRACompare.h"
13#include "eon/Eigen.h"
14#include <cstdlib>
15
16#ifdef WITH_IRA
17extern "C" {
18#include "iralib_interf.h"
19}
20#include "eon/libs/IRA/IRAResource.h" // Include IRA resource after IRA interfaces are defined
21#endif
22
23namespace eonc {
24
26 double distThreshold) {
27 MatchResult result;
28#ifdef WITH_IRA
29 auto &res = get_ira_resource();
30
31 // Lock the library for the duration of this specific comparison
32 std::lock_guard<std::mutex> lock(res.library_mutex);
33
34 try {
35 res.require_loaded();
36 } catch (const std::exception &e) {
37 result.error = -1;
38 return result;
39 }
40
41 const int nat1 = m1.numberOfAtoms();
42 const int nat2 = m2.numberOfAtoms();
43
44 // Prepare type arrays
45 std::vector<int> typ1(nat1), typ2(nat2);
46 auto nrs1 = m1.getAtomicNrs();
47 auto nrs2 = m2.getAtomicNrs();
48 for (int i = 0; i < nat1; i++)
49 typ1[i] = nrs1[i];
50 for (int i = 0; i < nat2; i++)
51 typ2[i] = nrs2[i];
52
53 // Prepare coordinates using the new Eigen helper - use direct data access
54 const AtomMatrix &pos1 = m1.getPositions();
55 const AtomMatrix &pos2 = m2.getPositions();
56
57 // Use Eigen::Map to reinterpret the row-major data as column-major for
58 // Fortran
59 Eigen::Map<const AtomMatrixF> coords1_map(pos1.data(), 3, nat1);
60 Eigen::Map<const AtomMatrixF> coords2_map(pos2.data(), 3, nat2);
61
62 // Candidate arrays: -1 means "use geometric center as origin" (good
63 // initial guess for translation with equal-size structures)
64 std::vector<int> cand1(nat1, 0), cand2(nat2, 0);
65 if (nat1 == nat2) {
66 cand1[0] = -1;
67 cand2[0] = -1;
68 } else {
69 cand1[0] = 1;
70 for (int i = 0; i < nat2; i++)
71 cand2[i] = i + 1;
72 }
73
74 // Pre-allocate output buffers (libira_match fills these)
75 std::vector<double> rmat_buf(9);
76 std::vector<double> tr_buf(3);
77 std::vector<int> perm_buf(nat2);
78 double hd = 0.0;
79 int ierr = 0;
80
81 // libira_match expects double** for rotation and translation
82 // (it may reallocate internally)
83 double *rmat_ptr = rmat_buf.data();
84 double *tr_ptr = tr_buf.data();
85 int *perm_ptr = perm_buf.data();
86
87 res.get_match_fn()(nat1, typ1.data(), coords1_map.data(), cand1.data(), nat2,
88 typ2.data(), coords2_map.data(), cand2.data(),
89 distThreshold, &rmat_ptr, &tr_ptr, &perm_ptr, &hd, &ierr);
90
91 result.error = ierr;
92 if (ierr == 0) {
93 result.permutation.assign(perm_ptr, perm_ptr + nat2);
94 result.hausdorffDistance = hd;
95 // rmat is 3x3 flat array (column-major from Fortran)
96 // Use Eigen maps for efficient conversion
97 Eigen::Map<const Matrix3d> rot_map(rmat_ptr);
98 result.rotation =
99 rot_map.transpose(); // Transpose to get proper row-major layout
100 result.translation = Eigen::Map<const Vector3d>(tr_ptr);
101 }
102
103 // If libira reallocated (pointers changed), free the new buffers.
104 // libira allocates replacement output buffers via c_malloc on the Fortran
105 // side (see libira's lib_match wrapper), so std::free is the matching
106 // deallocator. Never free the original stack/vector-backed buffers.
107 if (rmat_ptr != rmat_buf.data())
108 std::free(rmat_ptr);
109 if (tr_ptr != tr_buf.data())
110 std::free(tr_ptr);
111 if (perm_ptr != perm_buf.data())
112 std::free(perm_ptr);
113#else
114 result.error = -1;
115#endif
116 return result;
117}
118
120 double distThreshold) {
121 MatchResult result;
122#ifdef WITH_IRA
123 auto &res = get_ira_resource();
124
125 // Lock the library for the duration of this specific comparison
126 std::lock_guard<std::mutex> lock(res.library_mutex);
127
128 try {
129 res.require_loaded();
130 } catch (const std::exception &e) {
131 result.error = -1;
132 return result;
133 }
134
135 const int nat1 = m1.numberOfAtoms();
136 const int nat2 = m2.numberOfAtoms();
137
138 std::vector<int> typ1(nat1), typ2(nat2);
139 auto nrs1 = m1.getAtomicNrs();
140 auto nrs2 = m2.getAtomicNrs();
141 for (int i = 0; i < nat1; i++)
142 typ1[i] = nrs1[i];
143 for (int i = 0; i < nat2; i++)
144 typ2[i] = nrs2[i];
145
146 // Prepare coordinates using direct Eigen data access
147 const AtomMatrix &pos1 = m1.getPositions();
148 const AtomMatrix &pos2 = m2.getPositions();
149
150 Eigen::Map<const AtomMatrixF> coords1_map(pos1.data(), 3, nat1);
151 Eigen::Map<const AtomMatrixF> coords2_map(pos2.data(), 3, nat2);
152
153 // Lattice vectors (3x3 column-major)
154 Matrix3d cell = m2.getCell();
155 double lat[9];
156 for (int i = 0; i < 3; i++)
157 for (int j = 0; j < 3; j++)
158 lat[j * 3 + i] = cell(i, j);
159
160 // Caller must pre-allocate output arrays; Fortran receives pointers only
161 std::vector<int> found_buf(nat1);
162 std::vector<double> dists_buf(nat1);
163 int *found_ptr = found_buf.data();
164 double *dists_ptr = dists_buf.data();
165
166 // NOTE: this calls cshda_pbc only (assignment, no rotation/SVD matching).
167 // It finds the best atom assignment under PBC but does not compute the
168 // optimal rotation or translation.
169 res.get_cshda_pbc_fn()(nat1, typ1.data(), coords1_map.data(), nat2,
170 typ2.data(), coords2_map.data(), lat, distThreshold,
171 &found_ptr, &dists_ptr);
172
173 result.permutation.assign(found_ptr, found_ptr + nat1);
174 result.hausdorffDistance = 0.0;
175 for (int i = 0; i < nat1; i++) {
176 result.hausdorffDistance = std::max(result.hausdorffDistance, dists_ptr[i]);
177 }
178 result.rotation = Eigen::Matrix3d::Identity();
179 result.translation = Eigen::Vector3d::Zero();
180 result.error = 0;
181#else
182 result.error = -1;
183#endif
184 return result;
185}
186
188IRACompare::findSymmetry(const Matter &m, double threshold, bool prescreenIh) {
189 SymmetryResult result;
190#ifdef WITH_IRA
191 auto &res = get_ira_resource();
192
193 // Lock the library for the duration of this specific symmetry analysis
194 std::lock_guard<std::mutex> lock(res.library_mutex);
195
196 try {
197 res.require_loaded();
198 } catch (const std::exception &e) {
199 result.error = -1;
200 return result;
201 }
202
203 const int nat = m.numberOfAtoms();
204 std::vector<int> typ(nat);
205 auto nrs = m.getAtomicNrs();
206 for (int i = 0; i < nat; i++)
207 typ[i] = nrs[i];
208
209 // Prepare coordinates using direct Eigen data access
210 const AtomMatrix &pos = m.getPositions();
211 Eigen::Map<const AtomMatrixF> coords_map(pos.data(), 3, nat);
212
213 // libira_compute_all requires pre-allocated output arrays of size nmax
214 int nmax = res.get_get_nmax_fn()();
215
216 int n_mat = 0;
217 std::vector<double> mat_buf(9 * nmax);
218 std::vector<int> perm_buf(nat * nmax);
219 std::vector<char> op_buf(nmax + 1);
220 std::vector<int> n_buf(nmax);
221 std::vector<int> p_buf(nmax);
222 std::vector<double> ax_buf(3 * nmax);
223 std::vector<double> angle_buf(nmax);
224 std::vector<double> dH_buf(nmax);
225 std::vector<char> pg_buf(11);
226 int n_prin_ax = 0;
227 std::vector<double> prin_ax_buf(3 * nmax);
228 int cerr = 0;
229
230 double *mat_data = mat_buf.data();
231 int *perm_data = perm_buf.data();
232 char *op_data = op_buf.data();
233 int *n_data = n_buf.data();
234 int *p_data = p_buf.data();
235 double *ax_data = ax_buf.data();
236 double *angle_data = angle_buf.data();
237 double *dH_data = dH_buf.data();
238 char *pg = pg_buf.data();
239 double *prin_ax = prin_ax_buf.data();
240
241 res.get_compute_all_fn()(nat, typ.data(), coords_map.data(), threshold,
242 prescreenIh ? 1 : 0, &n_mat, &mat_data, &perm_data,
243 &op_data, &n_data, &p_data, &ax_data, &angle_data,
244 &dH_data, &pg, &n_prin_ax, &prin_ax, &cerr);
245
246 result.error = cerr;
247 if (cerr == 0) {
248 result.nOperations = n_mat;
249 if (pg) {
250 result.pointGroup = std::string(pg);
251 }
252
253 result.operations.resize(n_mat);
254 for (int k = 0; k < n_mat; k++) {
255 Eigen::Map<const Matrix3d> op_map(&mat_data[k * 9]);
256 result.operations[k] =
257 op_map.transpose(); // Transpose to get proper row-major layout
258 }
259
260 if (angle_data) {
261 result.angles.assign(angle_data, angle_data + n_mat);
262 }
263 if (ax_data) {
264 result.axes.resize(n_mat);
265 for (int k = 0; k < n_mat; k++) {
266 result.axes[k] = Eigen::Map<const Vector3d>(&ax_data[k * 3]);
267 }
268 }
269 }
270
271 // Only free if Fortran reallocated (pointer changed from our pre-allocated
272 // buffer). If pointer is unchanged, the vector destructor handles cleanup.
273 // Reallocated buffers come from libira's c_malloc side, so std::free is the
274 // matching deallocator; never free the vector-backed originals.
275 if (mat_data != mat_buf.data())
276 std::free(mat_data);
277 if (perm_data != perm_buf.data())
278 std::free(perm_data);
279 if (op_data != op_buf.data())
280 std::free(op_data);
281 if (n_data != n_buf.data())
282 std::free(n_data);
283 if (p_data != p_buf.data())
284 std::free(p_data);
285 if (ax_data != ax_buf.data())
286 std::free(ax_data);
287 if (angle_data != angle_buf.data())
288 std::free(angle_data);
289 if (dH_data != dH_buf.data())
290 std::free(dH_data);
291 if (pg != pg_buf.data())
292 std::free(pg);
293 if (prin_ax != prin_ax_buf.data())
294 std::free(prin_ax);
295#else
296 result.error = -1;
297#endif
298 return result;
299}
300
301} // namespace eonc
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
static MatchResult matchPBC(const Matter &m1, const Matter &m2, double distThreshold)
Atom assignment under periodic boundary conditions (CShDA only, no rotation/SVD).
static SymmetryResult findSymmetry(const Matter &m, double threshold, bool prescreenIh=true)
Find all symmetry operations of a structure (SOFI algorithm).
static MatchResult match(const Matter &m1, const Matter &m2, double distThreshold)
Match two structures using CShDA + SVD (optimal rotation + assignment).
VectorXi getAtomicNrs() const
Definition Matter.cpp:569
long int numberOfAtoms() const
Definition Matter.cpp:209
Matrix3d getCell() const
Definition Matter.cpp:211
const AtomMatrix & getPositions() const
Definition Matter.cpp:236
RAII resource manager for the ARTn C library with global synchronization.
IRAResource & get_ira_resource()
Global access to thread-safe IRA resource.
Eigen::Vector3d translation
Definition IRACompare.h:28
std::vector< int > permutation
Definition IRACompare.h:26
std::vector< double > angles
Definition IRACompare.h:38
std::vector< Eigen::Matrix3d > operations
Definition IRACompare.h:36
std::vector< Eigen::Vector3d > axes
Definition IRACompare.h:39