votca 2026-dev
Loading...
Searching...
No Matches
chargeresponsefit.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <exception>
22#include <sstream>
23#include <stdexcept>
24
25// VOTCA includes
27
28// Local VOTCA includes
29#include "votca/xtp/aomatrix.h"
30#include "votca/xtp/basisset.h"
32#include "votca/xtp/dftengine.h"
33#include "votca/xtp/logger.h"
34#include "votca/xtp/orbitals.h"
35
36namespace votca {
37namespace xtp {
38
39namespace {
40// Hirshfeld weights of all atoms at a point (rows: atoms), zero where the
41// promolecule density is negligible (as HirshfeldPartition::EvaluateWeight)
42Eigen::VectorXd Weights(
43 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
44 const Eigen::Vector3d& point) {
45 Eigen::VectorXd rho(Index(atoms.size()));
46 for (Index j = 0; j < rho.size(); ++j) {
48 atoms[std::size_t(j)].basis, atoms[std::size_t(j)].density, point);
49 }
50 const double denominator = rho.sum();
51 constexpr double kNegligibleDenominator = 1.e-12;
52 if (denominator < kNegligibleDenominator) {
53 return Eigen::VectorXd::Zero(rho.size());
54 }
55 return rho / denominator;
56}
57} // namespace
58
59std::vector<Eigen::MatrixXd> ChargeResponseFit::WeightMatrices(
60 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
61 const AOBasis& basis, const Vxc_Grid& grid) {
62 const Index n_atoms = Index(atoms.size());
63 const Index n_ao = basis.AOBasisSize();
64 std::vector<Eigen::MatrixXd> W(std::size_t(n_atoms),
65 Eigen::MatrixXd::Zero(n_ao, n_ao));
66 std::exception_ptr eptr = nullptr;
67#pragma omp parallel for schedule(guided)
68 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
69 try {
70 const GridBox& box = grid[i];
71 if (!box.Matrixsize()) {
72 continue;
73 }
74 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
75 const std::vector<double>& weights = box.getGridWeights();
76 const Index n_points = Index(points.size());
77 Eigen::MatrixXd ao(box.Matrixsize(), n_points);
78 Eigen::MatrixXd w(n_points, n_atoms);
79 for (Index p = 0; p < n_points; ++p) {
80 ao.col(p) = box.CalcAOValues(points[std::size_t(p)]).values;
81 w.row(p) = weights[std::size_t(p)] *
82 Weights(atoms, points[std::size_t(p)]).transpose();
83 }
84 std::vector<Eigen::MatrixXd> local(static_cast<std::size_t>(n_atoms));
85 for (Index k = 0; k < n_atoms; ++k) {
86 if (w.col(k).cwiseAbs().maxCoeff() == 0.0) {
87 continue;
88 }
89 local[std::size_t(k)] = ao * w.col(k).asDiagonal() * ao.transpose();
90 }
91#pragma omp critical
92 {
93 for (Index k = 0; k < n_atoms; ++k) {
94 if (local[std::size_t(k)].size() > 0) {
95 box.AddtoBigMatrix(W[std::size_t(k)], local[std::size_t(k)]);
96 }
97 }
98 }
99 } catch (...) {
100#pragma omp critical
101 {
102 if (!eptr) {
103 eptr = std::current_exception();
104 }
105 }
106 }
107 }
108 if (eptr) {
109 std::rethrow_exception(eptr);
110 }
111 for (Eigen::MatrixXd& m : W) {
112 m = 0.5 * (m + m.transpose()).eval();
113 }
114 return W;
115}
116
117std::vector<Eigen::Matrix3d> ChargeResponseFit::AtomicPolarizabilities(
118 const std::vector<HirshfeldPartition::AtomicReference>& atoms,
119 const std::vector<Eigen::Vector3d>& positions, const AOBasis& basis,
120 const Vxc_Grid& grid, const std::array<Eigen::MatrixXd, 3>& dP_field) {
121 const Index n_atoms = Index(atoms.size());
122 if (Index(positions.size()) != n_atoms) {
123 throw std::runtime_error(
124 "ChargeResponseFit: positions and Hirshfeld references differ in "
125 "size");
126 }
127 for (const Eigen::MatrixXd& m : dP_field) {
128 if (m.rows() != basis.AOBasisSize() || m.cols() != basis.AOBasisSize()) {
129 throw std::runtime_error(
130 "ChargeResponseFit: density response does not match the basis");
131 }
132 }
133 std::vector<Eigen::Matrix3d> alpha(std::size_t(n_atoms),
134 Eigen::Matrix3d::Zero());
135 std::exception_ptr eptr = nullptr;
136#pragma omp parallel for schedule(guided)
137 for (Index i = 0; i < grid.getBoxesSize(); ++i) {
138 try {
139 const GridBox& box = grid[i];
140 if (!box.Matrixsize()) {
141 continue;
142 }
143 std::array<Eigen::MatrixXd, 3> dP_box;
144 for (Index c = 0; c < 3; ++c) {
145 dP_box[std::size_t(c)] =
146 box.ReadFromBigMatrix(dP_field[std::size_t(c)]);
147 }
148 const std::vector<Eigen::Vector3d>& points = box.getGridPoints();
149 const std::vector<double>& weights = box.getGridWeights();
150 std::vector<Eigen::Matrix3d> local(std::size_t(n_atoms),
151 Eigen::Matrix3d::Zero());
152 for (std::size_t p = 0; p < points.size(); ++p) {
153 const Eigen::VectorXd w = Weights(atoms, points[p]);
154 if (w.cwiseAbs().maxCoeff() == 0.0) {
155 continue;
156 }
157 const Eigen::VectorXd ao = box.CalcAOValues(points[p]).values;
158 Eigen::Vector3d rho;
159 for (Index c = 0; c < 3; ++c) {
160 rho(c) = ao.dot(dP_box[std::size_t(c)] * ao);
161 }
162 for (Index k = 0; k < n_atoms; ++k) {
163 const Eigen::Vector3d d = points[p] - positions[std::size_t(k)];
164 local[std::size_t(k)] -= (weights[p] * w(k)) * d * rho.transpose();
165 }
166 }
167#pragma omp critical
168 {
169 for (Index k = 0; k < n_atoms; ++k) {
170 alpha[std::size_t(k)] += local[std::size_t(k)];
171 }
172 }
173 } catch (...) {
174#pragma omp critical
175 {
176 if (!eptr) {
177 eptr = std::current_exception();
178 }
179 }
180 }
181 }
182 if (eptr) {
183 std::rethrow_exception(eptr);
184 }
185 return alpha;
186}
187
189 const std::vector<Eigen::Vector3d>& positions,
190 const std::vector<Eigen::MatrixXd>& W,
191 const std::vector<Eigen::MatrixXd>& dP_potential,
192 const std::array<Eigen::MatrixXd, 3>& dipole,
193 const std::array<Eigen::MatrixXd, 3>& dP_field,
194 const std::vector<Eigen::Matrix3d>& alpha_atoms) {
195 const Index n = Index(positions.size());
196 if (Index(W.size()) != n || Index(dP_potential.size()) != n ||
197 Index(alpha_atoms.size()) != n || n < 2) {
198 throw std::runtime_error(
199 "ChargeResponseFit::Analyse: inconsistent number of atoms");
200 }
201 Result r;
202 Eigen::MatrixXd pi(n, n);
203 for (Index j = 0; j < n; ++j) {
204 for (Index k = 0; k < n; ++k) {
205 pi(j, k) =
206 -W[std::size_t(j)].cwiseProduct(dP_potential[std::size_t(k)]).sum();
207 }
208 }
209 r.pi_asymmetry = (pi - pi.transpose()).norm() / pi.norm();
210 const Eigen::MatrixXd projector =
211 Eigen::MatrixXd::Identity(n, n) -
212 Eigen::MatrixXd::Constant(n, n, 1.0 / double(n));
213 r.pi = projector * (0.5 * (pi + pi.transpose())) * projector;
214 r.pi = 0.5 * (r.pi + r.pi.transpose()).eval();
215 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(r.pi,
216 Eigen::EigenvaluesOnly);
217 r.pi_eigenvalues = es.eigenvalues();
218 if (r.pi_eigenvalues(0) < -1e-6 * r.pi_eigenvalues(n - 1)) {
219 std::stringstream message;
220 message << "ChargeResponseFit: Pi is not positive semidefinite (lowest "
221 "eigenvalue "
222 << r.pi_eigenvalues(0)
223 << "); tighten the SCF convergence or change the step";
224 throw std::runtime_error(message.str());
225 }
226
227 for (Index a = 0; a < 3; ++a) {
228 for (Index c = 0; c < 3; ++c) {
229 r.alpha(a, c) =
230 -dipole[std::size_t(a)].cwiseProduct(dP_field[std::size_t(c)]).sum();
231 }
232 }
233 r.alpha = 0.5 * (r.alpha + r.alpha.transpose()).eval();
234
235 Eigen::MatrixX3d X(n, 3);
236 for (Index k = 0; k < n; ++k) {
237 X.row(k) = positions[std::size_t(k)].transpose();
238 }
239 X.rowwise() -= X.colwise().mean();
240 r.alpha_ct = X.transpose() * r.pi * X;
241 r.alpha_res = r.alpha - r.alpha_ct;
242 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eres(r.alpha_res,
243 Eigen::EigenvaluesOnly);
244 if (eres.eigenvalues()(0) <= 0.0) {
245 std::stringstream message;
246 message << "ChargeResponseFit: alpha - X^T Pi X is not positive definite "
247 "(eigenvalues "
248 << eres.eigenvalues().transpose()
249 << " bohr^3); the charge response over-polarizes";
250 throw std::runtime_error(message.str());
251 }
252
253 r.alpha_atoms = alpha_atoms;
254 r.res_weights.resize(n);
255 for (Index k = 0; k < n; ++k) {
256 r.res_weights(k) = std::max(alpha_atoms[std::size_t(k)].trace(), 0.0);
257 }
258 if (!(r.res_weights.sum() > 0.0)) {
259 throw std::runtime_error(
260 "ChargeResponseFit: no positive Hirshfeld atomic polarizability");
261 }
262 r.res_weights /= r.res_weights.sum();
263 for (Index k = 0; k < n; ++k) {
264 r.alpha_res_atoms.push_back(r.res_weights(k) * r.alpha_res);
265 }
266 return r;
267}
268
270 const QMMolecule& mol, const AOBasis& basis, double tol) {
271 Inversion inv;
272 const Index n = mol.size();
273 Eigen::Vector3d centre = Eigen::Vector3d::Zero();
274 for (const QMAtom& atom : mol) {
275 centre += atom.getPos();
276 }
277 centre /= double(n);
278 std::vector<Index> map(static_cast<std::size_t>(n), -1);
279 for (Index k = 0; k < n; ++k) {
280 const Eigen::Vector3d image = 2.0 * centre - mol[k].getPos();
281 for (Index j = 0; j < n; ++j) {
282 if ((mol[j].getPos() - image).norm() < tol &&
283 mol[j].getElement() == mol[k].getElement()) {
284 map[std::size_t(k)] = j;
285 break;
286 }
287 }
288 if (map[std::size_t(k)] < 0) {
289 return inv;
290 }
291 }
292 const Index n_ao = basis.AOBasisSize();
293 Eigen::MatrixXd T = Eigen::MatrixXd::Zero(n_ao, n_ao);
294 for (Index k = 0; k < n; ++k) {
295 const std::vector<const AOShell*> from = basis.getShellsofAtom(k);
296 const std::vector<const AOShell*> to =
297 basis.getShellsofAtom(map[std::size_t(k)]);
298 if (from.size() != to.size()) {
299 return inv;
300 }
301 for (std::size_t s = 0; s < from.size(); ++s) {
302 if (from[s]->getL() != to[s]->getL()) {
303 return inv;
304 }
305 const double parity = (Index(from[s]->getL()) % 2 == 0) ? 1.0 : -1.0;
306 for (Index f = 0; f < from[s]->getNumFunc(); ++f) {
307 T(to[s]->getStartIndex() + f, from[s]->getStartIndex() + f) = parity;
308 }
309 }
310 }
311 inv.atom_map = map;
312 inv.T = T;
313 return inv;
314}
315
317 const tools::Property& dftoptions, Orbitals& orb, const FitOptions& fit,
318 Logger& log) {
319 if (orb.getSpin() != 1) {
320 throw std::runtime_error(
321 "ChargeResponseFit: closed-shell molecules only (spin 1)");
322 }
323 if (!fit.analytic && !(fit.step > 0.0)) {
324 throw std::runtime_error("ChargeResponseFit: step must be positive");
325 }
326 if (fit.analytic && !(fit.tolerance > 0.0)) {
327 throw std::runtime_error("ChargeResponseFit: tolerance must be positive");
328 }
329 const double step = fit.step;
330 const QMMolecule& mol = orb.QMAtoms();
331 const Index n_atoms = mol.size();
332 // DFTEngine::Initialize takes a non-const tree
333 tools::Property options = dftoptions;
334
335 DFTSetupCache cache;
336 // one SCF of mol with the electron potential energy operator v (none if
337 // empty), warm-started from the ground state for the perturbed ones
338 auto scf = [&](const Eigen::MatrixXd& v, Orbitals& target) {
339 DFTEngine engine;
340 engine.setLogger(&log);
341 engine.Initialize(options);
342 engine.setSetupCache(&cache);
343 engine.setWarmStart(v.size() > 0);
344 if (v.size() > 0) {
346 }
347 const bool ok = engine.Evaluate(target);
348 engine.ReturnSetupCache();
349 if (!ok) {
350 throw std::runtime_error("ChargeResponseFit: SCF did not converge");
351 }
352 };
353
354 XTP_LOG(Log::error, log) << TimeStamp()
355 << " Charge-response fit: ground state"
356 << std::flush;
357 scf(Eigen::MatrixXd(), orb);
358
359 DFTEngine setup;
360 setup.setLogger(&log);
361 setup.Initialize(options);
362 AOBasis basis;
363 {
364 BasisSet bs;
365 bs.Load(options.get("basisset").as<std::string>());
366 basis.Fill(bs, mol);
367 }
368 Vxc_Grid grid;
369 grid.GridSetup(setup.getGridName(), mol, basis);
370 const std::vector<HirshfeldPartition::AtomicReference> atoms =
371 setup.HirshfeldReferences(mol);
372 const std::vector<Eigen::MatrixXd> W = WeightMatrices(atoms, basis, grid);
373 {
374 AOOverlap overlap;
375 overlap.Fill(basis);
376 Eigen::MatrixXd sum = -overlap.Matrix();
377 for (const Eigen::MatrixXd& w : W) {
378 sum += w;
379 }
380 XTP_LOG(Log::error, log)
381 << TimeStamp() << " Hirshfeld weight matrices: max |sum_k W_k - S| = "
382 << sum.cwiseAbs().maxCoeff() << std::flush;
383 }
384 AODipole dipole;
385 dipole.setCenter(mol.getPos());
386 dipole.Fill(basis);
387
388 // central differences: dP = (P(+step) - P(-step)) / (2 step)
389 auto response = [&](const Eigen::MatrixXd& h, const std::string& label) {
390 Eigen::MatrixXd dP = Eigen::MatrixXd::Zero(h.rows(), h.cols());
391 for (double sign : {1.0, -1.0}) {
392 XTP_LOG(Log::error, log)
393 << TimeStamp() << " Perturbation " << label
394 << (sign > 0 ? " +" : " -") << step << std::flush;
395 Orbitals perturbed = orb;
396 scf(sign * step * h, perturbed);
397 dP += sign * perturbed.DensityMatrixGroundState();
398 }
399 return Eigen::MatrixXd(dP / (2.0 * step));
400 };
401 Inversion inv;
402 if (fit.use_inversion) {
403 inv = FindInversion(mol, basis);
404 if (!inv.atom_map.empty()) {
405 XTP_LOG(Log::error, log)
406 << TimeStamp()
407 << " Inversion centre: one atom of each pair is perturbed"
408 << std::flush;
409 }
410 }
411 // the atoms whose response is computed; the others are their images
412 std::vector<Index> perturbed;
413 for (Index k = 0; k < n_atoms; ++k) {
414 if (inv.atom_map.empty() || inv.atom_map[std::size_t(k)] >= k) {
415 perturbed.push_back(k);
416 }
417 }
418 std::vector<Eigen::MatrixXd> dP_potential(static_cast<std::size_t>(n_atoms));
419 std::array<Eigen::MatrixXd, 3> dP_field;
420 if (fit.analytic) {
421 std::vector<Eigen::MatrixXd> h;
422 for (Index k : perturbed) {
423 h.push_back(W[std::size_t(k)]);
424 }
425 for (Index c = 0; c < 3; ++c) {
426 h.push_back(dipole.Matrix()[std::size_t(c)]);
427 }
428 XTP_LOG(Log::error, log) << TimeStamp() << " Coupled-perturbed KS for "
429 << h.size() << " perturbations" << std::flush;
430 DFTEngine engine;
431 engine.setLogger(&log);
432 engine.Initialize(options);
433 const std::vector<Eigen::MatrixXd> dP =
434 engine.StaticResponse(orb, h, fit.tolerance);
435 for (std::size_t j = 0; j < perturbed.size(); ++j) {
436 dP_potential[std::size_t(perturbed[j])] = dP[j];
437 }
438 for (std::size_t c = 0; c < 3; ++c) {
439 dP_field[c] = dP[perturbed.size() + c];
440 }
441 } else {
442 for (Index k : perturbed) {
443 dP_potential[std::size_t(k)] =
444 response(W[std::size_t(k)],
445 "w_" + std::to_string(k) + " (" + mol[k].getElement() + ")");
446 }
447 const std::array<std::string, 3> axes = {"x", "y", "z"};
448 for (Index c = 0; c < 3; ++c) {
449 dP_field[std::size_t(c)] = response(dipole.Matrix()[std::size_t(c)],
450 "field " + axes[std::size_t(c)]);
451 }
452 }
453 for (Index k = 0; k < n_atoms; ++k) {
454 if (!inv.atom_map.empty() && inv.atom_map[std::size_t(k)] < k) {
455 dP_potential[std::size_t(k)] =
456 inv.T * dP_potential[std::size_t(inv.atom_map[std::size_t(k)])] *
457 inv.T.transpose();
458 }
459 }
460
461 std::vector<Eigen::Vector3d> positions;
462 for (const QMAtom& atom : mol) {
463 positions.push_back(atom.getPos());
464 }
465 const std::vector<Eigen::Matrix3d> alpha_atoms =
466 AtomicPolarizabilities(atoms, positions, basis, grid, dP_field);
467 return Analyse(positions, W, dP_potential, dipole.Matrix(), dP_field,
468 alpha_atoms);
469}
470
471} // namespace xtp
472} // namespace votca
class to manage program options with xml serialization functionality
Definition property.h:55
Property & get(const std::string &key)
get existing property
Definition property.cc:79
T as() const
return value as type
Definition property.h:283
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
const std::vector< const AOShell * > getShellsofAtom(Index AtomId) const
Definition aobasis.cc:63
Index AOBasisSize() const
Definition aobasis.h:46
void Fill(const BasisSet &bs, const QMMolecule &atoms)
Definition aobasis.cc:85
void setCenter(const Eigen::Vector3d &r)
Definition aomatrix.h:94
const std::array< Eigen::MatrixXd, 3 > & Matrix() const
Definition aomatrix.h:92
void Fill(const AOBasis &aobasis) final
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:52
const Eigen::Vector3d & getPos() const
void Load(const std::string &name)
Definition basisset.cc:149
static Inversion FindInversion(const QMMolecule &mol, const AOBasis &basis, double tol=1e-4)
static std::vector< Eigen::Matrix3d > AtomicPolarizabilities(const std::vector< HirshfeldPartition::AtomicReference > &atoms, const std::vector< Eigen::Vector3d > &positions, const AOBasis &basis, const Vxc_Grid &grid, const std::array< Eigen::MatrixXd, 3 > &dP_field)
static Result Analyse(const std::vector< Eigen::Vector3d > &positions, const std::vector< Eigen::MatrixXd > &W, const std::vector< Eigen::MatrixXd > &dP_potential, const std::array< Eigen::MatrixXd, 3 > &dipole, const std::array< Eigen::MatrixXd, 3 > &dP_field, const std::vector< Eigen::Matrix3d > &alpha_atoms)
static std::vector< Eigen::MatrixXd > WeightMatrices(const std::vector< HirshfeldPartition::AtomicReference > &atoms, const AOBasis &basis, const Vxc_Grid &grid)
static Result Fit(const tools::Property &dftoptions, Orbitals &orb, const FitOptions &fit, Logger &log)
Electronic ground-state via Density-Functional Theory.
Definition dftengine.h:95
void setWarmStart(bool warm_start)
Definition dftengine.h:126
void setLogger(Logger *pLog)
Attach the logger used for SCF progress and diagnostics.
Definition dftengine.h:101
std::vector< HirshfeldPartition::AtomicReference > HirshfeldReferences(const QMMolecule &mol) const
void setExternalPotentialMatrix(const Eigen::MatrixXd &v)
Definition dftengine.h:207
void Initialize(tools::Property &options)
Read DFT, grid, and SCF settings from the user options tree.
Definition dftengine.cc:104
const std::string & getGridName() const
Name of the integration grid of the SCF (xtpdft.integration_grid).
Definition dftengine.h:199
std::vector< Eigen::MatrixXd > StaticResponse(const Orbitals &orb, const std::vector< Eigen::MatrixXd > &perturbations, double tolerance=1e-7, Index max_iterations=100)
bool Evaluate(Orbitals &orb)
Definition dftengine.cc:985
void setSetupCache(DFTSetupCache *cache)
Definition dftengine.h:131
const std::vector< Eigen::Vector3d > & getGridPoints() const
Definition gridbox.h:55
Eigen::MatrixXd ReadFromBigMatrix(const Eigen::MatrixXd &bigmatrix) const
Definition gridbox.cc:97
void AddtoBigMatrix(Eigen::MatrixXd &bigmatrix, const Eigen::MatrixXd &smallmatrix) const
Definition gridbox.cc:84
const std::vector< double > & getGridWeights() const
Definition gridbox.h:57
AOShell::AOValues CalcAOValues(const Eigen::Vector3d &point) const
Definition gridbox.cc:44
Index Matrixsize() const
Definition gridbox.h:75
static double EvaluateAtomicDensity(const AOBasis &atom_basis, const Eigen::MatrixXd &reference_density, const Eigen::Vector3d &point)
Logger is used for thread-safe output of messages.
Definition logger.h:164
Container for molecular orbitals and derived one-particle data.
Definition orbitals.h:47
Index getSpin() const
Return the stored spin multiplicity.
Definition orbitals.h:252
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
Definition orbitals.h:262
container for QM atoms
Definition qmatom.h:37
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
void GridSetup(const std::string &type, const QMMolecule &atoms, const AOBasis &basis)
Definition vxc_grid.cc:230
Index getBoxesSize() const
Definition vxc_grid.h:42
#define XTP_LOG(level, log)
Definition logger.h:40
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
double step
finite differences: strength of each perturbation
std::vector< Eigen::Matrix3d > alpha_res_atoms
std::vector< Eigen::Matrix3d > alpha_atoms
Parts of the DFT setup that stay the same between runs on the same molecule with the same basis sets,...
Definition dftengine.h:55