Skip to content

Commit 5f94ac0

Browse files
authored
Merge pull request #67 from heal-research/feature/concepts
feat(concepts): add C++20 concepts for operators, metrics, and policy types
2 parents 9f051c1 + a4ed4bc commit 5f94ac0

9 files changed

Lines changed: 181 additions & 137 deletions

File tree

include/operon/core/concepts.hpp

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,87 @@
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: Copyright 2019-2023 Heal Research
3+
14
#ifndef OPERON_CONCEPTS_HPP
25
#define OPERON_CONCEPTS_HPP
36

47
#include <concepts>
8+
#include <cstddef>
9+
#include <string_view>
510
#include <type_traits>
611

12+
#include "types.hpp"
13+
14+
namespace Operon {
15+
class Tree;
16+
struct Individual;
17+
} // namespace Operon
18+
719
namespace Operon::Concepts {
8-
// T is an arithmetic number
9-
template<typename T>
10-
concept Arithmetic = std::is_arithmetic_v<T>;
11-
} // namespace Operon::Concepts
1220

13-
#endif
21+
template<typename T>
22+
concept Arithmetic = std::is_arithmetic_v<T>;
23+
24+
// Callable with two (or three) value spans, returns a scalar error measure.
25+
template<typename T>
26+
concept ErrorMetricCallable = requires(T t,
27+
Operon::Span<Operon::Scalar const> x,
28+
Operon::Span<Operon::Scalar const> y,
29+
Operon::Span<Operon::Scalar const> w) {
30+
{ t(x, y) } -> std::convertible_to<double>;
31+
{ t(x, y, w) } -> std::convertible_to<double>;
32+
};
33+
34+
// String-to-hash callable with transparent lookup support.
35+
template<typename T>
36+
concept Hasher = requires(T t, std::string_view sv) {
37+
typename T::is_transparent;
38+
{ t(sv) } -> std::convertible_to<Operon::Hash>;
39+
};
40+
41+
// Builds a new Tree given RNG + (targetLength, minDepth, maxDepth).
42+
template<typename T>
43+
concept Creator = requires(T const& t, Operon::RandomGenerator& rng,
44+
std::size_t a, std::size_t b, std::size_t c) {
45+
{ t(rng, a, b, c) } -> std::same_as<Operon::Tree>;
46+
};
1447

48+
// Mutates a Tree and returns the result.
49+
template<typename T>
50+
concept Mutator = requires(T const& t, Operon::RandomGenerator& rng, Operon::Tree tree) {
51+
{ t(rng, tree) } -> std::same_as<Operon::Tree>;
52+
};
1553

54+
// Recombines two parent Trees into a child Tree.
55+
template<typename T>
56+
concept Crossover = requires(T const& t, Operon::RandomGenerator& rng,
57+
Operon::Tree const& a, Operon::Tree const& b) {
58+
{ t(rng, a, b) } -> std::same_as<Operon::Tree>;
59+
};
60+
61+
// Selects an individual index from a pre-set population.
62+
template<typename T>
63+
concept Selector = requires(T const& t, Operon::RandomGenerator& rng) {
64+
{ t(rng) } -> std::convertible_to<std::size_t>;
65+
};
66+
67+
// Merges offspring into the parent population in-place.
68+
template<typename T>
69+
concept Reinserter = requires(T const& t, Operon::RandomGenerator& rng,
70+
Operon::Span<Operon::Individual> parents,
71+
Operon::Span<Operon::Individual> offspring) {
72+
{ t(rng, parents, offspring) } -> std::same_as<void>;
73+
};
74+
75+
// Evaluates an Individual and returns a fitness vector.
76+
// Both the buffered and unbuffered call forms are required.
77+
template<typename T>
78+
concept EvaluatorCallable = requires(T const& t, Operon::RandomGenerator& rng,
79+
Operon::Individual const& ind,
80+
Operon::Span<Operon::Scalar> buf) {
81+
{ t(rng, ind) } -> std::same_as<Operon::Vector<Operon::Scalar>>;
82+
{ t(rng, ind, buf) } -> std::same_as<Operon::Vector<Operon::Scalar>>;
83+
};
84+
85+
} // namespace Operon::Concepts
86+
87+
#endif

include/operon/error_metrics/correlation_coefficient.hpp

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,71 +5,72 @@
55
#define OPERON_METRICS_CORRELATION_COEFFICIENT_HPP
66

