Skip to content

Commit

Permalink
Merge pull request #34 from heal-research/math-backends
Browse files Browse the repository at this point in the history
Arithmetic backends
  • Loading branch information
foolnotion authored Feb 4, 2024
2 parents 1db1fbc + e652980 commit a20685b
Show file tree
Hide file tree
Showing 46 changed files with 4,810 additions and 595 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.vs/
.vscode/
.cache/
build/
build*/
cmake/open-cpp-coverage.cmake
cmake-build-*/
prefix/
Expand Down
31 changes: 31 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ find_package(cpp-sort REQUIRED)
find_package(eve REQUIRED)
find_package(fmt REQUIRED)
find_package(lbfgs REQUIRED)
find_package(mdspan REQUIRED)
find_package(outcome REQUIRED)
find_package(pratt-parser REQUIRED)
find_package(unordered_dense REQUIRED)
Expand Down Expand Up @@ -133,6 +134,35 @@ if (USE_JEMALLOC)
endif()
endif()

if(NOT MATH_BACKEND)
set(MATH_BACKEND "Eigen")
endif()

message(STATUS "MATH: ${MATH_BACKEND}")

if (MATH_BACKEND STREQUAL "Fastor")
find_package(Fastor REQUIRED)
target_link_libraries(operon_operon PUBLIC Fastor::Fastor)
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_FASTOR)
elseif (MATH_BACKEND STREQUAL "Eve")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_EVE)
elseif (MATH_BACKEND STREQUAL "Vdt")
find_package(vdt REQUIRED)
target_link_libraries(operon_operon PUBLIC vdt::vdt)
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_VDT)
elseif (MATH_BACKEND STREQUAL "Eigen")
message(STATUS "Using Eigen backend")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_EIGEN)
elseif(MATH_BACKEND STREQUAL "Stl")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_STL)
elseif (MATH_BACKEND STREQUAL "Fast_v1")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_FAST_V1)
elseif (MATH_BACKEND STREQUAL "Fast_v2")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_FAST_V2)
elseif (MATH_BACKEND STREQUAL "Fast_v3")
target_compile_definitions(operon_operon PUBLIC OPERON_MATH_FAST_V3)
endif()

# print summary of enabled/disabled features
feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:" QUIET_ON_EMPTY)
feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:" QUIET_ON_EMPTY)
Expand Down Expand Up @@ -195,6 +225,7 @@ target_link_libraries(operon_operon PUBLIC
Eigen3::Eigen
Threads::Threads
fmt::fmt
std::mdspan
pratt-parser::pratt-parser # required by infix parser
vstat::vstat
lbfgs::lbfgs
Expand Down
13 changes: 8 additions & 5 deletions cmake/variables.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ if(PROJECT_IS_TOP_LEVEL)
set(JEMALLOC_DESCRIPTION "Link against jemalloc, a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support [default=OFF].")
set(USE_SINGLE_PRECISION_DESCRIPTION "Perform model evaluation using floats (single precision) instead of doubles. Great for reducing runtime, might not be appropriate for all purposes [default=OFF].")
set(USE_CERES_DESCRIPTION "Use the non-linear least squares optimizer from Ceres solver to tune model coefficients (if OFF, Eigen::LevenbergMarquardt will be used instead).")
set(MATH_BACKEND_DESCRIPTION "Math library for tree evaluation (defaults to Eigen)")

# option descriptions
option(USE_JEMALLOC ${JEMALLOC_DESCRIPTION} OFF)
option(USE_SINGLE_PRECISION ${USE_SINGLE_PRECISION_DESCRIPTION} ON)
option(USE_CERES ${USE_CERES_DESCRIPTION} OFF)
option(USE_SINGLE_PRECISION ${USE_SINGLE_PRECISION_DESCRIPTION} ON)
option(USE_CERES ${USE_CERES_DESCRIPTION} OFF)
option(MATH_BACKEND ${MATH_BACKEND_DESCRIPTION} "Eigen")

# provide a summary of configured options
include(FeatureSummary)
add_feature_info(USE_JEMALLOC USE_JEMALLOC ${JEMALLOC_DESCRIPTION})
add_feature_info(USE_SINGLE_PRECISION USE_SINGLE_PRECISION ${USE_SINGLE_PRECISION_DESCRIPTION})
add_feature_info(USE_CERES USE_CERES ${USE_CERES_DESCRIPTION})
add_feature_info(USE_JEMALLOC USE_JEMALLOC ${JEMALLOC_DESCRIPTION})
add_feature_info(USE_SINGLE_PRECISION USE_SINGLE_PRECISION ${USE_SINGLE_PRECISION_DESCRIPTION})
add_feature_info(USE_CERES USE_CERES ${USE_CERES_DESCRIPTION})
add_feature_info(MATH_BACKEND MATH_BACKEND_DESCRIPTION ${MATH_BACKEND_DESCRIPTION})
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
if(CMAKE_EXPORT_COMPILE_COMMANDS)
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
Expand Down
8 changes: 7 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
lbfgs.url = "github:foolnotion/lbfgs";
pratt-parser.url = "github:foolnotion/pratt-parser-calculator";
vstat.url = "github:heal-research/vstat/cpp20-eve";
vdt.url = "github:foolnotion/vdt/master";

# make everything follow nixpkgs
foolnotion.inputs.nixpkgs.follows = "nixpkgs";
lbfgs.inputs.nixpkgs.follows = "nixpkgs";
pratt-parser.inputs.nixpkgs.follows = "nixpkgs";
vstat.inputs.nixpkgs.follows = "nixpkgs";
vdt.inputs.nixpkgs.follows = "nixpkgs";
};

