Files
2026-07-13 12:47:05 +08:00

416 lines
13 KiB
C++

/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
#ifndef LIBND4J_REQUIREMENTSHELPER_H
#define LIBND4J_REQUIREMENTSHELPER_H
#include <ConstMessages.h>
#include <helpers/ShapeUtils.h>
#include <helpers/logger.h>
#include <system/Environment.h>
#include <system/op_boilerplate.h>
#include <exception>
#include <iostream>
#include <type_traits>
namespace sd {
inline std::ostream& operator<<(std::ostream& o, DataType type) {
o << DataTypeUtils::asString(type);
return o;
}
template <class T, std::size_t N>
SD_LIB_HIDDEN std::ostream& operator<<(std::ostream& o, const std::array<T, N>& arr) {
o << '[';
for (auto& x : arr) {
o << x << ", ";
}
o << ']';
return o;
}
template <class T>
SD_LIB_HIDDEN std::ostream& operator<<(std::ostream& o, const std::initializer_list<T>& arr) {
for (auto& x : arr) {
o << x << ", ";
}
return o;
}
template <typename T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
// https://stackoverflow.com/questions/15393938/find-out-whether-a-c-object-is-callable
template <typename T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename T>
using remove_ref_t = typename std::remove_reference<T>::type;
template <typename T>
using remove_refptr_t = typename std::remove_pointer<remove_ref_t<T>>::type;
template <typename T>
using is_function_t = typename std::is_function<remove_refptr_t<T>>::type;
template <bool isObject, typename T>
struct is_callable_impl : public is_function_t<T> {};
template <typename T>
struct is_callable_impl<true, T> {
private:
struct Fallback {
void operator()();
};
struct Derived : T, Fallback {};
template <typename U, U>
struct Check;
template <typename>
static std::true_type test(...);
template <typename C>
static std::false_type test(Check<void (Fallback::*)(), &C::operator()>*);
public:
typedef decltype(test<Derived>(nullptr)) type;
};
template <typename T>
using is_callable = typename is_callable_impl<std::is_class<remove_ref_t<T>>::value, remove_ref_t<T>>::type;
template <typename S, typename T>
class is_streamable {
template <typename SS, typename TT>
static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());
template <typename, typename>
static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<S, T>(0))::value;
};
template <typename T>
class has_getMsg {
template <typename TT>
static auto test(int) -> decltype(std::declval<TT&>().getMsg(), std::true_type());
template <typename>
static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T>(0))::value;
};
template <typename T>
class has_getValue {
template <typename TT>
static auto test(int) -> decltype(std::declval<TT&>().getValue(), std::true_type());
template <typename>
static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T>(0))::value;
};
template <typename T>
class has_getValueStr {
template <typename TT>
static auto test(int) -> decltype(std::declval<TT&>().getValueStr(), std::true_type());
template <typename>
static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T>(0))::value;
};
template <typename T, typename Enable = void>
struct Underline {
using type = T;
};
template <typename T>
struct Underline<T, typename std::enable_if<has_getValue<T>::value>::type> {
using type = remove_cvref_t<decltype(std::declval<T&>().getValue())>;
};
template <typename T, typename Enable = void>
struct UnderlineMsg {
using type = T;
};
template <typename T>
struct UnderlineMsg<T, typename std::enable_if<has_getMsg<T>::value>::type> {
using type = decltype(std::declval<T&>().getMsg());
};
template <typename T>
using UnderlineType = typename Underline<T>::type;
template <typename T>
using UnderlineMsgType = typename UnderlineMsg<T>::type;
template <typename T>
typename std::enable_if<(!has_getMsg<T>::value ||
(has_getMsg<T>::value && !is_streamable<std::stringstream, UnderlineMsgType<T>>::value)),
const char*>::type
getMsg(T x) {
return "";
}
template <typename T>
typename std::enable_if<(has_getMsg<T>::value && is_streamable<std::stringstream, UnderlineMsgType<T>>::value),
UnderlineMsgType<T>>::type
getMsg(const T& x) {
return x.getMsg();
}
template <typename T>
typename std::enable_if<!has_getValue<T>::value, T>::type getValue(const T& x) {
return x;
}
template <typename T>
typename std::enable_if<has_getValue<T>::value, decltype(std::declval<T&>().getValue())>::type getValue(const T& x) {
return x.getValue();
}
template <typename T>
typename std::enable_if<!has_getValueStr<T>::value && !std::is_same<remove_cvref_t<UnderlineType<T>>, bool>::value &&
is_streamable<std::stringstream, UnderlineType<T>>::value,
UnderlineType<T>>::type
getStreamValue(const T& x) {
return getValue(x);
}
template <typename T>
typename std::enable_if<!has_getValueStr<T>::value && !is_streamable<std::stringstream, UnderlineType<T>>::value,
std::string>::type
getStreamValue(T x) {
return "{//system can not stringify the variable}";
}
template <typename T>
typename std::enable_if<has_getValueStr<T>::value, std::string>::type getStreamValue(const T& x) {
return x.getValueStr();
}
template <typename T>
typename std::enable_if<!has_getValueStr<T>::value && std::is_same<remove_cvref_t<UnderlineType<T>>, bool>::value,
const char*>::type
getStreamValue(const T& x) {
return getValue(x) ? "True" : "False";
}
/**
* @brief Requirements helper is used to replace plain checks for making them output informative messages
* @see https://github.com/eclipse/deeplearning4j/blob/master/libnd4j/Helpers.md
*/
class Requirements {
public:
Requirements(const char* prefix_msg = "") : prefix(prefix_msg), ok(true) {
#if defined(ENABLE_LOG_TO_TEST)
sd::Environment::getInstance().setDebug(true);
sd::Environment::getInstance().setVerbose(true);
#endif
};
// Requirements is implicitly converted to bool.
// Note: this way you can use short circuit operators && to chain requirements
// req.expect && req.expect && ...
// This way you will get the shortcircuit that will not evaluate the later
// it is better than using calls chaining like: req.expect( ).expect
operator bool() { return this->ok; }
// sets the prefix message
Requirements& setPrefix(const char* prefix_msg) {
prefix = prefix_msg;
return *this;
}
// Compares two values with comparison
// Note: to achive full lazy evaluation of the obtained values or messages
// you should wrap them and use getValue getMsg functions
template <typename T, typename T1, typename Op>
Requirements& expect(const T& expVar, const T1& reqVar, Op comparision, const char* first_half = "") {
if (!this->ok) return *this;
bool cmp = comparision(getValue(expVar), getValue(reqVar));
if (!cmp && sd::Environment::getInstance().isDebug() && sd::Environment::getInstance().isVerbose()) {
std::stringstream stream;
stream << prefix << ": " << getMsg(expVar) << " {" << getStreamValue(expVar) << "} " << first_half << ' '
<< getMsg(reqVar) << ' ' << getStreamValue(reqVar);
sd::Logger::info("%s\n", stream.str().c_str());
}
this->ok = this->ok && cmp;
return *this;
}
template <typename T, typename T1>
Requirements& expectEq(const T& exp, const T1& req) {
return expect(exp, req, std::equal_to<UnderlineType<T1>>{}, EXPECTED_EQ_MSG);
}
template <typename T, typename T1>
Requirements& expectNotEq(const T& exp, const T1& req) {
return expect(exp, req, std::not_equal_to<UnderlineType<T1>>{}, EXPECTED_NE_MSG);
}
template <typename T, typename T1>
Requirements& expectLess(const T& exp, const T1& req) {
return expect(exp, req, std::less<UnderlineType<T1>>{}, EXPECTED_LT_MSG);
}
template <typename T, typename T1>
Requirements& expectLessEq(const T& exp, const T1& req) {
return expect(exp, req, std::less_equal<UnderlineType<T1>>{}, EXPECTED_LE_MSG);
}
template <typename T, typename T1>
Requirements& expectGreater(T exp, T1 req) {
return expect(exp, req, std::greater<UnderlineType<T1>>{}, EXPECTED_GT_MSG);
}
template <typename T, typename T1>
Requirements& expectGreaterEq(const T& exp, const T1& req) {
return expect(exp, req, std::greater_equal<UnderlineType<T1>>{}, EXPECTED_GE_MSG);
}
// throws. use this if you want to throw an exception if there is any failure
void throws() {
if (!this->ok) THROW_EXCEPTION(OP_VALIDATION_FAIL_MSG);
}
template <typename T>
Requirements& expectTrue(const T& expVar, const char* msg = EXPECTED_TRUE) {
if (!this->ok) return *this;
bool cmp = static_cast<bool>(getValue(expVar));
if (!cmp && sd::Environment::getInstance().isDebug() && sd::Environment::getInstance().isVerbose()) {
std::stringstream stream;
stream << prefix << ": " << getMsg(expVar) << " {" << getStreamValue(expVar) << "} " << msg;
sd::Logger::info("%s\n", stream.str().c_str());
}
this->ok = this->ok && cmp;
return *this;
}
template <typename T>
Requirements& expectFalse(const T& expVar, const char* msg = EXPECTED_FALSE) {
if (!this->ok) return *this;
bool cmp = static_cast<bool>(getValue(expVar));
if (cmp && sd::Environment::getInstance().isDebug() && sd::Environment::getInstance().isVerbose()) {
std::stringstream stream;
stream << prefix << ": " << getMsg(expVar) << " {" << getStreamValue(expVar) << "} " << msg;
sd::Logger::info("%s\n", stream.str().c_str());
}
this->ok = this->ok && !cmp;
return *this;
}
template <typename T, typename T2>
Requirements expectIn(const T& expVar, std::initializer_list<T2> vals) {
if (!ok) return *this;
auto val = getValue(expVar);
for (const auto& r_i : vals) {
if (val == r_i) return *this;
}
if (sd::Environment::getInstance().isDebug() && sd::Environment::getInstance().isVerbose()) {
std::stringstream stream;
stream << prefix << ": " << getMsg(expVar) << '{' << getStreamValue(expVar) << "} " << EXPECTED_IN << " {"
<< getStreamValue(vals) << "}\n";
sd::Logger::info("%s", stream.str().c_str());
}
ok = false;
return *this;
}
// Logs the Successfull cases to know if the requiremets met the conditions
void logTheSuccess() {
if (this->ok && sd::Environment::getInstance().isDebug() && sd::Environment::getInstance().isVerbose()) {
sd::Logger::info("%s: %s\n", prefix, REQUIREMENTS_MEETS_MSG);
}
}
private:
// the prefix used to add messages in each log
const char* prefix;
// bool value used for the Requirements to mimick bool
bool ok;
};
// Generic wrapper where variable and info can be lazy evaluated using the lambda
template <typename T1, typename T2>
struct InfoVariable {
T1 value_or_op;
T2 message_or_op;
InfoVariable(T1 valOrOp, T2 msgOrOp) : value_or_op(valOrOp), message_or_op(msgOrOp) {}
template <typename U = T1, typename std::enable_if<!is_callable<U>::value, int>::type = 0>
U getValue() const {
return value_or_op;
}
template <typename U = T1, typename std::enable_if<is_callable<U>::value, int>::type = 0>
auto getValue() const -> decltype(std::declval<U&>()()) {
return value_or_op();
}
template <typename U = T2, typename std::enable_if<!is_callable<U>::value, int>::type = 0>
U getMsg() const {
return message_or_op;
}
template <typename U = T2, typename std::enable_if<is_callable<U>::value, int>::type = 0>
auto getMsg() const -> decltype(std::declval<U&>()()) {
return message_or_op();
}
};
template <typename T1, typename T2>
SD_LIB_HIDDEN InfoVariable<T1, T2> makeInfoVariable(T1&& v1, T2&& v2) {
return InfoVariable<T1, T2>(std::forward<T1>(v1), std::forward<T2>(v2));
}
template <typename T>
struct ShapeInfoVariable {
explicit ShapeInfoVariable(const T& val, const char* msg = "") : value(val), message(msg) {}
const T& value;
const char* message;
const char* getMsg() const { return message; }
const T& getValue() const { return value; }
std::string getValueStr() const { return ShapeUtils::shapeAsString(value); }
};
template <typename T>
ShapeInfoVariable<T> makeShapeInfoVariable(T&& v, const char* msg) {
return ShapeInfoVariable<T>(std::forward<T>(v), msg);
}
} // namespace sd
#endif // LIBND4J_REQUIREMENTSHELPER_H