77
#include <iterator>
8-
#include <type_traits>
98
#include <vstat/vstat.hpp>
10-
#include "operon/core/types.hpp"
9+
#include "operon/core/concepts.hpp"
1110

1211
namespace Operon {
1312

14-
template<typename InputIt1, typename InputIt2>
13+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2>
14+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
15+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
16+
typename std::iterator_traits<InputIt2>::value_type>
1517
inline auto CorrelationCoefficient(InputIt1 begin1, InputIt1 end1, InputIt2 begin2) noexcept -> double
1618
{
1719
using V1 = typename std::iterator_traits<InputIt1>::value_type;
18-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
19-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
20-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
21-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
2220
return vstat::bivariate::accumulate<V1>(begin1, end1, begin2).correlation;
2321
}
2422

25-
template<typename InputIt1, typename InputIt2, typename InputIt3>
23+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2, std::contiguous_iterator InputIt3>
24+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
25+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
26+
typename std::iterator_traits<InputIt2>::value_type>
2627
inline auto CorrelationCoefficient(InputIt1 begin1, InputIt1 end1, InputIt2 begin2, InputIt3 begin3) noexcept -> double
2728
{
2829
using V1 = typename std::iterator_traits<InputIt1>::value_type;
29-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
30-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
31-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
32-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
3330
return vstat::bivariate::accumulate<V1>(begin1, end1, begin2, begin3).correlation;
3431
}
3532

36-
template<typename T>
33+
template<Concepts::Arithmetic T>
3734
inline auto CorrelationCoefficient(Operon::Span<T const> x, Operon::Span<T const> y) -> double
3835
{
39-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
4036
EXPECT(x.size() == y.size());
4137
EXPECT(!x.empty());
42-
return vstat::bivariate::accumulate<T>(x.begin(), x.end(), y.begin()).correlation;
38+
return vstat::bivariate::accumulate<T>(x.data(), x.data() + x.size(), y.data()).correlation;
4339
}
4440

45-
template<typename T>
41+
template<Concepts::Arithmetic T>
4642
inline auto CorrelationCoefficient(Operon::Span<T const> x, Operon::Span<T const> y, Operon::Span<T const> w) -> double
4743
{
48-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
4944
EXPECT(x.size() == y.size());
5045
EXPECT(!x.empty());
51-
return vstat::bivariate::accumulate<T>(x.begin(), x.end(), y.begin(), w.begin()).correlation;
46+
return vstat::bivariate::accumulate<T>(x.data(), x.data() + x.size(), y.data(), w.data()).correlation;
5247
}
5348

54-
template<typename InputIt1, typename InputIt2>
49+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2>
50+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
51+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
52+
typename std::iterator_traits<InputIt2>::value_type>
5553
inline auto SquaredCorrelation(InputIt1 begin1, InputIt1 end1, InputIt2 begin2) noexcept -> double {
5654
auto r = CorrelationCoefficient(begin1, end1, begin2);
5755
return r * r;
5856
}
5957

60-
template<typename InputIt1, typename InputIt2, typename InputIt3>
58+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2, std::contiguous_iterator InputIt3>
59+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
60+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
61+
typename std::iterator_traits<InputIt2>::value_type>
6162
inline auto SquaredCorrelation(InputIt1 begin1, InputIt1 end1, InputIt2 begin2, InputIt3 begin3) noexcept -> double {
6263
auto r = CorrelationCoefficient(begin1, end1, begin2, begin3);
6364
return r * r;
6465
}
6566

66-
template<typename T>
67+
template<Concepts::Arithmetic T>
6768
inline auto SquaredCorrelation(Operon::Span<T const> x, Operon::Span<T const> y) -> double {
6869
auto r = CorrelationCoefficient(x, y);
6970
return r * r;
7071
}
7172

72-
template<typename T>
73+
template<Concepts::Arithmetic T>
7374
inline auto SquaredCorrelation(Operon::Span<T const> x, Operon::Span<T const> y, Operon::Span<T const> w) -> double {
7475
auto r = CorrelationCoefficient(x, y, w);
7576
return r * r;

include/operon/error_metrics/mean_absolute_error.hpp

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,45 @@
55
#define OPERON_METRICS_MEAN_ABSOLUTE_ERROR_HPP
66

77
#include <iterator>
8-
#include <type_traits>
98
#include <vstat/vstat.hpp>
10-
#include "operon/core/types.hpp"
9+
#include "operon/core/concepts.hpp"
1110

1211
namespace Operon {
1312

14-
template<typename InputIt1, typename InputIt2>
13+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2>
14+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
15+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
16+
typename std::iterator_traits<InputIt2>::value_type>
1517
inline auto MeanAbsoluteError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2) noexcept -> double
1618
{
1719
using V1 = typename std::iterator_traits<InputIt1>::value_type;
18-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
19-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
20-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
21-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
22-
return vstat::univariate::accumulate<V1>(begin1, end1, begin2, [](auto a, auto b) { return std::abs(a-b); }).mean;
20+
return vstat::metrics::mean_absolute_error<V1>(begin1, end1, begin2);
2321
}
2422

25-
template<typename InputIt1, typename InputIt2, typename InputIt3>
23+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2, std::contiguous_iterator InputIt3>
24+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
25+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
26+
typename std::iterator_traits<InputIt2>::value_type>
2627
inline auto MeanAbsoluteError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2, InputIt3 begin3) noexcept -> double
2728
{
2829
using V1 = typename std::iterator_traits<InputIt1>::value_type;
29-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
30-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
31-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
32-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
33-
return vstat::univariate::accumulate<V1>(begin1, end1, begin2, begin3, [](auto a, auto b) { return std::abs(a-b); }).mean;
30+
return vstat::metrics::mean_absolute_error<V1>(begin1, end1, begin2, begin3);
3431
}
3532

36-
template<typename T>
33+
template<Concepts::Arithmetic T>
3734
inline auto MeanAbsoluteError(Operon::Span<T const> x, Operon::Span<T const> y) -> double
3835
{
39-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
4036
EXPECT(x.size() == y.size());
4137
EXPECT(!x.empty());
42-
return vstat::univariate::accumulate<T>(x.begin(), x.end(), y.begin(), [](auto a, auto b) { return std::abs(a-b); }).mean;
38+
return MeanAbsoluteError(x.data(), x.data() + x.size(), y.data());
4339
}
4440

45-
template<typename T>
41+
template<Concepts::Arithmetic T>
4642
inline auto MeanAbsoluteError(Operon::Span<T const> x, Operon::Span<T const> y, Operon::Span<T const> w) -> double
4743
{
48-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
4944
EXPECT(x.size() == y.size());
5045
EXPECT(!x.empty());
51-
return vstat::univariate::accumulate<T>(x.begin(), x.end(), y.begin(), w.begin(), [](auto a, auto b) { return std::abs(a-b); }).mean;
46+
return MeanAbsoluteError(x.data(), x.data() + x.size(), y.data(), w.data());
5247
}
5348

5449
} // namespace Operon

