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"
15#include <cstdlib>
16#include <mutex>
17
18#ifdef WITH_IRA
19extern "C" {
20#include "iralib_interf.h"
21}
22#endif
23
24namespace eonc {
25
27 double distThreshold) {
28 const int nat1 = m1.numberOfAtoms();
29 const int nat2 = m2.numberOfAtoms();
30 if (nat1 <= 0 || nat2 <= 0) {
31 MatchResult result;
32 result.error = -1;
33 return result;
34 }
35 std::vector<int> typ1(static_cast<size_t>(nat1)),
36 typ2(static_cast<size_t>(nat2));
37 auto nrs1 = m1.getAtomicNrs();
38 auto nrs2 = m2.getAtomicNrs();
39 for (int i = 0; i < nat1; i++)
40 typ1[static_cast<size_t>(i)] = nrs1[i];
41 for (int i = 0; i < nat2; i++)
42 typ2[static_cast<size_t>(i)] = nrs2[i];
43 return matchArrays(nat1, typ1.data(), m1.getPositions().data(), nat2,
44 typ2.data(), m2.getPositions().data(), distThreshold);
45}
46
48 const double *pos1, int nat2,
49 const int *typ2,
50 const double *pos2,
51 double distThreshold) {
52#ifdef WITH_IRA
53 return matchArrays(nat1, typ1, pos1, nat2, typ2, pos2, distThreshold,
55#else
56 MatchResult result;
57 result.error = -1;
58 return result;
59#endif
60}
61
63IRACompare::matchArrays(int nat1, const int *typ1, const double *pos1, int nat2,
64 const int *typ2, const double *pos2,
65 double distThreshold, IIRAResource &res) {
66 MatchResult result;
67 std::lock_guard<std::mutex> lock(res.library_mutex);
68
69 try {
70 res.require_loaded();
71 } catch (const std::exception &e) {
72 result.error = -1;
73 return result;
74 }
75
76 if (nat1 <= 0 || nat2 <= 0 || typ1 == nullptr || typ2 == nullptr ||
77 pos1 == nullptr || pos2 == nullptr) {
78 result.error = -1;
79 return result;
80 }
81
82 // Use Eigen::Map to reinterpret the row-major data as column-major for
83 // Fortran
84 Eigen::Map<const AtomMatrixF> coords1_map(pos1, 3, nat1);
85 Eigen::Map<const AtomMatrixF> coords2_map(pos2, 3, nat2);
86
87 // Candidate arrays: -1 means "use geometric center as origin" (good
88 // initial guess for translation with equal-size structures)
89 std::vector<int> cand1(nat1, 0), cand2(nat2, 0);
90 if (nat1 == nat2) {
91 cand1[0] = -1;
92 cand2[0] = -1;
93 } else {
94 cand1[0] = 1;
95 for (int i = 0; i < nat2; i++)
96 cand2[i] = i + 1;
97 }
98
99 // Pre-allocate output buffers (libira_match fills these)
100 std::vector<double> rmat_buf(9);
101 std::vector<double> tr_buf(3);
102 std::vector<int> perm_buf(nat2);
103 double hd = 0.0;
104 int ierr = 0;
105
106 // libira_match expects double** for rotation and translation
107 // (it may reallocate internally)
108 double *rmat_ptr = rmat_buf.data();
109 double *tr_ptr = tr_buf.data();
110 int *perm_ptr = perm_buf.data();
111
112 res.get_match_fn()(nat1, typ1, coords1_map.data(), cand1.data(), nat2, typ2,
113 coords2_map.data(), cand2.data(), distThreshold, &rmat_ptr,
114 &tr_ptr, &perm_ptr, &hd, &ierr);
115
116 result.error = ierr;
117 if (ierr == 0) {
118 result.permutation.assign(perm_ptr, perm_ptr + nat2);
119 result.hausdorffDistance = hd;
120 // rmat is 3x3 flat array (column-major from Fortran)
121 // Use Eigen maps for efficient conversion
122 Eigen::Map<const Matrix3d> rot_map(rmat_ptr);
123 result.rotation =
124 rot_map.transpose(); // Transpose to get proper row-major layout
125 result.translation = Eigen::Map<const Vector3d>(tr_ptr);
126 }
127
128 // If libira reallocated (pointers changed), free the new buffers.
129 // libira allocates replacement output buffers via c_malloc on the Fortran
130 // side (see libira's lib_match wrapper), so std::free is the matching
131 // deallocator. Never free the original stack/vector-backed buffers.
132 if (rmat_ptr != rmat_buf.data())
133 std::free(rmat_ptr);
134 if (tr_ptr != tr_buf.data())
135 std::free(tr_ptr);
136 if (perm_ptr != perm_buf.data())
137 std::free(perm_ptr);
138 return result;
139}
140
142 double distThreshold) {
143#ifdef WITH_IRA
144 return matchPBC(m1, m2, distThreshold, get_ira_resource());
145#else
146 MatchResult result;
147 result.error = -1;
148 return result;
149#endif
150}
151
153 double distThreshold,
154 IIRAResource &res) {
155 MatchResult result;
156 std::lock_guard<std::mutex> lock(res.library_mutex);
157
158 try {
159 res.require_loaded();
160 } catch (const std::exception &e) {
161 result.error = -1;
162 return result;
163 }
164
165 const int nat1 = m1.numberOfAtoms();
166 const int nat2 = m2.numberOfAtoms();
167 if (nat1 <= 0 || nat2 <= 0) {
168 result.error = -1;
169 return result;
170 }
171
172 std::vector<int> typ1(nat1), typ2(nat2);
173 auto nrs1 = m1.getAtomicNrs();
174 auto nrs2 = m2.getAtomicNrs();
175 for (int i = 0; i < nat1; i++)
176 typ1[i] = nrs1[i];
177 for (int i = 0; i < nat2; i++)
178 typ2[i] = nrs2[i];
179
180 // Prepare coordinates using direct Eigen data access
181 const AtomMatrix &pos1 = m1.getPositions();
182 const AtomMatrix &pos2 = m2.getPositions();
183
184 Eigen::Map<const AtomMatrixF> coords1_map(pos1.data(), 3, nat1);
185 Eigen::Map<const AtomMatrixF> coords2_map(pos2.data(), 3, nat2);
186
187 // Lattice vectors (3x3 column-major)
188 Matrix3d cell = m2.getCell();
189 double lat[9];
190 for (int i = 0; i < 3; i++)
191 for (int j = 0; j < 3; j++)
192 lat[j * 3 + i] = cell(i, j);
193
194 // Caller must pre-allocate output arrays; Fortran receives pointers only
195 std::vector<int> found_buf(nat1);
196 std::vector<double> dists_buf(nat1);
197 int *found_ptr = found_buf.data();
198 double *dists_ptr = dists_buf.data();
199
200 // NOTE: this calls cshda_pbc only (assignment, no rotation/SVD matching).
201 // It finds the best atom assignment under PBC but does not compute the
202 // optimal rotation or translation.
203 res.get_cshda_pbc_fn()(nat1, typ1.data(), coords1_map.data(), nat2,
204 typ2.data(), coords2_map.data(), lat, distThreshold,
205 &found_ptr, &dists_ptr);
206
207 result.permutation.assign(found_ptr, found_ptr + nat1);
208 result.hausdorffDistance = 0.0;
209 for (int i = 0; i < nat1; i++) {
210 result.hausdorffDistance = std::max(result.hausdorffDistance, dists_ptr[i]);
211 }
212 result.rotation = Eigen::Matrix3d::Identity();
213 result.translation = Eigen::Vector3d::Zero();
214 result.error = 0;
215 return result;
216}
217
219IRACompare::findSymmetry(const Matter &m, double threshold, bool prescreenIh) {
220#ifdef WITH_IRA
221 return findSymmetry(m, threshold, prescreenIh, get_ira_resource());
222#else
223 SymmetryResult result;
224 result.error = -1;
225 return result;
226#endif
227}
228
230 double threshold,
231 bool prescreenIh,
232 IIRAResource &res) {
233 SymmetryResult result;
234 std::lock_guard<std::mutex> lock(res.library_mutex);
235
236 try {
237 res.require_loaded();
238 } catch (const std::exception &e) {
239 result.error = -1;
240 return result;
241 }
242
243 const int nat = m.numberOfAtoms();
244 std::vector<int> typ(nat);
245 auto nrs = m.getAtomicNrs();
246 for (int i = 0; i < nat; i++)
247 typ[i] = nrs[i];
248
249 // Prepare coordinates using direct Eigen data access
250 const AtomMatrix &pos = m.getPositions();
251 Eigen::Map<const AtomMatrixF> coords_map(pos.data(), 3, nat);
252
253 // libira_compute_all requires pre-allocated output arrays of size nmax
254 int nmax = res.get_get_nmax_fn()();
255
256 int n_mat = 0;
257 std::vector<double> mat_buf(9 * nmax);
258 std::vector<int> perm_buf(nat * nmax);
259 std::vector<char> op_buf(nmax + 1);
260 std::vector<int> n_buf(nmax);
261 std::vector<int> p_buf(nmax);
262 std::vector<double> ax_buf(3 * nmax);
263 std::vector<double> angle_buf(nmax);
264 std::vector<double> dH_buf(nmax);
265 std::vector<char> pg_buf(11);
266 int n_prin_ax = 0;
267 std::vector<double> prin_ax_buf(3 * nmax);
268 int cerr = 0;
269
270 double *mat_data = mat_buf.data();
271 int *perm_data = perm_buf.data();
272 char *op_data = op_buf.data();
273 int *n_data = n_buf.data();
274 int *p_data = p_buf.data();
275 double *ax_data = ax_buf.data();
276 double *angle_data = angle_buf.data();
277 double *dH_data = dH_buf.data();
278 char *pg = pg_buf.data();
279 double *prin_ax = prin_ax_buf.data();
280
281 res.get_compute_all_fn()(nat, typ.data(), coords_map.data(), threshold,
282 prescreenIh ? 1 : 0, &n_mat, &mat_data, &perm_data,
283 &op_data, &n_data, &p_data, &ax_data, &angle_data,
284 &dH_data, &pg, &n_prin_ax, &prin_ax, &cerr);
285
286 result.error = cerr;
287 if (cerr == 0) {
288 result.nOperations = n_mat;
289 if (pg) {
290 result.pointGroup = std::string(pg);
291 }
292
293 result.operations.resize(n_mat);
294 for (int k = 0; k < n_mat; k++) {
295 Eigen::Map<const Matrix3d> op_map(&mat_data[k * 9]);
296 result.operations[k] =
297 op_map.transpose(); // Transpose to get proper row-major layout
298 }
299
300 if (angle_data) {
301 result.angles.assign(angle_data, angle_data + n_mat);
302 }
303 if (ax_data) {
304 result.axes.resize(n_mat);
305 for (int k = 0; k < n_mat; k++) {
306 result.axes[k] = Eigen::Map<const Vector3d>(&ax_data[k * 3]);
307 }
308 }
309 }
310
311 // Only free if Fortran reallocated (pointer changed from our pre-allocated
312 // buffer). If pointer is unchanged, the vector destructor handles cleanup.
313 // Reallocated buffers come from libira's c_malloc side, so std::free is the
314 // matching deallocator; never free the vector-backed originals.
315 if (mat_data != mat_buf.data())
316 std::free(mat_data);
317 if (perm_data != perm_buf.data())
318 std::free(perm_data);
319 if (op_data != op_buf.data())
320 std::free(op_data);
321 if (n_data != n_buf.data())
322 std::free(n_data);
323 if (p_data != p_buf.data())
324 std::free(p_data);
325 if (ax_data != ax_buf.data())
326 std::free(ax_data);
327 if (angle_data != angle_buf.data())
328 std::free(angle_data);
329 if (dH_data != dH_buf.data())
330 std::free(dH_data);
331 if (pg != pg_buf.data())
332 std::free(pg);
333 if (prin_ax != prin_ax_buf.data())
334 std::free(prin_ax);
335 return result;
336}
337
340 double distThreshold) {
341 MatchResult result = match(reactant, product, distThreshold);
342 if (result.error != 0) {
343 return result;
344 }
345 const long n = reactant.numberOfAtoms();
346 if (n != product.numberOfAtoms() ||
347 static_cast<long>(result.permutation.size()) != n) {
348 result.error = -2;
349 return result;
350 }
351 AtomMatrix pos = reactant.getPositionsCopy();
352 for (long i = 0; i < n; ++i) {
353 Eigen::Vector3d p = pos.row(i);
354 pos.row(i) = (result.rotation * p + result.translation).transpose();
355 }
356 bool oneBased = false;
357 for (int idx : result.permutation) {
358 if (idx == n) {
359 oneBased = true;
360 break;
361 }
362 }
363 AtomMatrix reordered = pos;
364 for (long i = 0; i < n; ++i) {
365 int src = result.permutation[static_cast<size_t>(i)];
366 if (oneBased) {
367 --src;
368 }
369 if (src < 0 || src >= n) {
370 result.error = -3;
371 return result;
372 }
373 reordered.row(i) = pos.row(src);
374 }
375 reactant.setPositions(reordered);
376 return result;
377}
378
379} // 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
virtual void require_loaded()=0
virtual libira_compute_all_fn get_compute_all_fn() const =0
virtual libira_match_fn get_match_fn() const =0
std::mutex library_mutex
Serializes access to the Fortran backend's shared global state.
Definition IRAResource.h:60
virtual libira_get_nmax_fn get_get_nmax_fn() const =0
virtual libira_cshda_pbc_fn get_cshda_pbc_fn() const =0
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 alignReactantToProduct(Matter &reactant, const Matter &product, double distThreshold)
Rigid-align + permute reactant onto product.
static MatchResult match(const Matter &m1, const Matter &m2, double distThreshold)
Match two structures using CShDA + SVD (optimal rotation + assignment).
static MatchResult matchArrays(int nat1, const int *typ1, const double *pos1, int nat2, const int *typ2, const double *pos2, double distThreshold)
Same as match(), from packed (n,3) row-major coordinates and Z arrays.
VectorXi getAtomicNrs() const
Definition Matter.cpp:691
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:350
Matrix3d getCell() const
Definition Matter.cpp:275
long int numberOfAtoms() const
Definition Matter.cpp:273
AtomMatrix getPositionsCopy() const
Definition Matter.cpp:310
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:30
std::vector< int > permutation
Definition IRACompare.h:28
std::vector< double > angles
Definition IRACompare.h:40
std::vector< Eigen::Matrix3d > operations
Definition IRACompare.h:38
std::vector< Eigen::Vector3d > axes
Definition IRACompare.h:41