outputs = { self, flake-utils, nixpkgs, foolnotion, pratt-parser, vstat, lbfgs }:
outputs = { self, flake-utils, nixpkgs, foolnotion, pratt-parser, vdt, vstat, lbfgs }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
Expand Down Expand Up @@ -49,19 +51,23 @@
eigen
eve
fast_float
fastor
fmt
icu
jemalloc
mdspan
pkg-config
pratt-parser.packages.${system}.default
scnlib
taskflow
unordered_dense
vdt.packages.${system}.default
vstat.packages.${system}.default
lbfgs.packages.${system}.default
ned14-outcome
ned14-quickcpplib
ned14-status-code
xad
xxHash
]);
};
Expand Down
3 changes: 2 additions & 1 deletion include/operon/core/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <cstddef>
#include <type_traits>
#include "types.hpp"
#include <bitset>

namespace Operon {
enum class NodeType : uint32_t {
Expand Down Expand Up @@ -64,6 +63,8 @@ struct NodeTypes {
{
return std::countr_zero(static_cast<uint32_t>(type));
}

static auto constexpr NoType{NodeType{123456}};
};

inline constexpr auto operator&(NodeType lhs, NodeType rhs) -> NodeType { return static_cast<NodeType>(static_cast<UnderlyingNodeType>(lhs) & static_cast<UnderlyingNodeType>(rhs)); }
Expand Down
22 changes: 22 additions & 0 deletions include/operon/interpreter/backend/backend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef OPERON_BACKEND_HPP
#define OPERON_BACKEND_HPP

#include "operon/mdspan/mdspan.hpp"
#include "operon/core/types.hpp"

namespace Operon::Backend {
template<typename T>
static auto constexpr BatchSize = 512UL / sizeof(T);

static auto constexpr DefaultAlignment = 32UL;

template<typename T, std::size_t S = BatchSize<T>>
using View = std::mdspan<T, std::extents<int, S, std::dynamic_extent>, std::layout_left>;

template<typename T, std::size_t S>
auto Ptr(View<T, S> view, std::integral auto col) -> Backend::View<T, S>::element_type* {
return view.data_handle() + col * S;
}
} // namespace Operon::Backend

#endif
9 changes: 9 additions & 0 deletions include/operon/interpreter/backend/eigen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2019-2024 Heal Research

#ifndef OPERON_BACKEND_EIGEN_HPP
#define OPERON_BACKEND_EIGEN_HPP

#include "eigen/derivatives.hpp"

#endif
188 changes: 188 additions & 0 deletions include/operon/interpreter/backend/eigen/derivatives.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2019-2023 Heal Research

#ifndef OPERON_BACKEND_EIGEN_DERIVATIVES_HPP
#define OPERON_BACKEND_EIGEN_DERIVATIVES_HPP

#include "operon/core/node.hpp"
#include "functions.hpp"

namespace Operon::Backend {
namespace detail {
template<typename T>
inline auto IsNaN(T value) { return std::isnan(value); }

template<typename Compare>
struct FComp {
auto operator()(auto x, auto y) const {
using T = std::common_type_t<decltype(x), decltype(y)>;
if ((IsNaN(x) && IsNaN(y)) || (x == y)) {
return std::numeric_limits<T>::quiet_NaN();
}
if (IsNaN(x)) { return T{0}; }
if (IsNaN(y)) { return T{1}; }
return static_cast<T>(Compare{}(T{x}, T{y}));
}
};
} // namespace detail

template<typename T, std::size_t S>
auto Add(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> /*primal*/, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j).setConstant(T{1});
}

template<typename T, std::size_t S>
auto Mul(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
Col(trace, j) = Col(primal, i) / Col(primal, j);
}

template<typename T, std::size_t S>
auto Sub(std::vector<Operon::Node> const& nodes, Backend::View<T const, S> /*primal*/, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
auto v = (nodes[i].Arity == 1 || j < i-1) ? T{-1} : T{+1};
Col(trace, j).setConstant(v);
}

template<typename T, std::size_t S>
auto Div(std::vector<Operon::Node> const& nodes, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
auto const& n = nodes[i];
if (n.Arity == 1) {
Col(trace, j) = -Col(primal, j).square().inverse();
} else {
Col(trace, j) = (j == i-1 ? T{1} : T{-1}) * Col(primal, i) / Col(primal, j);
}
}

template<typename T, std::size_t S>
auto Aq(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
if (j == i-1) {
Col(trace, j) = Col(primal, i) / Col(primal, j);
} else {
Col(trace, j) = -Col(primal, j) * Col(primal, i).pow(T{3}) / Col(primal, i-1).square();
}
}

template<typename T, std::size_t S>
auto Pow(std::vector<Operon::Node> const& nodes, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
if (j == i-1) {
auto const k = j - (nodes[j].Length + 1);
Col(trace, j) = Col(primal, i) * Col(primal, k) / Col(primal, j);
} else {
auto const k = i-1;
Col(trace, j) = Col(primal, i) * Col(primal, k).log();
}
}

template<typename T, std::size_t S>
auto Min(std::vector<Operon::Node> const& nodes, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
auto k = j == i - 1 ? (j - nodes[j].Length - 1) : i - 1;
Col(trace, j) = Col(primal, j).binaryExpr(Col(primal, k), detail::FComp<std::less<>>{});
}

template<typename T, std::size_t S>
auto Max(std::vector<Operon::Node> const& nodes, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
auto k = j == i - 1 ? (j - nodes[j].Length - 1) : i - 1;
Col(trace, j) = Col(primal, j).binaryExpr(Col(primal, k), detail::FComp<std::greater<>>{});
}

template<typename T, std::size_t S>
auto Square(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = T{2} * Col(primal, j);
}

template<typename T, std::size_t S>
auto Abs(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).sign();
}

template<typename T, std::size_t S>
auto Ceil(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).ceil();
}