include/operon/error_metrics/mean_squared_error.hpp

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,45 @@
55
#define OPERON_METRICS_MEAN_SQUARED_ERROR_HPP
66

77
#include <iterator>
8-
#include <type_traits>
98
#include <vstat/vstat.hpp>
10-
11-
#include "operon/core/types.hpp"
9+
#include "operon/core/concepts.hpp"
1210

1311
namespace Operon {
1412

15-
template<typename InputIt1, typename InputIt2>
13+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2>
14+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
15+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
16+
typename std::iterator_traits<InputIt2>::value_type>
1617
inline auto MeanSquaredError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2) noexcept -> double
1718
{
1819
using V1 = typename std::iterator_traits<InputIt1>::value_type;
19-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
20-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
21-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
22-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
23-
auto sqres = [](auto a, auto b){ auto e = a-b; return e*e; };
24-
return vstat::univariate::accumulate<V1>(begin1, end1, begin2, sqres).mean;
20+
return vstat::metrics::mean_squared_error<V1>(begin1, end1, begin2);
2521
}
2622

27-
template<typename InputIt1, typename InputIt2, typename InputIt3>
23+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2, std::contiguous_iterator InputIt3>
24+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
25+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
26+
typename std::iterator_traits<InputIt2>::value_type>
2827
inline auto MeanSquaredError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2, InputIt3 begin3) noexcept -> double
2928
{
3029
using V1 = typename std::iterator_traits<InputIt1>::value_type;
31-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
32-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
33-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
34-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
35-
auto sqres = [](auto a, auto b){ auto e = a-b; return e*e; };
36-
return vstat::univariate::accumulate<V1>(begin1, end1, begin2, begin3, sqres).mean;
30+
return vstat::metrics::mean_squared_error<V1>(begin1, end1, begin2, begin3);
3731
}
3832

