Skip to content

Commit

Permalink
Merge branch 'main' into ex-refactor-detectors
Browse files Browse the repository at this point in the history
  • Loading branch information
andiwand authored Dec 7, 2024
2 parents 1755a30 + 9067679 commit 6b561c2
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 3 deletions.
5 changes: 4 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ test_exatrkx_unittests:

- apt-get update -y
- git clone $CLONE_URL src
- source src/CI/dependencies.sh
- cd src
- git checkout $HEAD_SHA
- source CI/dependencies.sh
- cd ..
- ctest --test-dir build -R ExaTrkX

test_exatrkx_python:
Expand Down
1 change: 0 additions & 1 deletion CI/codespell_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ coner
dthe
iself
sortings
fime
gaus
te
parm
Expand Down
141 changes: 141 additions & 0 deletions Core/include/Acts/Utilities/Result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Result {
Result(std::variant<T, E>&& var) : m_var(std::move(var)) {}

public:
using ValueType = T;
using ErrorType = E;

/// Default construction is disallowed.
Result() = delete;

Expand Down Expand Up @@ -172,6 +175,144 @@ class Result {
return std::move(std::get<T>(m_var));
}

/// Retrieves the valid value from the result object, or returns a default
/// value if no valid value exists.
///
/// @param[in] v The default value to use if no valid value exists.
/// @note This is the lvalue version.
/// @note This function always returns by value.
/// @return Either the valid value, or the given substitute.
template <typename U>
std::conditional_t<std::is_reference_v<U>, const T&, T> value_or(U&& v) const&
requires(std::same_as<std::decay_t<U>, T>)
{
if (ok()) {
return value();
} else {
return std::forward<U>(v);
}
}

/// Retrieves the valid value from the result object, or returns a default
/// value if no valid value exists.
///
/// @param[in] v The default value to use if no valid value exists.
/// @note This is the rvalue version which moves the value out.
/// @note This function always returns by value.
/// @return Either the valid value, or the given substitute.
template <typename U>
T value_or(U&& v) &&
requires(std::same_as<std::decay_t<U>, T>)
{
if (ok()) {
return std::move(*this).value();
} else {
return std::forward<U>(v);
}
}

/// Transforms the value contained in this result.
///
/// Applying a function `f` to a valid value `x` returns `f(x)`, while
/// applying `f` to an invalid value returns another invalid value.
///
/// @param[in] callable The transformation function to apply.
/// @note This is the lvalue version.
/// @note This functions is `fmap` on the functor in `A` of `Result<A, E>`.
/// @return The modified valid value if exists, or an error otherwise.
template <typename C>
auto transform(C&& callable) const&
requires std::invocable<C, const T&>
{
using CallableReturnType = decltype(std::declval<C>()(std::declval<T>()));
using R = Result<std::decay_t<CallableReturnType>, E>;
if (ok()) {
return R::success(callable(value()));
} else {
return R::failure(error());
}
}

/// Transforms the value contained in this result.
///
/// Applying a function `f` to a valid value `x` returns `f(x)`, while
/// applying `f` to an invalid value returns another invalid value.
///
/// @param[in] callable The transformation function to apply.
/// @note This is the rvalue version.
/// @note This functions is `fmap` on the functor in `A` of `Result<A, E>`.
/// @return The modified valid value if exists, or an error otherwise.
template <typename C>
auto transform(C&& callable) &&
requires std::invocable<C, T&&>
{
using CallableReturnType = decltype(std::declval<C>()(std::declval<T>()));
using R = Result<std::decay_t<CallableReturnType>, E>;
if (ok()) {
return R::success(callable(std::move(*this).value()));
} else {
return R::failure(std::move(*this).error());
}
}

/// Bind a function to this result monadically.
///
/// This function takes a function `f` and, if this result contains a valid
/// value `x`, returns `f(x)`. If the type of `x` is `T`, then `f` is
/// expected to accept type `T` and return `Result<U>`. In this case,
/// `transform` would return the unhelpful type `Result<Result<U>>`, so
/// `and_then` strips away the outer layer to return `Result<U>`. If the
/// value is invalid, this returns an invalid value in `Result<U>`.
///
/// @param[in] callable The transformation function to apply.
/// @note This is the lvalue version.
/// @note This functions is `>>=` on the functor in `A` of `Result<A, E>`.
/// @return The modified valid value if exists, or an error otherwise.
template <typename C>
auto and_then(C&& callable) const&
requires std::invocable<C, const T&>
{
using R = decltype(std::declval<C>()(std::declval<T>()));

static_assert(std::same_as<typename R::ErrorType, ErrorType>,
"bind must take a callable with the same error type");

if (ok()) {
return callable(value());
} else {
return R::failure(error());
}
}

/// Bind a function to this result monadically.
///
/// This function takes a function `f` and, if this result contains a valid
/// value `x`, returns `f(x)`. If the type of `x` is `T`, then `f` is
/// expected to accept type `T` and return `Result<U>`. In this case,
/// `transform` would return the unhelpful type `Result<Result<U>>`, so
/// `and_then` strips away the outer layer to return `Result<U>`. If the
/// value is invalid, this returns an invalid value in `Result<U>`.
///
/// @param[in] callable The transformation function to apply.
/// @note This is the rvalue version.
/// @note This functions is `>>=` on the functor in `A` of `Result<A, E>`.
/// @return The modified valid value if exists, or an error otherwise.
template <typename C>
auto and_then(C&& callable) &&
requires std::invocable<C, T&&>
{
using R = decltype(std::declval<C>()(std::declval<T>()));

static_assert(std::same_as<typename R::ErrorType, ErrorType>,
"bind must take a callable with the same error type");

if (ok()) {
return callable(std::move(*this).value());
} else {
return R::failure(std::move(*this).error());
}
}

private:
std::variant<T, E> m_var;

Expand Down
2 changes: 1 addition & 1 deletion Examples/Scripts/vertex_mu_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def main(files: List[Path], output: str, title: str = ""):
if time_file.exists():
time = numpy.append(time, float(time_file.read_text()))
else:
fime.append(float("nan"))
time.append(float("nan"))

rf = uproot.open(f"{file}:vertexing")

Expand Down
88 changes: 88 additions & 0 deletions Tests/UnitTests/Core/Utilities/ResultTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,94 @@ BOOST_AUTO_TEST_CASE(BoolResult) {
BOOST_CHECK_EQUAL(res.error(), MyError::Failure);
}

BOOST_AUTO_TEST_CASE(ValueOrResult) {
using Result = Result<int>;

Result res = Result::success(5);
BOOST_CHECK_EQUAL(res.value_or(42), 5);

res = Result::failure(MyError::Failure);
BOOST_CHECK_EQUAL(res.value_or(42), 42);

BOOST_CHECK_EQUAL(Result::success(5).value_or(42), 5);
BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(42), 42);

int val = 25;
const int cval = 30;

BOOST_CHECK_EQUAL(Result::success(5).value_or(val), 5);
BOOST_CHECK_EQUAL(Result::success(5).value_or(cval), 5);
BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(val), 25);
BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(cval), 30);

