diff --git a/include/aidge/graph/GraphView.hpp b/include/aidge/graph/GraphView.hpp index 3311797d858cf4899a6cfed7a18fb9840afb514e..46fa56ef0e7d63ce10bb3c96a8d7e1c42b191322 100644 --- a/include/aidge/graph/GraphView.hpp +++ b/include/aidge/graph/GraphView.hpp @@ -62,10 +62,10 @@ public: return mNodes == gv.mNodes; } - NodePtr operator[](const std::string& name) + NodePtr operator[](const std::string& nodeName) { - assert(mNodeRegistry.find(name) != mNodeRegistry.end() && "Could not find Node in the GraphView."); - return mNodeRegistry.at(name); + AIDGE_ASSERT(mNodeRegistry.find(nodeName) != mNodeRegistry.end(), "No node named {} in graph {}.", nodeName, name()); + return mNodeRegistry.at(nodeName); } /////////////////////////////////////////////////////// @@ -379,11 +379,10 @@ public: * @param toTensor Input Tensor ID of the new Node. Default to gk_IODefaultIndex, meaning * first available data input for the Node. */ - inline void addChild(NodePtr toOtherNode, std::string fromOutNodeName, + inline void addChild(NodePtr toOtherNode, const std::string& fromOutNodeName, const IOIndex_t fromTensor = IOIndex_t(0), IOIndex_t toTensor = gk_IODefaultIndex) { - assert(mNodeRegistry.find(fromOutNodeName) != mNodeRegistry.end() && - "No Node with this name found in the GraphView."); + AIDGE_ASSERT(mNodeRegistry.find(fromOutNodeName) != mNodeRegistry.end(), "No node named {} in graph {}.", fromOutNodeName, name()); addChild(toOtherNode, mNodeRegistry.at(fromOutNodeName), fromTensor, toTensor); } @@ -524,7 +523,6 @@ private: // TOPOLOGY /////////////////////////////////////////////////////// - void _forwardDims(std::set<NodePtr> listNodes); }; /** diff --git a/include/aidge/utils/ErrorHandling.hpp b/include/aidge/utils/ErrorHandling.hpp index 653a774b92e26513c9ac555e0aec1daed793e208..d4235d2db9b06597df80966e67306d84ac814a3c 100644 --- a/include/aidge/utils/ErrorHandling.hpp +++ b/include/aidge/utils/ErrorHandling.hpp @@ -18,13 +18,15 @@ #include <fmt/format.h> #include <fmt/ranges.h> +#include "aidge/utils/Log.hpp" + #ifdef NO_EXCEPTION #define AIDGE_THROW_OR_ABORT(ex, ...) \ -do { fmt::print(__VA_ARGS__); std::abort(); } while (false) +do { Aidge::Log::fatal(__VA_ARGS__); std::abort(); } while (false) #else #include <stdexcept> #define AIDGE_THROW_OR_ABORT(ex, ...) \ -throw ex(fmt::format(__VA_ARGS__)) +do { Aidge::Log::fatal(__VA_ARGS__); throw ex(fmt::format(__VA_ARGS__)); } while (false) #endif /** @@ -33,7 +35,7 @@ throw ex(fmt::format(__VA_ARGS__)) * If it asserts, it means an user error. */ #define AIDGE_ASSERT(stm, ...) \ -if (!(stm)) { fmt::print("Assertion failed: " #stm " in {}:{}", __FILE__, __LINE__); \ +if (!(stm)) { Aidge::Log::error("Assertion failed: " #stm " in {}:{}", __FILE__, __LINE__); \ AIDGE_THROW_OR_ABORT(std::runtime_error, __VA_ARGS__); } /** diff --git a/include/aidge/utils/Log.hpp b/include/aidge/utils/Log.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8a18bbab34d3c1c86252833852abc5faca41dd96 --- /dev/null +++ b/include/aidge/utils/Log.hpp @@ -0,0 +1,148 @@ +/******************************************************************************** + * Copyright (c) 2023 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + + +#ifndef AIDGE_LOG_H_ +#define AIDGE_LOG_H_ + +#include <memory> + +#include <fmt/format.h> +#include <fmt/ranges.h> + +namespace Aidge { +/** + * Aidge logging class, for displaying and file logging of events. +*/ +class Log { +public: + enum Level { + Debug = 0, + Info, + Notice, + Warn, + Error, + Fatal + }; + + /** + * Detailed messages for debugging purposes, providing information helpful + * for developers to trace and identify issues. + * Detailed insights of what is appening in an operation, not useful for the + * end-user. The operation is performed nominally. + * @note This level is disabled at compile time for Release, therefore + * inducing no runtime overhead for Release. + */ + template <typename... Args> + constexpr static void debug(Args&&... args) { +#ifndef NDEBUG + // only when compiled in Debug + log(Debug, fmt::format(std::forward<Args>(args)...)); +#endif + } + + /** + * Messages that provide a record of the normal operation, about + * the application's state, progress, or important events. + * Reports normal start, end and key steps in an operation. The operation is + * performed nominally. + */ + template <typename... Args> + constexpr static void info(Args&&... args) { + log(Info, fmt::format(std::forward<Args>(args)...)); + } + + /** + * Applies to normal but significant conditions that may require monitoring, + * like unusual or normal fallback events. + * Reports specific paths in an operation. The operation can still be + * performed normally. + */ + template <typename... Args> + constexpr static void notice(Args&&... args) { + log(Notice, fmt::format(std::forward<Args>(args)...)); + } + + /** + * Indicates potential issues or situations that may lead to errors but do + * not necessarily cause immediate problems. + * Some specific steps of the operation could not be performed, but it can + * still provide an exploitable result. + */ + template <typename... Args> + constexpr static void warn(Args&&... args) { + log(Warn, fmt::format(std::forward<Args>(args)...)); + } + + /** + * Signifies a problem or unexpected condition that the application can + * recover from, but attention is needed to prevent further issues. + * The operation could not be performed, but it does not prevent potential + * further operations. + */ + template <typename... Args> + constexpr static void error(Args&&... args) { + log(Error, fmt::format(std::forward<Args>(args)...)); + } + + /** + * Represents a critical error or condition that leads to the termination of + * the application, indicating a severe and unrecoverable problem. + * The operation could not be performed and any further operation is + * impossible. + */ + template <typename... Args> + constexpr static void fatal(Args&&... args) { + log(Fatal, fmt::format(std::forward<Args>(args)...)); + } + + /** + * Set the minimum log level displayed in the console. + */ + constexpr static void setConsoleLevel(Level level) { + mConsoleLevel = level; + } + + /** + * Set the minimum log level saved in the log file. + */ + constexpr static void setFileLevel(Level level) { + mFileLevel = level; + } + + /** + * Set the log file name. + * Close the current log file and open the one with the new file name. + * If empty, stop logging into a file. + */ + static void setFileName(const std::string& fileName) { + if (fileName != mFileName) { + mFileName = fileName; + mFile.release(); + + if (!fileName.empty()) { + initFile(fileName); + } + } + } + +private: + static void log(Level level, const std::string& msg); + static void initFile(const std::string& fileName); + + static Level mConsoleLevel; + static Level mFileLevel; + static std::string mFileName; + static std::unique_ptr<FILE, decltype(&std::fclose)> mFile; +}; +} + +#endif //AIDGE_LOG_H_ diff --git a/include/aidge/utils/Registrar.hpp b/include/aidge/utils/Registrar.hpp index a5bd260ec189ac998134b738ca1ae757f2a0038c..567270d63c092aef6411a4438f59b7770ee3d5bf 100644 --- a/include/aidge/utils/Registrar.hpp +++ b/include/aidge/utils/Registrar.hpp @@ -132,11 +132,13 @@ void declare_registrable(py::module& m, const std::string& class_name){ #ifdef PYBIND #define SET_IMPL_MACRO(T_Op, op, backend_name) \ \ - if(Py_IsInitialized()) { \ - auto obj = py::cast(&(op)); \ - (op).setImpl(Registrar<T_Op>::create(backend_name)(op)); \ - } else { \ - (op).setImpl(Registrar<T_Op>::create(backend_name)(op)); \ + if (Registrar<T_Op>::exists(backend_name)) { \ + if(Py_IsInitialized()) { \ + auto obj = py::cast(&(op)); \ + (op).setImpl(Registrar<T_Op>::create(backend_name)(op)); \ + } else { \ + (op).setImpl(Registrar<T_Op>::create(backend_name)(op)); \ + } \ } #else #define SET_IMPL_MACRO(T_Op, op, backend_name) \ diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp index 6c4dd29dfbb158774ea86b181503e7e7e718bda4..52863735ca431e797fab3426d7e61796a8725dd2 100644 --- a/python_binding/pybind_core.cpp +++ b/python_binding/pybind_core.cpp @@ -23,6 +23,7 @@ void init_DataProvider(py::module&); void init_Tensor(py::module&); void init_OperatorImpl(py::module&); void init_Attributes(py::module&); +void init_Log(py::module&); void init_Operator(py::module&); void init_OperatorTensor(py::module&); @@ -85,6 +86,7 @@ void init_Aidge(py::module& m){ init_OperatorImpl(m); init_Attributes(m); + init_Log(m); init_Operator(m); init_OperatorTensor(m); init_Add(m); diff --git a/python_binding/utils/pybind_Log.cpp b/python_binding/utils/pybind_Log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10a02dcafefe089c8836ee7d4e3a9783a2aa96a6 --- /dev/null +++ b/python_binding/utils/pybind_Log.cpp @@ -0,0 +1,103 @@ +#include <pybind11/pybind11.h> +#include "aidge/utils/Log.hpp" + +namespace py = pybind11; +namespace Aidge { +void init_Log(py::module& m){ + py::enum_<Log::Level>(m, "Level") + .value("Debug", Log::Debug) + .value("Info", Log::Info) + .value("Notice", Log::Notice) + .value("Warn", Log::Warn) + .value("Error", Log::Error) + .value("Fatal", Log::Fatal); + + py::class_<Log>(m, "Log") + .def_static("debug", [](const std::string& msg) { Log::debug(msg); }, py::arg("msg"), + R"mydelimiter( + Detailed messages for debugging purposes, providing information helpful + for developers to trace and identify issues. + Detailed insights of what is appening in an operation, not useful for the + end-user. The operation is performed nominally. + Note: This level is disabled at compile time for Release, therefore + inducing no runtime overhead for Release. + + :param msg: Debug message. + :type msg: str + )mydelimiter") + .def_static("info", [](const std::string& msg) { Log::info(msg); }, py::arg("msg"), + R"mydelimiter( + Messages that provide a record of the normal operation, about + the application's state, progress, or important events. + Reports normal start, end and key steps in an operation. The operation is + performed nominally. + + :param msg: Info message. + :type msg: str + )mydelimiter") + .def_static("notice", [](const std::string& msg) { Log::notice(msg); }, py::arg("msg"), + R"mydelimiter( + Applies to normal but significant conditions that may require monitoring, + like unusual or normal fallback events. + Reports specific paths in an operation. The operation can still be + performed normally. + + :param msg: Notice message. + :type msg: str + )mydelimiter") + .def_static("warn", [](const std::string& msg) { Log::warn(msg); }, py::arg("msg"), + R"mydelimiter( + Indicates potential issues or situations that may lead to errors but do + not necessarily cause immediate problems. + Some specific steps of the operation could not be performed, but it can + still provide an exploitable result. + + :param msg: Warning message. + :type msg: str + )mydelimiter") + .def_static("error",[](const std::string& msg) { Log::error(msg); }, py::arg("msg"), + R"mydelimiter( + Signifies a problem or unexpected condition that the application can + recover from, but attention is needed to prevent further issues. + The operation could not be performed, but it does not prevent potential + further operations. + + :param msg: Error message. + :type msg: str + )mydelimiter") + .def_static("fatal", [](const std::string& msg) { Log::fatal(msg); }, py::arg("msg"), + R"mydelimiter( + Represents a critical error or condition that leads to the termination of + the application, indicating a severe and unrecoverable problem. + The operation could not be performed and any further operation is + impossible. + + :param msg: Fatal message. + :type msg: str + )mydelimiter") + .def_static("setConsoleLevel", &Log::setConsoleLevel, py::arg("level"), + R"mydelimiter( + Set the minimum log level displayed in the console. + + :param level: Log level. + :type level: Level + )mydelimiter") + .def_static("setFileLevel", &Log::setFileLevel, py::arg("level"), + R"mydelimiter( + Set the minimum log level saved in the log file. + + :param level: Log level. + :type level: Level + )mydelimiter") + .def_static("setFileName", &Log::setFileName, py::arg("fileName"), + R"mydelimiter( + Set the log file name. + Close the current log file and open the one with the new file name. + If empty, stop logging into a file. + + :param fileName: Log file name. + :type fileName: str + )mydelimiter"); +} + +} diff --git a/src/backend/OperatorImpl.cpp b/src/backend/OperatorImpl.cpp index 1911da228c83d66117a2591adf47dc07cd8dc674..1439391b2e22fe0bea3b5a7692941afc67bc1c6b 100644 --- a/src/backend/OperatorImpl.cpp +++ b/src/backend/OperatorImpl.cpp @@ -25,14 +25,18 @@ Aidge::OperatorImpl::OperatorImpl(const Operator& op): } Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredData(const Aidge::IOIndex_t inputIdx) const { - assert(mOp.getRawInput(inputIdx) && "requires valid input"); + AIDGE_ASSERT(mOp.getRawInput(inputIdx), + "a valid input is required at index {} for operator type {}", + inputIdx, mOp.type()); // Requires the whole tensor by default return std::static_pointer_cast<Tensor>(mOp.getRawInput(inputIdx))->size(); } Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredProtected(IOIndex_t inputIdx) const { - assert(mOp.getRawInput(inputIdx) && "requires valid input"); + AIDGE_ASSERT(mOp.getRawInput(inputIdx), + "a valid input is required at index {} for operator type {}", + inputIdx, mOp.type()); // Protect the whole tensor by default return std::static_pointer_cast<Tensor>(mOp.getRawInput(inputIdx))->size(); @@ -40,19 +44,25 @@ Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredProtected(IOIndex_t inputIdx) Aidge::NbElts_t Aidge::OperatorImpl::getRequiredMemory(const Aidge::IOIndex_t outputIdx, const std::vector<Aidge::DimSize_t> &/*inputsSize*/) const { - assert(mOp.getRawOutput(outputIdx) && "requires valid output"); + AIDGE_ASSERT(mOp.getRawOutput(outputIdx), + "a valid output is required at index {} for operator type {}", + outputIdx, mOp.type()); // Requires the whole tensor by default, regardless of available data on inputs return std::static_pointer_cast<Tensor>(mOp.getRawOutput(outputIdx))->size(); } Aidge::NbElts_t Aidge::OperatorImpl::getNbConsumedData(Aidge::IOIndex_t inputIdx) const { - assert(static_cast<std::size_t>(inputIdx) < mNbConsumedData.size()); + AIDGE_ASSERT(static_cast<std::size_t>(inputIdx) < mNbConsumedData.size(), + "input index ({}) is out of bound ({}) for operator type {}", + inputIdx, mNbConsumedData.size(), mOp.type()); return mNbConsumedData[static_cast<std::size_t>(inputIdx)]; } Aidge::NbElts_t Aidge::OperatorImpl::getNbProducedData(Aidge::IOIndex_t outputIdx) const { - assert(static_cast<std::size_t>(outputIdx) < mNbProducedData.size()); + AIDGE_ASSERT(static_cast<std::size_t>(outputIdx) < mNbProducedData.size(), + "output index ({}) is out of bound ({}) for operator type {}", + outputIdx, mNbProducedData.size(), mOp.type()); return mNbProducedData[static_cast<std::size_t>(outputIdx)]; } diff --git a/src/graph/GraphView.cpp b/src/graph/GraphView.cpp index 3681ac533cab36d68e5243fe0486b7d0febca694..005a7e679da5941d0995204b6c2a28a01ce376b4 100644 --- a/src/graph/GraphView.cpp +++ b/src/graph/GraphView.cpp @@ -328,19 +328,18 @@ void Aidge::GraphView::compile(const std::string& backend, const Aidge::DataType } void Aidge::GraphView::forwardDims(const std::vector<std::vector<Aidge::DimSize_t>> dims) { - std::set<NodePtr> startNodes = inputNodes(); - // setInputs // Link every tensor to the right pointer // following parent - children informations if (!dims.empty()){ - AIDGE_ASSERT(dims.size() == mInputNodes.size(), "GraphView forwardDims error - Inconsistent number of dimensions and graph inputs"); + AIDGE_ASSERT(dims.size() == mInputNodes.size(), "GraphView forwardDims error - Inconsistent number of given dimensions ({}) and graph inputs ({})", dims.size(), mInputNodes.size()); for (std::size_t i = 0; i < dims.size(); ++i){ auto tensor = std::make_shared<Tensor>(dims[i]); mInputNodes[i].first->getOperator()->setInput(mInputNodes[i].second, tensor); } } - + + // Ensure every node in the graph is correctly connected for (std::shared_ptr<Node> nodePtr : getNodes()) { for (IOIndex_t i = 0; i < nodePtr->nbInputs(); ++i) { // assess if the input was not already set and is a Tensor then link it to parent output @@ -352,7 +351,7 @@ void Aidge::GraphView::forwardDims(const std::vector<std::vector<Aidge::DimSize_ nodePtr->getOperator()->associateInput(i, inputI.first->getOperator()->getRawOutput(inputI.second)); } else { - AIDGE_ASSERT(false, "Non-tensor entries not handled yet.\n"); + AIDGE_ASSERT(false, "Non-tensor entries not handled yet, for node {} (of type {}).", nodePtr->name(), nodePtr->type()); } } } else { @@ -362,54 +361,37 @@ void Aidge::GraphView::forwardDims(const std::vector<std::vector<Aidge::DimSize_ } } - - if (nodePtr->type() == Producer_Op::Type) { - startNodes.insert(nodePtr); - } } - // Compute dimensions of every node - _forwardDims(startNodes); -} - -void Aidge::GraphView::_forwardDims(std::set<std::shared_ptr<Node>> listNodes) { - // TODO: support multi-inputs/outputs - std::set<std::shared_ptr<Node>> nextList = std::set<std::shared_ptr<Node>>(); - for (std::shared_ptr<Node> nodePtr : listNodes) { - if (nodePtr->getOperator()->operatorType() == OperatorType::Tensor) { - const auto op = std::static_pointer_cast<OperatorTensor>(nodePtr->getOperator()); - if (!op->outputDimsForwarded()) { - op->computeOutputDims(); - } - if (!op->outputDimsForwarded()) { // try to compute output dimensions again later - nextList.insert(nodePtr); - } else { // compute output dimensions of children - std::set<std::shared_ptr<Node>> children = nodePtr->getChildren(); - for (auto child : children) { - const auto childOp = std::static_pointer_cast<OperatorTensor>(child->getOperator()); - if (!childOp->outputDimsForwarded()) { - nextList.insert(child); - } - } - } - } - } - if (nextList.empty()) { - for (std::shared_ptr<Node> nodePtr : getNodes()) { + // Compute dimensions of every node + std::set<std::shared_ptr<Node>> listNodes = getNodes(); + do { + std::set<std::shared_ptr<Node>> nextList; + for (std::shared_ptr<Node> nodePtr : listNodes) { if (nodePtr->getOperator()->operatorType() == OperatorType::Tensor) { - if (!std::static_pointer_cast<OperatorTensor>(nodePtr->getOperator())->outputDimsForwarded()) { - nextList.insert(nodePtr); - } + const auto op = std::static_pointer_cast<OperatorTensor>(nodePtr->getOperator()); + // Recompute everytime, even if it was already computed in a + // previous call of forwardDims(), as the graph may have changed! + op->computeOutputDims(); + if (!op->outputDimsForwarded()) { + nextList.insert(nodePtr); + } } } - } - // Internal check to make sure we won't enter in an infinite loop! - AIDGE_ASSERT(nextList != listNodes, "Unable to forward dimensions (circular dependency and/or wrong dimensions?)"); + // Internal check to make sure we won't enter in an infinite loop! + if (nextList == listNodes) { + // We are stuck! + std::vector<std::string> nodesName; + std::transform(nextList.begin(), nextList.end(), + std::back_inserter(nodesName), + [](auto val){ return val->name() + " (" + val->type() + ")"; }); + AIDGE_THROW_OR_ABORT(std::runtime_error, "Unable to forward dimensions (circular dependency and/or wrong dimensions?). Unable to compute output dims for nodes {}.", nodesName); + } - if (!nextList.empty()) { - _forwardDims(nextList); + listNodes.swap(nextList); } + while (!listNodes.empty()); } void Aidge::GraphView::setBackend(const std::string &backend, DeviceIdx_t device) { @@ -458,7 +440,7 @@ Aidge::GraphView::outputs(const std::string& nodeName) const { void Aidge::GraphView::setInputId(Aidge::IOIndex_t /*inID*/, Aidge::IOIndex_t /*newNodeOutID*/) { - fmt::print("Not implemented yet.\n"); + AIDGE_THROW_OR_ABORT(std::runtime_error, "Not implemented yet."); } void Aidge::GraphView::add(std::shared_ptr<Node> node, bool includeLearnableParam) { @@ -714,10 +696,7 @@ std::set<std::shared_ptr<Aidge::Node>> Aidge::GraphView::getParents() const { std::vector<std::shared_ptr<Aidge::Node>> Aidge::GraphView::getParents(const std::string nodeName) const { std::map<std::string, std::shared_ptr<Node>>::const_iterator it = mNodeRegistry.find(nodeName); - if (it == mNodeRegistry.end()) { - fmt::print("No such node a {} in {} graph.\n", nodeName, name()); - exit(-1); - } + AIDGE_ASSERT(it != mNodeRegistry.end(), "No node named {} in graph {}.", nodeName, name()); return (it->second)->getParents(); } @@ -743,20 +722,15 @@ std::vector<std::vector<std::shared_ptr<Aidge::Node>>> Aidge::GraphView::getChildren(const std::string nodeName) const { std::map<std::string, std::shared_ptr<Node>>::const_iterator it = mNodeRegistry.find(nodeName); - if (it == mNodeRegistry.end()) { - fmt::print("No such node a {} in {} graph.\n", nodeName, name()); - exit(-1); - } + AIDGE_ASSERT(it != mNodeRegistry.end(), "No node named {} in graph {}.", nodeName, name()); return (it->second)->getOrderedChildren(); } std::set<std::shared_ptr<Aidge::Node>> Aidge::GraphView::getChildren(const std::shared_ptr<Node> otherNode) const { std::set<std::shared_ptr<Node>>::const_iterator it = mNodes.find(otherNode); - if (it == mNodes.end()) { - fmt::print("No such node in graph.\n"); - exit(-1); - } + AIDGE_ASSERT(it != mNodes.end(), "The node {} (of type {}) is not in graph {}.", + (otherNode) ? otherNode->name() : "#nullptr", (otherNode) ? otherNode->type() : "", name()); return (*it)->getChildren(); } @@ -768,7 +742,7 @@ Aidge::GraphView::getNode(const std::string& nodeName) const { if (it != mNodeRegistry.cend()) { return it->second; } else { - fmt::print("No Node named {} in the current GraphView.\n", nodeName); + Log::warn("No Node named {} in the current GraphView {}.", nodeName, name()); return nullptr; } } diff --git a/src/graph/Node.cpp b/src/graph/Node.cpp index 5d210144e2faa122416186734c52b67f1a0f8281..14e166402039230a283ce617e4997c9ad099eed9 100644 --- a/src/graph/Node.cpp +++ b/src/graph/Node.cpp @@ -169,7 +169,9 @@ Aidge::IOIndex_t Aidge::Node::nbValidOutputs() const { } void Aidge::Node::setInputId(const IOIndex_t inId, const IOIndex_t newNodeoutId) { - assert(inId != gk_IODefaultIndex && (inId < nbInputs()) && "Must be a valid index"); + AIDGE_ASSERT(inId != gk_IODefaultIndex && inId < nbInputs(), + "Input index ({}) is out of bound ({}) for node {} (of type {})", + inId, nbInputs(), name(), type()); if (mIdOutParents[inId] != gk_IODefaultIndex) { fmt::print("Warning: filling a Tensor already attributed\n"); auto originalParent = input(inId); @@ -194,7 +196,7 @@ void Aidge::Node::addChildOp(std::shared_ptr<Node> otherNode, const IOIndex_t ou "Output index (#{}) of the node {} (of type {}) is out of bound (it has {} outputs), when trying to add the child node {} (of type {})", outId, name(), type(), nbOutputs(), otherNode->name(), otherNode->type()); if (otherNode->input(otherInId).second != gk_IODefaultIndex) { - fmt::print("Warning, the {}-th Parent of the child node already existed.\n", otherInId); + Log::notice("Notice: the {}-th Parent of the child node {} (of type {}) already existed", otherInId, otherNode->name(), otherNode->type()); } // manage tensors and potential previous parent otherNode->setInputId(otherInId, outId); @@ -239,23 +241,29 @@ void Aidge::Node::addChild(std::shared_ptr<GraphView> otherView, const IOIndex_t void Aidge::Node::addParent(const std::shared_ptr<Node> other_node, const IOIndex_t inId) { if (getParent(inId) != nullptr) { - fmt::print("Warning, you're replacing a Parent.\n"); + Log::notice("Notice: you are replacing an existing parent for node {} (of type {})", name(), type()); } - assert((inId != gk_IODefaultIndex) && (inId < nbInputs()) && "Input index out of bound."); + AIDGE_ASSERT(inId != gk_IODefaultIndex && inId < nbInputs(), + "Input index ({}) is out of bound ({}) for node {} (of type {})", + inId, nbInputs(), name(), type()); mParents[inId] = other_node; } std::vector<std::shared_ptr<Aidge::Node>> Aidge::Node::getParents() const { return mParents; } std::shared_ptr<Aidge::Node> Aidge::Node::popParent(const IOIndex_t inId) { - assert((inId != gk_IODefaultIndex) && (inId < nbInputs()) && "Input index out of bound."); + AIDGE_ASSERT(inId != gk_IODefaultIndex && inId < nbInputs(), + "Input index ({}) is out of bound ({}) for node {} (of type {})", + inId, nbInputs(), name(), type()); std::shared_ptr<Node> val = mParents[inId]; removeParent(inId); return val; } bool Aidge::Node::removeParent(const IOIndex_t inId) { - assert((inId != gk_IODefaultIndex) && (inId < nbInputs()) && "Parent index out of bound."); + AIDGE_ASSERT(inId != gk_IODefaultIndex && inId < nbInputs(), + "Input index ({}) is out of bound ({}) for node {} (of type {})", + inId, nbInputs(), name(), type()); if (mParents[inId]) { mParents[inId] = nullptr; mIdOutParents[inId] = gk_IODefaultIndex; diff --git a/src/graphRegex/GraphRegex.cpp b/src/graphRegex/GraphRegex.cpp index 00a031e3fa9b03ff1870446b9ae58e8d3eb65bf7..ca15ff8dec5ff5ebd4ea69141c6e286849162bb5 100644 --- a/src/graphRegex/GraphRegex.cpp +++ b/src/graphRegex/GraphRegex.cpp @@ -117,6 +117,8 @@ std::set<std::shared_ptr<MatchSolution>> GraphRegex::match(std::shared_ptr<Graph std::vector<std::shared_ptr<MatchSolution>> solution = fsm->test(combination); solutions.insert(solutions.end(), solution.begin(), solution.end()); } + + } return _findLargestCompatibleSet(solutions); } @@ -142,7 +144,10 @@ void GraphRegex::setNodeKey(const std::string key,std::function<bool(NodePtr)> f throw std::runtime_error(key + " is define"); } mAllLambda[key] = f; + _majConditionalInterpreterLambda(); + //we add the lambda as key by default + setNodeKey(key, key + "($)==true"); } void GraphRegex::_majConditionalInterpreterLambda(){ diff --git a/src/nodeTester/ConditionalLexer.cpp b/src/nodeTester/ConditionalLexer.cpp index 9379bd8409f8f7ec4bae3e0122f88de79718e9dd..e70772fc1a5d6136fb56f5981d73bf6cb0622991 100644 --- a/src/nodeTester/ConditionalLexer.cpp +++ b/src/nodeTester/ConditionalLexer.cpp @@ -120,7 +120,7 @@ std::shared_ptr<ParsingToken<ConditionalTokenTypes>> ConditionalLexer::getNextTo } - if (std::regex_match(currentChars,std::regex("(true|false)"))){ + if (std::regex_match(currentChars,std::regex("(true|false|True|False)"))){ return std::make_shared<ParsingToken<ConditionalTokenTypes>>(ConditionalTokenTypes::BOOL,currentChars); } else if (isLambda){ diff --git a/src/operator/Operator.cpp b/src/operator/Operator.cpp index 289b2be90735d848e5083090d2ae4319a7490fde..e4213cad80ebdc177649b0c25e4fc49222993211 100644 --- a/src/operator/Operator.cpp +++ b/src/operator/Operator.cpp @@ -75,4 +75,7 @@ void Aidge::Operator::forward() { runHooks(); } -void Aidge::Operator::backward() { mImpl->backward(); } +void Aidge::Operator::backward() { + AIDGE_ASSERT(mImpl != nullptr, "backward(): an implementation is required for {}!", type()); + mImpl->backward(); +} diff --git a/src/recipes/FuseMulAdd.cpp b/src/recipes/FuseMulAdd.cpp index f408959a13d007853c24e30c1ef683648cf9c200..b57c1c3fc5e4b12dbd0004472a864ddaa864116e 100644 --- a/src/recipes/FuseMulAdd.cpp +++ b/src/recipes/FuseMulAdd.cpp @@ -64,7 +64,7 @@ void Aidge::fuseMulAdd(std::shared_ptr<Aidge::Node> matmulNode, std::shared_ptr< { // If both inputs are producers, there is an ambiguity, but both options // result in a correct solution. - fmt::print("Warning: both MatMul inputs are Producers, assume data at input#0 and weights at input#1.\n"); + Log::notice("Notice: both MatMul inputs are Producers, assume data at input#0 and weights at input#1."); weight = matmulNode->getParent(1)->cloneSharedOperators(); } AIDGE_ASSERT(weight != nullptr, "Could not deduce weight input for MatMul operator."); diff --git a/src/utils/Log.cpp b/src/utils/Log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7649809339f4ebf716a7287f5744fb94a5b67ce2 --- /dev/null +++ b/src/utils/Log.cpp @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2023 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include "aidge/utils/Log.hpp" +#include "aidge/utils/ErrorHandling.hpp" + +#include <fmt/color.h> +#include <fmt/chrono.h> + +Aidge::Log::Level Aidge::Log::mConsoleLevel = Info; +Aidge::Log::Level Aidge::Log::mFileLevel = Debug; +std::string Aidge::Log::mFileName = "aidge.log"; +std::unique_ptr<FILE, decltype(&std::fclose)> Aidge::Log::mFile {nullptr, nullptr}; + +void Aidge::Log::log(Level level, const std::string& msg) { + if (level >= mConsoleLevel) { + // Apply log level style only for console. + // Styles that were already applied to msg with fmt are kept also in + // the log file. + const auto modifier + = (level == Debug) ? fmt::fg(fmt::color::gray) + : (level == Notice) ? fmt::fg(fmt::color::light_yellow) + : (level == Warn) ? fmt::fg(fmt::color::orange) + : (level == Error) ? fmt::fg(fmt::color::red) + : (level == Fatal) ? fmt::bg(fmt::color::red) + : fmt::text_style(); + + fmt::println("{}", fmt::styled(msg, modifier)); + } + + if (level >= mFileLevel && !mFileName.empty()) { + if (!mFile) { + initFile(mFileName); + } + + fmt::println(mFile.get(), msg); + } +} + +void Aidge::Log::initFile(const std::string& fileName) { + mFile = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(fileName.c_str(), "a"), &std::fclose); + + if (!mFile) { + mFileName.clear(); // prevents AIDGE_THROW_OR_ABORT() to try to log into file + AIDGE_THROW_OR_ABORT(std::runtime_error, + "Could not create log file: {}", fileName); + } + + const std::time_t t = std::time(nullptr); + fmt::println(mFile.get(), "###### {:%Y-%m-%d %H:%M:%S} ######", fmt::localtime(t)); +} diff --git a/unit_tests/graphRegex/Test_GraphRegex.cpp b/unit_tests/graphRegex/Test_GraphRegex.cpp index bcd6d0f4cd9ba32ee4318188343b7e6360670d3b..a62b9a8602b494f26fb47061b899eaba41129a1f 100644 --- a/unit_tests/graphRegex/Test_GraphRegex.cpp +++ b/unit_tests/graphRegex/Test_GraphRegex.cpp @@ -18,6 +18,32 @@ using namespace Aidge; TEST_CASE("GraphRegexUser") { + + SECTION("Match using custom lambda") { + + std::shared_ptr<GraphView> g1 = std::make_shared<GraphView>("TestGraph"); + std::shared_ptr<Node> conv = GenericOperator("Conv", 1, 0, 1, "c"); + std::shared_ptr<Node> fc = GenericOperator("FC", 1, 0, 1, "c1"); + std::shared_ptr<Node> conv2 = GenericOperator("Conv", 1, 0, 1, "c2"); + std::shared_ptr<Node> fc2 = GenericOperator("FC", 1, 0, 1, "c3"); + + g1->add(conv); + g1->addChild(fc, "c"); + g1->addChild(conv2, "c1"); + g1->addChild(fc2, "c2"); + + /// + std::shared_ptr<GraphRegex> sut = std::make_shared<GraphRegex>(); + sut->setNodeKey("C",+[](NodePtr NodeOp){return NodeOp->type() == "FC";}); + + sut->setNodeKey("A","C($)==True"); + sut->addQuery("A"); + auto match = sut->match(g1); + REQUIRE(match.size() == 2); + + } + + SECTION("INIT") { const std::string query = "Conv->FC"; diff --git a/unit_tests/utils/Test_Log.cpp b/unit_tests/utils/Test_Log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d8e672b84f5055a12185c3684c34bd888f0545b --- /dev/null +++ b/unit_tests/utils/Test_Log.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2023 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include <catch2/catch_test_macros.hpp> + +#include "aidge/utils/Log.hpp" + +#include <fmt/color.h> + +using namespace Aidge; + +TEST_CASE("[core/log] Log") { + SECTION("TestLog") { + Log::setConsoleLevel(Log::Debug); + Log::debug("debug"); + Log::debug("{}", fmt::styled("green debug", fmt::fg(fmt::color::green))); + Log::info("info"); + Log::notice("notice"); + Log::warn("warn"); + Log::error("error"); + Log::fatal("fatal"); + } +}