39-
template<typename T>
33+
template<Concepts::Arithmetic T>
4034
inline auto MeanSquaredError(Operon::Span<T const> x, Operon::Span<T const> y) noexcept -> double
4135
{
42-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
4336
EXPECT(x.size() == y.size());
4437
EXPECT(!x.empty());
45-
return MeanSquaredError(x.begin(), x.end(), y.begin());
38+
return MeanSquaredError(x.data(), x.data() + x.size(), y.data());
4639
}
4740

48-
template<typename T>
41+
template<Concepts::Arithmetic T>
4942
inline auto MeanSquaredError(Operon::Span<T const> x, Operon::Span<T const> y, Operon::Span<T const> w) noexcept -> double
5043
{
51-
static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type.");
5244
EXPECT(x.size() == y.size());
5345
EXPECT(!x.empty());
54-
return MeanSquaredError(x.begin(), x.end(), y.begin(), w.begin());
46+
return MeanSquaredError(x.data(), x.data() + x.size(), y.data(), w.data());
5547
}
5648

5749
} // namespace Operon

include/operon/error_metrics/normalized_mean_squared_error.hpp

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,52 @@
55
#define OPERON_METRICS_NORMALIZED_MEAN_SQUARED_ERROR_HPP
66

77
#include <iterator>
8-
#include <type_traits>
98
#include <vstat/vstat.hpp>
10-
#include "operon/core/types.hpp"
9+
#include "operon/core/concepts.hpp"
1110
#include "mean_squared_error.hpp"
1211

1312
namespace Operon {
1413

15-
template<typename InputIt1, typename InputIt2>
14+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2>
15+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
16+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
17+
typename std::iterator_traits<InputIt2>::value_type>
1618
inline auto NormalizedMeanSquaredError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2) noexcept -> double
1719
{
1820
using V1 = typename std::iterator_traits<InputIt1>::value_type;
19-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
20-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
21-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
22-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
2321
auto varY = vstat::univariate::accumulate<V1>(begin2, begin2 + std::distance(begin1, end1)).variance;
2422
if (varY > 0) {
2523
return MeanSquaredError(begin1, end1, begin2) / varY;
2624
}
2725
return 0.0;
2826
}
2927

30-
template<typename InputIt1, typename InputIt2, typename InputIt3>
28+
template<std::contiguous_iterator InputIt1, std::contiguous_iterator InputIt2, std::contiguous_iterator InputIt3>
29+
requires Concepts::Arithmetic<typename std::iterator_traits<InputIt1>::value_type>
30+
&& std::same_as<typename std::iterator_traits<InputIt1>::value_type,
31+
typename std::iterator_traits<InputIt2>::value_type>
3132
inline auto NormalizedMeanSquaredError(InputIt1 begin1, InputIt1 end1, InputIt2 begin2, InputIt3 begin3) noexcept -> double
3233
{
3334
using V1 = typename std::iterator_traits<InputIt1>::value_type;
34-
using V2 = typename std::iterator_traits<InputIt2>::value_type;
35-
static_assert(std::is_arithmetic_v<V1>, "InputIt1: value_type must be arithmetic.");
36-
static_assert(std::is_arithmetic_v<V2>, "InputIt2: value_type must be arithmetic.");
37-
static_assert(std::is_same_v<V1, V2>, "The types must be the same");
3835
auto varY = vstat::univariate::accumulate<V1>(begin2, begin2 + std::distance(begin1, end1), begin3).variance;
3936
if (varY > 0) {
4037
return MeanSquaredError(begin1, end1, begin2, begin3) / varY;
4138
}
4239
return 0.0;
4340
}
4441

45-
template<typename T>
42+
template<Concepts::Arithmetic T>
4643
inline auto NormalizedMeanSquaredError(Operon::Span<T const> x, Operon::Span<T const> y) noexcept -> double
4744
{
48-
return NormalizedMeanSquaredError(x.begin(), x.end(), y.begin());
45+
return NormalizedMeanSquaredError(x.data(), x.data() + x.size(), y.data());
4946
}
5047

51-
template<typename T>
48+
template<Concepts::Arithmetic T>
5249
inline auto NormalizedMeanSquaredError(Operon::Span<T const> x, Operon::Span<T const> y, Operon::Span<T const> w) noexcept -> double
5350
{
54-
return NormalizedMeanSquaredError(x.begin(), x.end(), y.begin(), w.begin());
51+
return NormalizedMeanSquaredError(x.data(), x.data() + x.size(), y.data(), w.data());
5552
}
53+
5654
} // namespace Operon
5755

5856
#endif

0 commit comments

Comments
 (0)