Loading...
Searching...
No Matches
XtsciEindir.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/XtsciEindir.h"
13
14#include <format>
15#include <limits>
16#include <memory>
17#include <stdexcept>
18#include <vector>
19
20#ifdef WITH_EINDIR
21#include <eindir-core.h>
22#include <xts.h>
23#endif
24
26
27namespace {
28
29std::string &provenance_slot() {
30 static std::string text;
31 return text;
32}
33
34bool scalar_ok(long actual, long required) {
35 return required == 0 || actual == required;
36}
37
38bool text_ok(const std::string &actual, const std::string &required) {
39 return required.empty() || actual == required;
40}
41
42} // namespace
43
44#ifdef WITH_EINDIR
45
46struct State {
47 ObjectiveFunction *objective{nullptr};
48 Eigen::VectorXd *cached{nullptr};
49 std::string schema;
50 std::string producer;
51 std::string length_unit;
52 std::string energy_unit;
53 std::vector<double> low;
54 std::vector<double> high;
55 eindir_objective_descriptor_t desc{};
56 eindir_objective_t obj{};
57};
58
59void set_positions_if_changed(State *state, const Eigen::VectorXd &x) {
60 auto *cached = state->cached;
61 if (cached != nullptr && cached->size() == x.size() &&
62 (*cached - x).isZero(0.0)) {
63 return;
64 }
65 const auto cur = state->objective->getPositions();
66 if (cur.size() == x.size() && (cur - x).isZero(0.0)) {
67 if (cached != nullptr) {
68 *cached = x;
69 }
70 return;
71 }
72 state->objective->setPositions(x);
73 if (cached != nullptr) {
74 *cached = x;
75 }
76}
77
78Eigen::Map<const Eigen::VectorXd>
79map_input(const DLManagedTensorVersioned *tensor) {
80 const auto &dl = tensor->dl_tensor;
81 if (dl.ndim != 1 || dl.dtype.code != kDLFloat || dl.dtype.bits != 64 ||
82 dl.dtype.lanes != 1 || dl.device.device_type != kDLCPU ||
83 dl.shape == nullptr || dl.data == nullptr) {
84 throw std::runtime_error("eindir objective requires a CPU f64 vector");
85 }
86 return {static_cast<const double *>(dl.data) +
87 dl.byte_offset / sizeof(double),
88 static_cast<Eigen::Index>(dl.shape[0])};
89}
90
91Eigen::Map<Eigen::VectorXd> map_output(DLManagedTensorVersioned *tensor) {
92 const auto &dl = tensor->dl_tensor;
93 if (dl.ndim != 1 || dl.dtype.code != kDLFloat || dl.dtype.bits != 64 ||
94 dl.dtype.lanes != 1 || dl.device.device_type != kDLCPU ||
95 dl.shape == nullptr || dl.data == nullptr) {
96 throw std::runtime_error("eindir gradient requires a CPU f64 vector");
97 }
98 return {static_cast<double *>(dl.data) + dl.byte_offset / sizeof(double),
99 static_cast<Eigen::Index>(dl.shape[0])};
100}
101
102eindir_status_t eval_cb(void *user, const DLManagedTensorVersioned *x,
103 double *value_out) {
104 try {
105 auto *state = static_cast<State *>(user);
106 set_positions_if_changed(state, map_input(x));
107 *value_out = state->objective->getEnergy();
108 return EINDIR_SUCCESS;
109 } catch (...) {
110 return EINDIR_INTERNAL_ERROR;
111 }
112}
113
114eindir_status_t grad_cb(void *user, const DLManagedTensorVersioned *x,
115 DLManagedTensorVersioned *grad_out) {
116 try {
117 auto *state = static_cast<State *>(user);
118 set_positions_if_changed(state, map_input(x));
119 const auto gradient = state->objective->getGradient();
120 auto output = map_output(grad_out);
121 if (output.size() != gradient.size()) {
122 return EINDIR_INVALID_PARAMETER;
123 }
124 output = gradient;
125 return EINDIR_SUCCESS;
126 } catch (...) {
127 return EINDIR_INTERNAL_ERROR;
128 }
129}
130
131#endif
132
134 View view;
135 view.schema_id = kSchemaId;
136 view.length_unit = "angstrom";
137 view.energy_unit = "eV";
138 view.energy_sign = 1;
139 view.gradient_sign = 1;
143 view.tensor_dtype_bits = 64;
144 view.tensor_dtype_lanes = 1;
147 return view;
148}
149
151 View view = eon_requirement();
152 view.producer_id = "rgpot";
153 return view;
154}
155
156bool compatible(const View &actual, const View &required) {
157 if (actual.schema_id.empty() || actual.schema_id != required.schema_id) {
158 return false;
159 }
160 if (!text_ok(actual.producer_id, required.producer_id) ||
161 !text_ok(actual.length_unit, required.length_unit) ||
162 !text_ok(actual.energy_unit, required.energy_unit)) {
163 return false;
164 }
165 if (!scalar_ok(actual.energy_sign, required.energy_sign) ||
166 !scalar_ok(actual.gradient_sign, required.gradient_sign)) {
167 return false;
168 }
169 if ((actual.operations & required.operations) != required.operations) {
170 return false;
171 }
172 return scalar_ok(actual.tensor_device_type, required.tensor_device_type) &&
173 scalar_ok(actual.tensor_dtype_code, required.tensor_dtype_code) &&
174 scalar_ok(actual.tensor_dtype_bits, required.tensor_dtype_bits) &&
175 scalar_ok(actual.tensor_dtype_lanes, required.tensor_dtype_lanes) &&
176 scalar_ok(static_cast<long>(actual.tensor_layout),
177 static_cast<long>(required.tensor_layout)) &&
178 scalar_ok(static_cast<long>(actual.callback_lifetime),
179 static_cast<long>(required.callback_lifetime));
180}
181
182bool abi_accepts_gradient(std::uint32_t major, std::uint32_t expected_major,
183 std::uint64_t features) {
184 return major == expected_major && (features & kFeatureGradient) != 0;
185}
186
187const std::string &provenance() { return provenance_slot(); }
188
189State *bind(ObjectiveFunction *objective, Eigen::VectorXd *cached) {
190#ifndef WITH_EINDIR
191 (void)objective;
192 (void)cached;
193 return nullptr;
194#else
195 if (objective == nullptr || objective->degreesOfFreedom() <= 0) {
196 return nullptr;
197 }
198 auto state = std::make_unique<State>();
199 state->objective = objective;
200 state->cached = cached;
201 const auto dim = static_cast<std::size_t>(objective->degreesOfFreedom());
202 const double inf = std::numeric_limits<double>::infinity();
203 state->low.assign(dim, -inf);
204 state->high.assign(dim, inf);
205 View actual = eon_requirement();
206 actual.producer_id = "eon.objective";
207 state->schema = actual.schema_id;
208 state->producer = actual.producer_id;
209 state->length_unit = actual.length_unit;
210 state->energy_unit = actual.energy_unit;
211 state->desc.schema_id = state->schema.c_str();
212 state->desc.producer_id = state->producer.c_str();
213 state->desc.length_unit = state->length_unit.c_str();
214 state->desc.energy_unit = state->energy_unit.c_str();
215 state->desc.energy_sign = actual.energy_sign;
216 state->desc.gradient_sign = actual.gradient_sign;
217 state->desc.operations = actual.operations;
218 state->desc.tensor_device_type = actual.tensor_device_type;
219 state->desc.tensor_dtype_code = actual.tensor_dtype_code;
220 state->desc.tensor_dtype_bits =
221 static_cast<std::uint8_t>(actual.tensor_dtype_bits);
222 state->desc.tensor_dtype_lanes =
223 static_cast<std::uint8_t>(actual.tensor_dtype_lanes);
224 state->desc.tensor_layout = actual.tensor_layout;
225 state->desc.callback_lifetime = actual.callback_lifetime;
226 state->obj.dim = dim;
227 state->obj.low = state->low.data();
228 state->obj.high = state->high.data();
229 state->obj.eval_fn = eval_cb;
230 state->obj.grad_fn = grad_cb;
231 state->obj.user_data = state.get();
232 state->obj.free_fn = nullptr;
233 state->obj.descriptor = &state->desc;
234 if (!compatible(actual, eon_requirement())) {
235 throw std::runtime_error("eOn eindir objective descriptor was rejected");
236 }
237 const auto stamp = eindir_core_abi_stamp();
238 if (eindir_core_abi_compatible(&stamp) == 0 ||
239 !abi_accepts_gradient(stamp.abi_major, 1, stamp.features)) {
240 throw std::runtime_error("incompatible eindir ABI stamp");
241 }
242 if (eindir_objective_has_grad(&state->obj) == 0) {
243 throw std::runtime_error("eindir objective has no gradient");
244 }
245 eindir_objective_descriptor_t required_desc = state->desc;
246 required_desc.producer_id = "";
247 if (eindir_objective_descriptor_compatible(&state->desc, &required_desc) ==
248 0) {
249 const char *err = eindir_last_error();
250 throw std::runtime_error(err != nullptr ? err
251 : "eindir descriptor mismatch");
252 }
253 const auto xts = xts_abi_stamp();
254 provenance_slot() =
255 std::format("xts-{}.{}.{}+eindir-{}.{}-layout-{}", xts.abi_major,
256 xts.abi_minor, xts.layout_revision, stamp.abi_major,
257 stamp.abi_minor, stamp.objective_layout);
258 return state.release();
259#endif
260}
261
262void release(State *state) {
263#ifdef WITH_EINDIR
264 delete state;
265#else
266 (void)state;
267#endif
268}
269
270int eval_grad(State *state, const DLManagedTensorVersioned *x, double *value,
271 DLManagedTensorVersioned *gradient) {
272#ifndef WITH_EINDIR
273 (void)state;
274 (void)x;
275 (void)value;
276 (void)gradient;
277 return 2;
278#else
279 if (state == nullptr) {
280 return 1;
281 }
282 if (eindir_objective_eval(&state->obj, x, value) != EINDIR_SUCCESS ||
283 eindir_objective_grad(&state->obj, x, gradient) != EINDIR_SUCCESS) {
284 return 2;
285 }
286 return 0;
287#endif
288}
289
290int minimize(State *state, double *x, std::size_t n, std::size_t maxiter,
291 double gtol, double istep, std::size_t memory, int method,
292 double *value_out) {
293#ifndef WITH_EINDIR
294 (void)state;
295 (void)x;
296 (void)n;
297 (void)maxiter;
298 (void)gtol;
299 (void)istep;
300 (void)memory;
301 (void)method;
302 (void)value_out;
303 return 1;
304#else
305 if (state == nullptr || x == nullptr) {
306 return 1;
307 }
308 auto *tensor = xts_tensor_borrow_cpu_f64(x, n);
309 if (tensor == nullptr) {
310 return 2;
311 }
312 const auto stamp = eindir_core_abi_stamp();
313 xts_control_t control{maxiter, gtol, istep, memory, 0.0};
314 xts_report_t report{};
315 const auto status =
316 xts_minimize_eindir(&state->obj, &stamp, tensor, &control,
317 static_cast<xts_method_t>(method), &report);
318 xts_tensor_free(tensor);
319 if (value_out != nullptr) {
320 *value_out = report.value;
321 }
322 return static_cast<int>(status);
323#endif
324}
325
326} // namespace eonc::xtsci_eindir
virtual int degreesOfFreedom()=0
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.
constexpr int kDtypeFloat
Definition XtsciEindir.h:46
constexpr int kDeviceCpu
Definition XtsciEindir.h:45
void release(State *state)
State * bind(ObjectiveFunction *objective, Eigen::VectorXd *cached)
Borrow an ObjectiveFunction as an eindir objective.
bool abi_accepts_gradient(std::uint32_t major, std::uint32_t expected_major, std::uint64_t features)
Major must match and the gradient feature bit must be set.
constexpr const char * kSchemaId
Definition XtsciEindir.h:42
int eval_grad(State *state, const DLManagedTensorVersioned *x, double *value, DLManagedTensorVersioned *gradient)
One fused energy and gradient through the borrowed eindir handle.
View eon_requirement()
What an eOn minimization may consume: eV, angstrom, analytic forces.
constexpr std::uint64_t kOpEnergy
Definition XtsciEindir.h:43
constexpr std::uint64_t kOpForces
Definition XtsciEindir.h:44
constexpr std::uint32_t kLifetimeBorrowed
Definition XtsciEindir.h:48
View rgpot_ev_angstrom()
rgpot producer advertising the same units and tensor contract.
const std::string & provenance()
Stamp text from the last successful eindir bind. Empty when unused.
bool compatible(const View &actual, const View &required)
Nonzero when actual satisfies required. Schema id is never a wildcard.
constexpr std::uint64_t kFeatureGradient
Definition XtsciEindir.h:49
constexpr std::uint32_t kLayoutContiguous
Definition XtsciEindir.h:47
Semantic objective contract shared with eindir_objective_descriptor_t.
Definition XtsciEindir.h:26
std::uint64_t operations
Definition XtsciEindir.h:33
std::uint32_t callback_lifetime
Definition XtsciEindir.h:39
std::uint32_t tensor_layout
Definition XtsciEindir.h:38