template<typename T, std::size_t S>
auto Floor(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).floor();
}

template<typename T, std::size_t S>
auto Exp(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
Col(trace, j) = Col(primal, i);
}

template<typename T, std::size_t S>
auto Log(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).inverse();
}

template<typename T, std::size_t S>
auto Log1p(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = (T{1} + Col(primal, j)).inverse();
}

template<typename T, std::size_t S>
auto Logabs(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).sign() / Col(primal, j).abs();
}

template<typename T, std::size_t S>
auto Sin(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).cos();
}

template<typename T, std::size_t S>
auto Cos(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = -Col(primal, j).sin();
}

template<typename T, std::size_t S>
auto Tan(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = T{1} + Col(primal, j).tan().square();
}

template<typename T, std::size_t S>
auto Sinh(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).cosh();
}

template<typename T, std::size_t S>
auto Cosh(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = Col(primal, j).sinh();
}

template<typename T, std::size_t S>
auto Tanh(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = T{1} - Col(primal, j).tanh().square();
}

template<typename T, std::size_t S>
auto Asin(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = (T{1} - Col(primal, j).square()).sqrt().inverse();
}

template<typename T, std::size_t S>
auto Acos(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = -((T{1} - Col(primal, j).square()).sqrt().inverse());
}

template<typename T, std::size_t S>
auto Atan(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto /*i*/, std::integral auto j) {
Col(trace, j) = (T{1} + Col(primal, j).square()).inverse();
}

template<typename T, std::size_t S>
auto Sqrt(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
Col(trace, j) = (T{2} * Col(primal, i)).inverse();
}

template<typename T, std::size_t S>
auto Sqrtabs(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
Col(trace, j) = Col(primal, j).sign() / (T{2} * Col(primal, i));
}

template<typename T, std::size_t S>
auto Cbrt(std::vector<Operon::Node> const& /*nodes*/, Backend::View<T const, S> primal, Backend::View<T> trace, std::integral auto i, std::integral auto j) {
Col(trace, j) = (T{3} * Col(primal, i).square()).inverse();
}
} // namespace Operon::Backend

#endif
Loading

0 comments on commit a20685b

Please sign in to comment.