Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/core/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ WIP
:maxdepth: 2

reference/c/index
reference/cxx/index
reference/json-formats
units

Expand Down
17 changes: 17 additions & 0 deletions docs/src/core/reference/cxx/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.. _cxx-api-core:

C++ API reference
=================

WIP

The functions and types provided in ``metatomic.hpp`` can be grouped in four
main groups:

.. toctree::
:maxdepth: 1

system
model
plugin
misc
14 changes: 14 additions & 0 deletions docs/src/core/reference/cxx/misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Miscellaneous
=============


Error handling
^^^^^^^^^^^^^^

.. doxygenclass:: metatomic::Error


Unit conversion
^^^^^^^^^^^^^^^

.. doxygenfunction:: metatomic::unit_conversion_factor
2 changes: 2 additions & 0 deletions docs/src/core/reference/cxx/model.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Model
=====
2 changes: 2 additions & 0 deletions docs/src/core/reference/cxx/plugin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Plugin system
=============
2 changes: 2 additions & 0 deletions docs/src/core/reference/cxx/system.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
System
======
20 changes: 12 additions & 8 deletions docs/src/core/units.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ Units
Models in metatensor can use arbitrary units for their inputs and outputs. The
unit conversion system allows models to specify the units they expect and
receive data in any compatible unit, with automatic conversion handled by
:c:func:`mta_execute_model`.
during model execution.

The :c:func:`mta_unit_conversion_factor` function parses two unit expressions,
checks that they have compatible physical dimensions, and returns the
multiplicative conversion factor:
Unit parsing is handled by one of the following functions:

.. code-block:: c
- :c:func:`mta_unit_conversion_factor` in C
- :cpp:func:`metatomic::unit_conversion_factor` in C++

These functions parses two unit expressions, checks that they have compatible
physical dimensions, and returns the multiplicative conversion factor. For
example, in C++:

.. code-block:: C++

// How many eV are in one kJ/mol?
double factor;
mta_unit_conversion_factor("kJ/mol", "eV", &factor);
double factor = metatomic::unit_conversion_factor("kJ/mol", "eV");
// factor ≈ 0.01036

// How many GPa are in one eV/A^3?
mta_unit_conversion_factor("eV/A^3", "GPa", &factor);
factor = metatomic::unit_conversion_factor("eV/A^3", "GPa");
// factor ≈ 160.22

If either (or both) unit strings are empty, the conversion returns ``1.0``
Expand Down
20 changes: 10 additions & 10 deletions metatomic-core/include/metatomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,18 @@ void mta_string_free(mta_string_t string);
const char *mta_string_view(mta_string_t string);

