limes 3.1.0
Composable Calculus Expressions for C++20
Loading...
Searching...
No Matches
binary.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <span>
4#include <string>
5#include <cstddef>
6#include <algorithm>
7#include "../concepts.hpp"
8
9namespace limes::expr {
10
11// Forward declaration of Integral type trait (defined in integral.hpp).
12// Used to exclude Integral types from generic operator* to avoid ambiguity.
13template<typename T>
14struct is_integral : std::false_type {};
15
16// Operation tags for binary operations
17struct Add {};
18struct Sub {};
19struct Mul {};
20struct Div {};
21
22// Binary<Op, L, R>: A binary operation node combining two child expressions.
23// Arity is max of children's arities.
24template<typename Op, typename L, typename R>
25struct Binary {
26 using value_type = typename L::value_type;
27 using op_type = Op;
28 using left_type = L;
29 using right_type = R;
30
31 static constexpr std::size_t arity_v = std::max(L::arity_v, R::arity_v);
32
35
36 constexpr Binary(L l, R r) noexcept : left{l}, right{r} {}
37
38 [[nodiscard]] constexpr value_type eval(std::span<value_type const> args) const {
39 value_type l_val = left.eval(args);
40 value_type r_val = right.eval(args);
41
42 if constexpr (std::is_same_v<Op, Add>) {
43 return l_val + r_val;
44 } else if constexpr (std::is_same_v<Op, Sub>) {
45 return l_val - r_val;
46 } else if constexpr (std::is_same_v<Op, Mul>) {
47 return l_val * r_val;
48 } else if constexpr (std::is_same_v<Op, Div>) {
49 return l_val / r_val;
50 }
51 }
52
53 [[nodiscard]] [[deprecated("use eval() instead")]]
54 constexpr value_type evaluate(std::span<value_type const> args) const {
55 return eval(args);
56 }
57
58 // Derivative via sum/difference, product, and quotient rules.
59 // Uses operator overloads for automatic simplification with Zero/One types.
60 template<std::size_t Dim>
61 [[nodiscard]] constexpr auto derivative() const {
62 auto dl = left.template derivative<Dim>();
63 auto dr = right.template derivative<Dim>();
64
65 if constexpr (std::is_same_v<Op, Add>) {
66 return dl + dr;
67 } else if constexpr (std::is_same_v<Op, Sub>) {
68 return dl - dr;
69 } else if constexpr (std::is_same_v<Op, Mul>) {
70 return (dl * right) + (left * dr);
71 } else if constexpr (std::is_same_v<Op, Div>) {
72 auto numer = (dl * right) - (left * dr);
73 auto denom = right * right;
74 return numer / denom;
75 }
76 }
77
78 [[nodiscard]] std::string to_string() const {
79 std::string op_str;
80 if constexpr (std::is_same_v<Op, Add>) {
81 op_str = "+";
82 } else if constexpr (std::is_same_v<Op, Sub>) {
83 op_str = "-";
84 } else if constexpr (std::is_same_v<Op, Mul>) {
85 op_str = "*";
86 } else if constexpr (std::is_same_v<Op, Div>) {
87 op_str = "/";
88 }
89 return "(" + op_str + " " + left.to_string() + " " + right.to_string() + ")";
90 }
91};
92
93// Type traits
94
95template<typename T>
96struct is_const_expr : std::false_type {};
97
98template<typename T>
99struct is_const_expr<Const<T>> : std::true_type {};
100
101template<typename T>
103
104template<typename T, typename = void>
105struct is_expr_node : std::false_type {};
106
107template<typename T>
108struct is_expr_node<T, std::void_t<decltype(T::arity_v)>> : std::true_type {};
109
110template<typename T>
112
113// Operator overloads for expression composition.
114// These use compile-time simplification via Zero<T> and One<T> marker types.
115
116// expr + expr
117template<typename L, typename R>
118 requires (is_expr_node_v<L> && is_expr_node_v<R>)
119[[nodiscard]] constexpr auto operator+(L l, R r) {
120 if constexpr (is_zero_v<L>) {
121 return r;
122 } else if constexpr (is_zero_v<R>) {
123 return l;
124 } else if constexpr (is_const_expr_v<L> && is_const_expr_v<R>) {
125 return Const<typename L::value_type>{l.value + r.value};
126 } else if constexpr (std::is_same_v<L, R>) {
127 using T = typename L::value_type;
128 return Const<T>{T(2)} * l;
129 } else {
130 return Binary<Add, L, R>{l, r};
131 }
132}
133
134// expr + scalar
135template<typename L, typename T>
136 requires (is_expr_node_v<L> && std::is_arithmetic_v<T>)
137[[nodiscard]] constexpr auto operator+(L l, T r) {
138 using VT = typename L::value_type;
139 return Binary<Add, L, Const<VT>>{l, Const<VT>{static_cast<VT>(r)}};
140}
141
142// scalar + expr
143template<typename T, typename R>
144 requires (std::is_arithmetic_v<T> && is_expr_node_v<R>)
145[[nodiscard]] constexpr auto operator+(T l, R r) {
146 using VT = typename R::value_type;
147 return Binary<Add, Const<VT>, R>{Const<VT>{static_cast<VT>(l)}, r};
148}
149
150// expr - expr
151template<typename L, typename R>
152 requires (is_expr_node_v<L> && is_expr_node_v<R>)
153[[nodiscard]] constexpr auto operator-(L l, R r) {
154 if constexpr (is_zero_v<R>) {
155 return l;
156 } else if constexpr (is_zero_v<L>) {
157 return -r;
158 } else if constexpr (is_const_expr_v<L> && is_const_expr_v<R>) {
159 return Const<typename L::value_type>{l.value - r.value};
160 } else if constexpr (std::is_same_v<L, R>) {
162 } else {
163 return Binary<Sub, L, R>{l, r};
164 }
165}
166
167// expr - scalar
168template<typename L, typename T>
169 requires (is_expr_node_v<L> && std::is_arithmetic_v<T>)
170[[nodiscard]] constexpr auto operator-(L l, T r) {
171 using VT = typename L::value_type;
172 return Binary<Sub, L, Const<VT>>{l, Const<VT>{static_cast<VT>(r)}};
173}
174
175// scalar - expr
176template<typename T, typename R>
177 requires (std::is_arithmetic_v<T> && is_expr_node_v<R>)
178[[nodiscard]] constexpr auto operator-(T l, R r) {
179 using VT = typename R::value_type;
180 return Binary<Sub, Const<VT>, R>{Const<VT>{static_cast<VT>(l)}, r};
181}
182
183// expr * expr (excludes Integral types -- they use ProductIntegral)
184template<typename L, typename R>
185 requires (is_expr_node_v<L> && is_expr_node_v<R> && !is_integral<L>::value && !is_integral<R>::value)
186[[nodiscard]] constexpr auto operator*(L l, R r) {
187 if constexpr (is_zero_v<L>) {
189 } else if constexpr (is_zero_v<R>) {
191 } else if constexpr (is_one_v<L>) {
192 return r;
193 } else if constexpr (is_one_v<R>) {
194 return l;
195 } else if constexpr (is_const_expr_v<L> && is_const_expr_v<R>) {
196 return Const<typename L::value_type>{l.value * r.value};
197 } else {
198 return Binary<Mul, L, R>{l, r};
199 }
200}
201
202// expr * scalar
203template<typename L, typename T>
204 requires (is_expr_node_v<L> && std::is_arithmetic_v<T>)
205[[nodiscard]] constexpr auto operator*(L l, T r) {
206 using VT = typename L::value_type;
207 return Binary<Mul, L, Const<VT>>{l, Const<VT>{static_cast<VT>(r)}};
208}
209
210// scalar * expr
211template<typename T, typename R>
212 requires (std::is_arithmetic_v<T> && is_expr_node_v<R>)
213[[nodiscard]] constexpr auto operator*(T l, R r) {
214 using VT = typename R::value_type;
215 return Binary<Mul, Const<VT>, R>{Const<VT>{static_cast<VT>(l)}, r};
216}
217
218// expr / expr
219template<typename L, typename R>
220 requires (is_expr_node_v<L> && is_expr_node_v<R>)
221[[nodiscard]] constexpr auto operator/(L l, R r) {
222 if constexpr (is_zero_v<L>) {
224 } else if constexpr (is_one_v<R>) {
225 return l;
226 } else if constexpr (is_const_expr_v<L> && is_const_expr_v<R>) {
227 return Const<typename L::value_type>{l.value / r.value};
228 } else if constexpr (std::is_same_v<L, R>) {
230 } else {
231 return Binary<Div, L, R>{l, r};
232 }
233}
234
235// expr / scalar
236template<typename L, typename T>
237 requires (is_expr_node_v<L> && std::is_arithmetic_v<T>)
238[[nodiscard]] constexpr auto operator/(L l, T r) {
239 using VT = typename L::value_type;
240 return Binary<Div, L, Const<VT>>{l, Const<VT>{static_cast<VT>(r)}};
241}
242
243// scalar / expr
244template<typename T, typename R>
245 requires (std::is_arithmetic_v<T> && is_expr_node_v<R>)
246[[nodiscard]] constexpr auto operator/(T l, R r) {
247 using VT = typename R::value_type;
248 return Binary<Div, Const<VT>, R>{Const<VT>{static_cast<VT>(l)}, r};
249}
250
251} // namespace limes::expr
Expression layer for composable calculus.
Definition analysis.hpp:7
constexpr bool is_const_expr_v
Definition binary.hpp:102
constexpr bool is_expr_node_v
Definition binary.hpp:111
constexpr Binary(L l, R r) noexcept
Definition binary.hpp:36
constexpr value_type evaluate(std::span< value_type const > args) const
Definition binary.hpp:54
constexpr auto derivative() const
Definition binary.hpp:61
std::string to_string() const
Definition binary.hpp:78
typename L::value_type value_type
Definition binary.hpp:26
static constexpr std::size_t arity_v
Definition binary.hpp:31
constexpr value_type eval(std::span< value_type const > args) const
Definition binary.hpp:38