res = Result::success(5);

BOOST_CHECK_EQUAL(res.value_or(val), 5);
BOOST_CHECK_EQUAL(&(res.value_or(val)), &res.value());
BOOST_CHECK_EQUAL(res.value_or(cval), 5);
BOOST_CHECK_EQUAL(&(res.value_or(cval)), &res.value());

res = Result::failure(MyError::Failure);

BOOST_CHECK_EQUAL(res.value_or(val), 25);
BOOST_CHECK_EQUAL(res.value_or(cval), 30);
BOOST_CHECK_EQUAL(&(res.value_or(val)), &val);
BOOST_CHECK_EQUAL(&(res.value_or(cval)), &cval);
}

BOOST_AUTO_TEST_CASE(TransformResult) {
using Result = Result<int>;

auto f1 = [](int x) { return 2 * x; };

Result res = Result::success(5);
Result res2 = res.transform(f1);
BOOST_CHECK(res2.ok());
BOOST_CHECK_EQUAL(*res2, 10);

res = Result::failure(MyError::Failure);
res2 = res.transform(f1);
BOOST_CHECK(!res2.ok());

BOOST_CHECK(Result::success(5).transform(f1).ok());
BOOST_CHECK_EQUAL(Result::success(5).transform(f1).value(), 10);

BOOST_CHECK(!Result::failure(MyError::Failure).transform(f1).ok());
}

BOOST_AUTO_TEST_CASE(AndThenResult) {
using Result1 = Result<int>;
using Result2 = Result<std::string>;

auto f1 = [](int x) -> Result2 {
return Result2::success("hello " + std::to_string(x));
};
auto f2 = [](int) -> Result2 { return Result2::failure(MyError::Failure); };

Result1 res = Result1::success(5);
Result2 res2 = res.and_then(f1);
BOOST_CHECK(res2.ok());
BOOST_CHECK_EQUAL(*res2, "hello 5");

res2 = res.and_then(f2);
BOOST_CHECK(!res2.ok());

res = Result1::failure(MyError::Failure);
res2 = res.and_then(f1);
BOOST_CHECK(!res2.ok());

res2 = res.and_then(f2);
BOOST_CHECK(!res2.ok());

BOOST_CHECK(Result1::success(5).and_then(f1).ok());
BOOST_CHECK_EQUAL(Result1::success(5).and_then(f1).value(), "hello 5");

BOOST_CHECK(!Result1::success(5).and_then(f2).ok());

BOOST_CHECK(!Result1::failure(MyError::Failure).and_then(f1).ok());

BOOST_CHECK(!Result1::failure(MyError::Failure).and_then(f2).ok());
}
BOOST_AUTO_TEST_SUITE_END()

} // namespace Acts::Test

0 comments on commit 6b561c2

Please sign in to comment.