/**
* Get the multiplicative conversion factor to use to convert from
* `from_unit` to `to_unit`. Both units are parsed as expressions (e.g.
* "kJ/mol/A^2", "(eV*u)^(1/2)") and their dimensions must match.
* Get the multiplicative conversion factor to use to convert from `from_unit`
* to `to_unit`. Both units are parsed as expressions (e.g. `kJ / mol / A^2`,
* `(eV * u)^(1/2)`) and their dimensions must match.
*
* Unit expressions are built from base units combined with `*`, `/`, `^`,
* and parentheses. Unit lookup is case-insensitive, and whitespace is
* ignored. For example:
* @verbatim embed:rst:leading-asterisk
*
* - `"kJ/mol"` -- energy per mole
* - `"eV/Angstrom^3"` -- pressure
* - `"(eV*u)^(1/2)"` -- momentum (fractional powers)
* - `"Hartree/Bohr"` -- force in atomic units
* .. seealso::
*
* The general documentation for :ref:`units`, with the expression
* syntax and list of supported base units.
*
* @endverbatim
*
* @param from_unit A null-terminated C string containing the unit to convert from.
* @param to_unit A null-terminated C string containing the unit to convert to.
Expand Down
1 change: 1 addition & 0 deletions metatomic-core/include/metatomic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
#include "metatomic/system.hpp" // IWYU pragma: export
#include "metatomic/model.hpp" // IWYU pragma: export
#include "metatomic/plugin.hpp" // IWYU pragma: export
#include "metatomic/errors.hpp" // IWYU pragma: export
108 changes: 108 additions & 0 deletions metatomic-core/include/metatomic/errors.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#pragma once

#include <cstring>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <utility>

#include <metatomic.h>

namespace metatomic {

/// Exception class used for all errors in metatomic
class Error: public std::runtime_error {
public:
/// Create a new MetatomicError with the given `message`
Error(const std::string& message): std::runtime_error(message) {}
};

namespace details {
/// Check if a return status from the C API indicates an error, and if it is
/// the case, throw an exception of type `metatomic::Error` with the last
/// error message from the library.
inline void check_status(mta_status_t status) {
if (status == MTA_SUCCESS) {
return;
} else if (status == MTA_MODEL_NOT_SUPPORTED_ERROR) {
const char* message = nullptr;
const char* origin = nullptr;
void* data = nullptr;
mta_last_error(&message, &origin, &data);
if (origin != nullptr &&std::strcmp(origin, "C++ exception") == 0 && data != nullptr) {
std::rethrow_exception(*static_cast<std::exception_ptr*>(data));
} else {
throw Error(message == nullptr ? "unknown error" : message);
}
} else {
const char* message = nullptr;
mta_last_error(&message, nullptr, nullptr);
throw Error(message == nullptr ? "unknown error" : message);
}
}

/// Call the given `function` with the given `args` (the function should
/// return an `mta_status_t`), catching any C++ exception, and translating
/// them to native metatomic error code.
///
/// This is required to prevent callbacks unwinding through the C API.
template<typename Function, typename ...Args>
inline mta_status_t catch_exceptions(Function function, Args ...args) {
try {
function(std::move(args)...);
return MTA_SUCCESS;
} catch (...) {
auto* exception_ptr = new std::exception_ptr(std::current_exception());

const char* message = nullptr;
try {
std::rethrow_exception(*exception_ptr);
} catch (const std::exception& e) {
message = e.what();
} catch (...) {
message = "C++ code threw an exception that was not a std::exception";
}

auto status = mta_set_last_error(
message,
"C++ exception",
exception_ptr,
[](void *ptr) { delete static_cast<std::exception_ptr*>(ptr); }
);

if (status != MTA_SUCCESS) {
// If we failed to set the error, we are in a very bad state,
// but we should still try to report the original error
// message if possible.
std::fprintf(stderr, "INTERNAL ERROR: unable to set last error after C++ callback failure (status: %d). ", status);
if (message != nullptr) {
fprintf(stderr, "C++ error was: %s\n", message);
} else {
fprintf(stderr, "Unknown C++ error\n");
}
delete exception_ptr;
}

return MTA_MODEL_NOT_SUPPORTED_ERROR;
}
}

/// Check if a pointer allocated by the C API is null, and if it is the
/// case, throw an exception of type `metatomic::Error` with the last
/// error message from the library.
inline void check_pointer(const void* pointer) {
if (pointer == nullptr) {
const char* message = nullptr;
const char* origin = nullptr;
void* data = nullptr;
mta_last_error(&message, &origin, &data);
if (std::strcmp(origin, "C++ exception") == 0 && data != nullptr) {
std::rethrow_exception(*static_cast<std::exception_ptr*>(data));
} else {
throw Error(message);
}
}
}
} // namespace details

} // namespace metatomic
29 changes: 29 additions & 0 deletions metatomic-core/include/metatomic/utils.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
#pragma once

#include <string>

#include <metatomic.h>
#include <metatomic/errors.hpp>

namespace metatomic {
/// Get the multiplicative conversion factor to use to convert from
/// `from_unit` to `to_unit`. Both units are parsed as expressions
/// (e.g. `kJ / mol / A^2`, `(eV * u)^(1/2)`) and their dimensions must
/// match.
///
/// @verbatim embed:rst:leading-slashes
///
/// .. seealso::
///
/// The general documentation for :ref:`units`, with the expression
/// syntax and list of supported base units.
///
/// @endverbatim
///
/// @param from_unit the unit to convert from
/// @param to_unit the unit to convert to
inline double unit_conversion_factor(
const std::string& from_unit,
const std::string& to_unit
) {
double conversion = 0.0;

auto status = mta_unit_conversion_factor(from_unit.c_str(), to_unit.c_str(), &conversion);
details::check_status(status);

return conversion;
}
} // namespace metatomic
20 changes: 10 additions & 10 deletions metatomic-core/src/c_api/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,18 @@ pub unsafe extern "C" fn mta_string_view(
return result;
}

/// Get the multiplicative conversion factor to use to convert from
/// `from_unit` to `to_unit`. Both units are parsed as expressions (e.g.
/// "kJ/mol/A^2", "(eV*u)^(1/2)") and their dimensions must match.
/// Get the multiplicative conversion factor to use to convert from `from_unit`
/// to `to_unit`. Both units are parsed as expressions (e.g. `kJ / mol / A^2`,
/// `(eV * u)^(1/2)`) and their dimensions must match.
///
/// Unit expressions are built from base units combined with `*`, `/`, `^`,
/// and parentheses. Unit lookup is case-insensitive, and whitespace is
/// ignored. For example:
/// @verbatim embed:rst:leading-asterisk
///
/// - `"kJ/mol"` -- energy per mole
/// - `"eV/Angstrom^3"` -- pressure
/// - `"(eV*u)^(1/2)"` -- momentum (fractional powers)
/// - `"Hartree/Bohr"` -- force in atomic units
/// .. seealso::
///
/// The general documentation for :ref:`units`, with the expression
/// syntax and list of supported base units.
///
/// @endverbatim
///
/// @param from_unit A null-terminated C string containing the unit to convert from.
/// @param to_unit A null-terminated C string containing the unit to convert to.
Expand Down
69 changes: 46 additions & 23 deletions metatomic-core/tests/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <catch.hpp>

#include "metatomic.h"
#include "metatomic.hpp"


TEST_CASE("Version macros") {
Expand Down Expand Up @@ -48,30 +49,52 @@ TEST_CASE("mta_string_t") {
mta_string_free(nullptr);
}

TEST_CASE("mta_unit_conversion_factor") {
double factor = 0.0;

// same unit -> factor = 1.0
auto status = mta_unit_conversion_factor("m", "m", &factor);
REQUIRE(status == MTA_SUCCESS);
CHECK(factor == 1.0);

// kJ/mol -> eV
CHECK(mta_unit_conversion_factor("kJ/mol", "eV", &factor) == MTA_SUCCESS);
CHECK(factor == Approx(0.010364269656262174).epsilon(1e-15));

// dimension mismatch -> error
status = mta_unit_conversion_factor("m", "kg", &factor);
REQUIRE(status != MTA_SUCCESS);

const char* error_msg = nullptr;
mta_last_error(&error_msg, nullptr, nullptr);
CHECK(std::string(error_msg) ==
"invalid parameter: dimension mismatch in unit conversion: "
"'m' has dimension [L] but 'kg' has dimension [M]");
TEST_CASE("unit conversion factor") {
SECTION("C API") {
double factor = 0.0;

// same unit -> factor = 1.0
auto status = mta_unit_conversion_factor("m", "m", &factor);
REQUIRE(status == MTA_SUCCESS);
CHECK(factor == 1.0);

// kJ/mol -> eV
CHECK(mta_unit_conversion_factor("kJ/mol", "eV", &factor) == MTA_SUCCESS);
CHECK(factor == Approx(0.010364269656262174).epsilon(1e-15));

// dimension mismatch -> error
status = mta_unit_conversion_factor("m", "kg", &factor);
REQUIRE(status != MTA_SUCCESS);

const char* error_msg = nullptr;
mta_last_error(&error_msg, nullptr, nullptr);
CHECK(std::string(error_msg) ==
"invalid parameter: dimension mismatch in unit conversion: "
"'m' has dimension [L] but 'kg' has dimension [M]"
);
}

SECTION("C++ API") {
// same unit -> factor = 1.0
auto factor = metatomic::unit_conversion_factor("m", "m");
CHECK(factor == 1.0);

// kJ/mol -> eV
factor = metatomic::unit_conversion_factor("kJ/mol", "eV");
CHECK(factor == Approx(0.010364269656262174).epsilon(1e-15));

// dimension mismatch -> error
try{
factor = metatomic::unit_conversion_factor("m", "kg");
}
catch(metatomic::Error& e){
CHECK(std::string(e.what()) == "invalid parameter: dimension mismatch in unit conversion: 'm' has dimension [L] but 'kg' has dimension [M]");
}
}
}

TEST_CASE("mta_format_metadata") {

TEST_CASE("metatdata formatting") {
std::string json =R"({
"type": "metatomic_model_metadata",
"name": "name",
Expand All @@ -88,7 +111,7 @@ TEST_CASE("mta_format_metadata") {
REQUIRE(mta_string != nullptr);
auto status = mta_format_metadata(json.c_str(), &mta_string);
REQUIRE(status == MTA_SUCCESS);
const auto expected = R"(This is the name model
const auto* expected = R"(This is the name model
======================

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
Expand Down
Loading