/* ****************************************************************************** * * * 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 #include #include #include #include #include #include #include namespace sd { inline std::ostream& operator<<(std::ostream& o, DataType type) { o << DataTypeUtils::asString(type); return o; } template SD_LIB_HIDDEN std::ostream& operator<<(std::ostream& o, const std::array& arr) { o << '['; for (auto& x : arr) { o << x << ", "; } o << ']'; return o; } template SD_LIB_HIDDEN std::ostream& operator<<(std::ostream& o, const std::initializer_list& arr) { for (auto& x : arr) { o << x << ", "; } return o; } template using remove_cvref_t = typename std::remove_cv::type>::type; // https://stackoverflow.com/questions/15393938/find-out-whether-a-c-object-is-callable template using remove_cvref_t = typename std::remove_cv::type>::type; template using remove_ref_t = typename std::remove_reference::type; template using remove_refptr_t = typename std::remove_pointer>::type; template using is_function_t = typename std::is_function>::type; template struct is_callable_impl : public is_function_t {}; template struct is_callable_impl { private: struct Fallback { void operator()(); }; struct Derived : T, Fallback {}; template struct Check; template static std::true_type test(...); template static std::false_type test(Check*); public: typedef decltype(test(nullptr)) type; }; template using is_callable = typename is_callable_impl>::value, remove_ref_t>::type; template class is_streamable { template static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; template class has_getMsg { template static auto test(int) -> decltype(std::declval().getMsg(), std::true_type()); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; template class has_getValue { template static auto test(int) -> decltype(std::declval().getValue(), std::true_type()); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; template class has_getValueStr { template static auto test(int) -> decltype(std::declval().getValueStr(), std::true_type()); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; template struct Underline { using type = T; }; template struct Underline::value>::type> { using type = remove_cvref_t().getValue())>; }; template struct UnderlineMsg { using type = T; }; template struct UnderlineMsg::value>::type> { using type = decltype(std::declval().getMsg()); }; template using UnderlineType = typename Underline::type; template using UnderlineMsgType = typename UnderlineMsg::type; template typename std::enable_if<(!has_getMsg::value || (has_getMsg::value && !is_streamable>::value)), const char*>::type getMsg(T x) { return ""; } template typename std::enable_if<(has_getMsg::value && is_streamable>::value), UnderlineMsgType>::type getMsg(const T& x) { return x.getMsg(); } template typename std::enable_if::value, T>::type getValue(const T& x) { return x; } template typename std::enable_if::value, decltype(std::declval().getValue())>::type getValue(const T& x) { return x.getValue(); } template typename std::enable_if::value && !std::is_same>, bool>::value && is_streamable>::value, UnderlineType>::type getStreamValue(const T& x) { return getValue(x); } template typename std::enable_if::value && !is_streamable>::value, std::string>::type getStreamValue(T x) { return "{//system can not stringify the variable}"; } template typename std::enable_if::value, std::string>::type getStreamValue(const T& x) { return x.getValueStr(); } template typename std::enable_if::value && std::is_same>, 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 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 Requirements& expectEq(const T& exp, const T1& req) { return expect(exp, req, std::equal_to>{}, EXPECTED_EQ_MSG); } template Requirements& expectNotEq(const T& exp, const T1& req) { return expect(exp, req, std::not_equal_to>{}, EXPECTED_NE_MSG); } template Requirements& expectLess(const T& exp, const T1& req) { return expect(exp, req, std::less>{}, EXPECTED_LT_MSG); } template Requirements& expectLessEq(const T& exp, const T1& req) { return expect(exp, req, std::less_equal>{}, EXPECTED_LE_MSG); } template Requirements& expectGreater(T exp, T1 req) { return expect(exp, req, std::greater>{}, EXPECTED_GT_MSG); } template Requirements& expectGreaterEq(const T& exp, const T1& req) { return expect(exp, req, std::greater_equal>{}, 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 Requirements& expectTrue(const T& expVar, const char* msg = EXPECTED_TRUE) { if (!this->ok) return *this; bool cmp = static_cast(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 Requirements& expectFalse(const T& expVar, const char* msg = EXPECTED_FALSE) { if (!this->ok) return *this; bool cmp = static_cast(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 Requirements expectIn(const T& expVar, std::initializer_list 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 struct InfoVariable { T1 value_or_op; T2 message_or_op; InfoVariable(T1 valOrOp, T2 msgOrOp) : value_or_op(valOrOp), message_or_op(msgOrOp) {} template ::value, int>::type = 0> U getValue() const { return value_or_op; } template ::value, int>::type = 0> auto getValue() const -> decltype(std::declval()()) { return value_or_op(); } template ::value, int>::type = 0> U getMsg() const { return message_or_op; } template ::value, int>::type = 0> auto getMsg() const -> decltype(std::declval()()) { return message_or_op(); } }; template SD_LIB_HIDDEN InfoVariable makeInfoVariable(T1&& v1, T2&& v2) { return InfoVariable(std::forward(v1), std::forward(v2)); } template 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 ShapeInfoVariable makeShapeInfoVariable(T&& v, const char* msg) { return ShapeInfoVariable(std::forward(v), msg); } } // namespace sd #endif // LIBND4J_REQUIREMENTSHELPER_H