23#if XTS_ABI_VERSION_MINOR < 10
24#error "xtsci-optimize ABI minor 10 or newer is required for set_periodic"
29struct XtsObjectiveContext {
30 eonc::ObjectiveFunction *objective;
36 Eigen::VectorXd *cached_x;
37 eonc::xtsci_eindir::State *eindir;
40Eigen::Map<const Eigen::VectorXd>
41map_input(
const DLManagedTensorVersioned *tensor) {
42 const auto &dl = tensor->dl_tensor;
43 if (dl.ndim != 1 || dl.dtype.code != kDLFloat || dl.dtype.bits != 64 ||
44 dl.dtype.lanes != 1 || dl.device.device_type != kDLCPU ||
45 dl.shape ==
nullptr || dl.data ==
nullptr) {
46 throw std::runtime_error(
"xtsci objective requires a CPU f64 vector");
48 return {
static_cast<const double *
>(dl.data) +
49 dl.byte_offset /
sizeof(
double),
50 static_cast<Eigen::Index
>(dl.shape[0])};
53Eigen::Map<Eigen::VectorXd> map_output(DLManagedTensorVersioned *tensor) {
54 const auto &dl = tensor->dl_tensor;
55 if (dl.ndim != 1 || dl.dtype.code != kDLFloat || dl.dtype.bits != 64 ||
56 dl.dtype.lanes != 1 || dl.device.device_type != kDLCPU ||
57 dl.shape ==
nullptr || dl.data ==
nullptr) {
58 throw std::runtime_error(
"xtsci tensor requires a CPU f64 vector");
60 return {
static_cast<double *
>(dl.data) + dl.byte_offset /
sizeof(
double),
61 static_cast<Eigen::Index
>(dl.shape[0])};
68 const Eigen::VectorXd &x,
69 Eigen::VectorXd *cached) {
70 if (cached !=
nullptr && cached->size() == x.size() &&
71 (*cached - x).isZero(0.0)) {
77 if (cur.size() == x.size() && (cur - x).isZero(0.0)) {
78 if (cached !=
nullptr) {
84 if (cached !=
nullptr) {
90xts_status_t evaluate_gradient(
void *user,
const DLManagedTensorVersioned *x,
92 DLManagedTensorVersioned *gradient_out) {
94 auto *context =
static_cast<XtsObjectiveContext *
>(user);
95 if (context->eindir !=
nullptr) {
97 context->eindir, x, value_out, gradient_out));
99 const auto positions = map_input(x);
100 set_positions_if_changed(context->objective, positions, context->cached_x);
101 *value_out = context->objective->getEnergy();
102 const auto gradient_value = context->objective->getGradient();
103 auto output = map_output(gradient_out);
104 if (output.size() != gradient_value.size()) {
105 return XTS_INVALID_PARAMETER;
107 output = gradient_value;
110 return XTS_INTERNAL_ERROR;
114xts_status_t hessian(
void *user,
const DLManagedTensorVersioned *x,
115 DLManagedTensorVersioned *hess_out) {
117 auto *context =
static_cast<XtsObjectiveContext *
>(user);
118 const auto positions = map_input(x);
120 positions, context->precon, context->pot, context->precon_A,
121 context->precon_mu, context->precon_rcut, *context->objective);
122 auto output = map_output(hess_out);
123 const Eigen::Index n = H.rows();
124 if (output.size() != n * n) {
125 return XTS_INVALID_PARAMETER;
127 for (Eigen::Index i = 0; i < n; ++i) {
128 for (Eigen::Index j = 0; j < n; ++j) {
129 output[i * n + j] = H(i, j);
134 return XTS_INTERNAL_ERROR;
138xts_method_t method_from_name(
const std::string &name) {
139 if (name ==
"lbfgs") {
142 if (name ==
"bfgs") {
151 if (name ==
"newton") {
157 if (name ==
"steepest") {
160 if (name ==
"adam") {
166 if (name ==
"polak_ribiere" || name ==
"nlcg" || name ==
"pr") {
167 return XTS_POLAK_RIBIERE;
169 if (name ==
"fletcher_reeves") {
170 return XTS_FLETCHER_REEVES;
172 if (name ==
"hestenes_stiefel") {
173 return XTS_HESTENES_STIEFEL;
175 if (name ==
"dai_yuan") {
178 if (name ==
"conjugate_descent") {
179 return XTS_CONJUGATE_DESCENT;
181 if (name ==
"hager_zhang") {
182 return XTS_HAGER_ZHANG;
184 if (name ==
"liu_storey") {
185 return XTS_LIU_STOREY;
187 if (name ==
"fr_pr") {
190 if (name ==
"fire") {
193 if (name ==
"bb" || name ==
"barzilai_borwein") {
196 if (name ==
"dogleg") {
199 if (name ==
"fire2") {
202 throw std::invalid_argument(
203 "unknown Xtsci.method '" + name +
204 "' (lbfgs, bfgs, sr1, sr2, newton, rfo, steepest, adam, pso, "
205 "polak_ribiere, fletcher_reeves, hestenes_stiefel, dai_yuan, "
206 "conjugate_descent, hager_zhang, liu_storey, fr_pr, fire, bb, "
210bool is_host_precon(
const std::string &name) {
211 return name ==
"pair" || name ==
"pair_abs" || name ==
"pair_full" ||
212 name ==
"exp" || name ==
"c1" || name ==
"lindh" ||
213 name ==
"lindh_full" || name ==
"fischer" || name ==
"schlegel" ||
262 const auto dim =
static_cast<size_t>(
m_objf->degreesOfFreedom());
266 const auto stamp = xts_abi_stamp();
267 if (xts_abi_compatible(&stamp) == 0) {
268 throw std::runtime_error(
"incompatible xtsci-optimize ABI");
271 const xts_method_t method = method_from_name(
m_optConfig.opts.xtsci.method);
272 double istep = std::max(a_maxMove, 1.0e-12);
273 if (method == XTS_FIRE || method == XTS_FIRE2) {
282 xts_control_t control{
283 static_cast<size_t>(std::max<long>(1,
m_optConfig.opts.max_iterations)),
286 static_cast<size_t>(std::max<long>(0, lbfgs.memory)),
289 m_solver = xts_solver_create(method, &control, dim);
291 throw std::runtime_error(xts_last_error());
294 if (
step ==
"newton") {
295 xts_solver_set_qn_step(
m_solver, XTS_QN_NEWTON);
296 }
else if (
step ==
"rfo") {
297 xts_solver_set_qn_step(
m_solver, XTS_QN_RFO);
299 xts_solver_set_qn_step(
m_solver, XTS_QN_LBFGS);
301 const auto accept = resolved_accept(
m_optConfig.opts);
302 if (accept ==
"energy") {
303 xts_solver_set_accept(
m_solver, XTS_ACCEPT_ENERGY);
304 }
else if (accept ==
"nonmonotone") {
305 xts_solver_set_accept(
m_solver, XTS_ACCEPT_NONMONOTONE);
307 xts_solver_set_accept(
m_solver, XTS_ACCEPT_NONE);
309 xts_solver_set_project_rigid(
m_solver, lbfgs.project_rigid ? 1 : 0);
310 xts_solver_set_extra_updates(
311 m_solver,
static_cast<size_t>(std::max<long>(0, lbfgs.extra_updates)));
312 if (lbfgs.curvature ==
"cautious") {
313 xts_solver_set_cautious(
m_solver, lbfgs.cautious_eps, lbfgs.cautious_alpha);
315 xts_solver_set_cautious(
m_solver, 0.0, lbfgs.cautious_alpha);
318 if (xts_solver_set_highs(
m_solver, 1) != 0) {
319 throw std::runtime_error(
320 "Xtsci.highs needs xtsci-optimize built with --features highs");
323 const auto &mani =
m_optConfig.opts.xtsci.manifold;
324 if (mani ==
"so3" && dim != 9) {
325 throw std::runtime_error(
326 "Xtsci.manifold=so3 needs length 9; a 3N cluster is "
327 "rigid_quotient (Sella R^{3N}/SE(3)), not a packed rotation");
329 if (mani ==
"se3" && dim != 12) {
330 throw std::runtime_error(
331 "Xtsci.manifold=se3 needs length 12; a 3N cluster is "
332 "rigid_quotient (Sella R^{3N}/SE(3)), not an SE(3) prefix");
334 if ((mani ==
"rigid_quotient" || mani ==
"mw_rigid" || mani ==
"sella" ||
335 mani ==
"eckart" || mani ==
"irc") &&
336 (dim < 6 || dim % 3 != 0)) {
337 throw std::runtime_error(
"Xtsci.manifold=" + mani +
338 " needs 3N Cartesians with N >= 2");
340 if (mani ==
"sphere") {
341 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_SPHERE);
342 }
else if (mani ==
"so3") {
343 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_SO3);
344 }
else if (mani ==
"stiefel") {
345 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_STIEFEL);
346 }
else if (mani ==
"se3") {
347 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_SE3);
348 }
else if (mani ==
"rigid_quotient" || mani ==
"sella") {
349 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_RIGID_QUOTIENT);
350 }
else if (mani ==
"mw_rigid" || mani ==
"eckart" || mani ==
"irc") {
351 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_MW_RIGID);
352 const auto masses =
m_objf->getMasses();
353 if (masses.size() > 0) {
354 xts_solver_set_masses(
m_solver, masses.data(),
355 static_cast<size_t>(masses.size()));
358 xts_solver_set_manifold(
m_solver, XTS_MANIFOLD_EUCLIDEAN);
360 if (mani ==
"rigid_quotient" || mani ==
"mw_rigid" || mani ==
"sella" ||
361 mani ==
"eckart" || mani ==
"irc") {
370 if (
m_objf->degreesOfFreedom() <= 0) {
371 return m_objf->isConverged() ? 1 : 0;
375 return m_objf->isConverged() ? 1 : 0;
378 xts_solver_set_maxmove(
m_solver, 0.0);
379 xts_solver_set_atom_maxmove(
m_solver, std::max(a_maxMove, 0.0));
381 if (
m_x.size() == 0) {
384 if (
m_x.size() !=
m_objf->degreesOfFreedom()) {
385 throw std::runtime_error(
"xtsci objective position dimension mismatch");
388 xts_tensor_borrow_cpu_f64(
m_x.data(),
static_cast<size_t>(
m_x.size()));
389 if (tensor ==
nullptr) {
390 throw std::runtime_error(
"could not allocate xtsci objective tensor");
393 std::string precon = resolved_precon(
m_optConfig.opts);
394 const xts_method_t method = method_from_name(
m_optConfig.opts.xtsci.method);
395 if (method == XTS_DOGLEG && !is_host_precon(precon)) {
398 XtsObjectiveContext context{
402 xts_report_t report{};
403 const bool want_hess = method == XTS_NEWTON || method == XTS_RFO ||
404 method == XTS_DOGLEG || is_host_precon(precon);
407 status = xts_solver_step_hess_fg(
m_solver, evaluate_gradient, hessian,
408 &context, tensor, &report);
410 status = xts_solver_step_fg(
m_solver, evaluate_gradient, &context, tensor,
413 xts_tensor_free(tensor);
414 if (status != XTS_SUCCESS) {
415 throw std::runtime_error(xts_last_error());
417 return m_objf->isConverged() ? 1 : 0;
421 if (
m_objf->degreesOfFreedom() <= 0 || a_maxIterations == 0) {
422 return m_objf->isConverged() ? 1 : 0;
426 std::string precon = resolved_precon(
m_optConfig.opts);
427 const xts_method_t method = method_from_name(
m_optConfig.opts.xtsci.method);
428 if (method == XTS_DOGLEG && !is_host_precon(precon)) {
431 const bool want_hess = method == XTS_NEWTON || method == XTS_RFO ||
432 method == XTS_DOGLEG || is_host_precon(precon);
435 if (
m_eindir !=
nullptr && !want_hess) {
436 if (
m_x.size() == 0) {
441 m_eindir,
m_x.data(),
static_cast<size_t>(
m_x.size()), a_maxIterations,
442 m_optConfig.opts.converged_force, std::max(a_maxMove, 1.0e-12),
443 static_cast<size_t>(std::max<long>(0, lbfgs.memory)),
444 static_cast<int>(method), &value);
445 if (status != XTS_SUCCESS) {
446 throw std::runtime_error(xts_last_error());
451 return m_objf->isConverged() ? 1 : 0;
453 for (
size_t i = 0; i < a_maxIterations; ++i) {
454 if (
step(a_maxMove) != 0) {
458 return m_objf->isConverged() ? 1 : 0;
virtual VectorXd getPositions()=0
virtual void setPositions(const VectorXd &x)=0
const OptimizerConfig m_optConfig
std::shared_ptr< ObjectiveFunction > m_objf
eonc::optimizer_options_t optimizer_options_t
int run(size_t a_maxIterations, double a_maxMove) override
void ensureSolver(double a_maxMove)
~XtsciOptimizer() override
Eigen::VectorXd m_cached_x
int step(double a_maxMove) override
xtsci_eindir::State * m_eindir
Eigen::MatrixXd build(const Eigen::VectorXd &pos, const std::string &kind, PotType pot, double A, double mu, double rcut_in, ObjectiveFunction &objf)
Analytic pair or model Hessian.
int minimize(State *state, double *x, std::size_t n, std::size_t maxiter, double gtol, double istep, std::size_t memory, int method, double *value_out)
xts_minimize_eindir on a caller-owned buffer. The caller keeps State.
void release(State *state)
State * bind(ObjectiveFunction *objective, Eigen::VectorXd *cached)
Borrow an ObjectiveFunction as an eindir objective.
int eval_grad(State *state, const DLManagedTensorVersioned *x, double *value, DLManagedTensorVersioned *gradient)
One fused energy and gradient through the borrowed eindir handle.
RAII resource manager for the ARTn C library with global synchronization.
struct eonc::optimizer_options_t::xtsci_t xtsci
struct eonc::optimizer_options_t::lbfgs_t lbfgs