From fe867fc3febf10b497e98a773f108bdf11d0aaa7 Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Wed, 29 Jan 2025 18:39:02 +0100
Subject: [PATCH 1/5] Added missing binding for Elts struct

---
 include/aidge/scheduler/ProdConso.hpp         |  4 +
 python_binding/data/pybind_Elts.cpp           | 85 +++++++++++++++++++
 python_binding/pybind_core.cpp                |  2 +
 python_binding/scheduler/pybind_ProdConso.cpp |  1 +
 4 files changed, 92 insertions(+)
 create mode 100644 python_binding/data/pybind_Elts.cpp

diff --git a/include/aidge/scheduler/ProdConso.hpp b/include/aidge/scheduler/ProdConso.hpp
index f30e00afa..bc42cb36c 100644
--- a/include/aidge/scheduler/ProdConso.hpp
+++ b/include/aidge/scheduler/ProdConso.hpp
@@ -34,6 +34,10 @@ public:
         return std::make_unique<ProdConso>(op, true);
     }
 
+    const Operator& getOperator() const noexcept {
+        return mOp;
+    }
+
     /**
      * @brief Minimum amount of data from a specific input required by the
      * implementation to be run.
diff --git a/python_binding/data/pybind_Elts.cpp b/python_binding/data/pybind_Elts.cpp
new file mode 100644
index 000000000..59a8211e2
--- /dev/null
+++ b/python_binding/data/pybind_Elts.cpp
@@ -0,0 +1,85 @@
+/********************************************************************************
+ * 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 <algorithm>  // std::transform
+#include <cctype>     // std::tolower
+#include <string>     // std::string
+#include <vector>
+
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <pybind11/operators.h>
+
+#include "aidge/data/Elts.hpp"
+
+namespace py = pybind11;
+namespace Aidge {
+
+template <class T>
+void bindEnum(py::module& m, const std::string& name) {
+    // Define enumeration names for python as lowercase type name
+    // This defined enum names compatible with basic numpy type
+    // name such as: float32, flot64, [u]int32, [u]int64, ...
+    auto python_enum_name = [](const T& type) {
+        auto str_lower = [](std::string& str) {
+            std::transform(str.begin(), str.end(), str.begin(),
+                           [](unsigned char c){
+                               return std::tolower(c);
+                           });
+        };
+        auto type_name = std::string(Aidge::format_as(type));
+        str_lower(type_name);
+        return type_name;
+    };
+
+    // Auto generate enumeration names from lowercase type strings
+    std::vector<std::string> enum_names;
+    for (auto type_str : EnumStrings<T>::data) {
+        auto type = static_cast<T>(enum_names.size());
+        auto enum_name = python_enum_name(type);
+        enum_names.push_back(enum_name);
+    }
+
+    // Define python side enumeration aidge_core.type
+    auto e_type = py::enum_<T>(m, name.c_str());
+
+    // Add enum value for each enum name
+    for (std::size_t idx = 0; idx < enum_names.size(); idx++) {
+        e_type.value(enum_names[idx].c_str(), static_cast<T>(idx));
+    }
+
+    // Define str() to return the bare enum name value, it allows
+    // to compare directly for instance str(tensor.type())
+    // with str(nparray.type)
+    e_type.def("__str__", [enum_names](const T& type) {
+        return enum_names[static_cast<int>(type)];
+    }, py::prepend());
+}
+
+void init_Elts(py::module& m) {
+    bindEnum<Elts_t::EltType>(m, "EltType");
+    m.def("format_as", (const char* (*)(Elts_t::EltType)) &format_as, py::arg("elt"));
+    
+    py::class_<Elts_t, std::shared_ptr<Elts_t>>(
+        m, "Elts_t", py::dynamic_attr())
+        .def_static("none_elts", &Elts_t::NoneElts)
+        .def_static("data_elts", &Elts_t::DataElts, py::arg("data"), py::arg("token") = 1)
+        .def_static("token_elts", &Elts_t::TokenElts, py::arg("token"))
+        .def_readwrite("data", &Elts_t::data)
+        .def_readwrite("token", &Elts_t::token)
+        .def_readwrite("type", &Elts_t::type)
+        .def(py::self + py::self)
+        .def(py::self += py::self)
+        .def(py::self < py::self)
+        .def(py::self > py::self);
+}
+
+} // namespace Aidge
diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp
index 1f35373f3..cc6f0bf25 100644
--- a/python_binding/pybind_core.cpp
+++ b/python_binding/pybind_core.cpp
@@ -21,6 +21,7 @@ void init_Random(py::module&);
 void init_Data(py::module&);
 void init_DataFormat(py::module&);
 void init_DataType(py::module&);
+void init_Elts(py::module&);
 void init_Database(py::module&);
 void init_DataProvider(py::module&);
 void init_Interpolation(py::module&);
@@ -112,6 +113,7 @@ void init_Aidge(py::module& m) {
     init_Data(m);
     init_DataFormat(m);
     init_DataType(m);
+    init_Elts(m);
     init_Database(m);
     init_DataProvider(m);
     init_Interpolation(m);
diff --git a/python_binding/scheduler/pybind_ProdConso.cpp b/python_binding/scheduler/pybind_ProdConso.cpp
index abd6d5379..547e2258d 100644
--- a/python_binding/scheduler/pybind_ProdConso.cpp
+++ b/python_binding/scheduler/pybind_ProdConso.cpp
@@ -104,6 +104,7 @@ void init_ProdConso(py::module& m){
     .def(py::init<const Operator&, bool>(), py::keep_alive<1, 1>(), py::keep_alive<1, 2>(), py::keep_alive<1,3>())
     .def_static("default_model", &ProdConso::defaultModel)
     .def_static("in_place_model", &ProdConso::inPlaceModel)
+    .def("get_operator", &ProdConso::getOperator)
     .def("get_nb_required_data", &ProdConso::getNbRequiredData)
     .def("get_nb_required_protected", &ProdConso::getNbRequiredProtected)
     .def("get_required_memory", &ProdConso::getRequiredMemory)
-- 
GitLab


From 84fac59710ae9d8b1c0dba6b3edd9b786e6a9a76 Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Wed, 29 Jan 2025 18:39:10 +0100
Subject: [PATCH 2/5] Fix ConstantFolding bug

---
 src/recipes/ConstantFolding.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/recipes/ConstantFolding.cpp b/src/recipes/ConstantFolding.cpp
index 40b0bda76..613393756 100644
--- a/src/recipes/ConstantFolding.cpp
+++ b/src/recipes/ConstantFolding.cpp
@@ -36,6 +36,7 @@ void Aidge::constantFolding(std::shared_ptr<GraphView> graph) {
         for (const auto& node : candidates) {
             bool foldable = true;
             auto replaceGraph = std::make_shared<GraphView>();
+            size_t i = 0;
             for (const auto& input : node->inputs()) {
                 if (input.first) {
                     if (input.first->type() != Producer_Op::Type) {
@@ -53,6 +54,13 @@ void Aidge::constantFolding(std::shared_ptr<GraphView> graph) {
 
                     replaceGraph->add(input.first, false);
                 }
+                else if (node->inputCategory(i) != InputCategory::OptionalData
+                    && node->inputCategory(i) != InputCategory::OptionalParam)
+                {
+                    foldable = false;
+                    break;
+                }
+                ++i;
             }
 
             if (foldable) {
-- 
GitLab


From a0fb5ecd7249789b50554fcc026a91dd6f21a9cd Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Thu, 30 Jan 2025 15:55:03 +0100
Subject: [PATCH 3/5] Added getFactorizedScheduling()

---
 include/aidge/scheduler/Scheduler.hpp         |  12 ++
 python_binding/scheduler/pybind_Scheduler.cpp |   1 +
 src/scheduler/Scheduler.cpp                   | 118 ++++++++++++++++++
 3 files changed, 131 insertions(+)

diff --git a/include/aidge/scheduler/Scheduler.hpp b/include/aidge/scheduler/Scheduler.hpp
index 51f62ed1b..75fc15778 100644
--- a/include/aidge/scheduler/Scheduler.hpp
+++ b/include/aidge/scheduler/Scheduler.hpp
@@ -187,6 +187,7 @@ public:
      * @param fileName Name of the file to save the diagram (without extension).
      */
     void saveStaticSchedulingDiagram(const std::string& fileName) const;
+    void saveFactorizedStaticSchedulingDiagram(const std::string& fileName) const;
 
     /**
      * @brief Save in a Mermaid file the order of layers execution.
@@ -233,6 +234,17 @@ protected:
      */
     void generateEarlyLateScheduling(std::vector<StaticSchedulingElement*>& schedule) const;
 
+    /**
+     * @brief Get the factorized scheduling, by identifying repetitive sequences
+     * in the scheduling.
+     *
+     * @param schedule Vector of shared pointers to StaticSchedulingElements to be processed
+     * @return Vector containing the repetitive sequences, in order. The second
+     * element of the pair is the number of repetitions.
+     */
+    std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>>
+        getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule) const;
+
 private:
     /**
      * @brief Summarize the consumer state of a node for debugging purposes.
diff --git a/python_binding/scheduler/pybind_Scheduler.cpp b/python_binding/scheduler/pybind_Scheduler.cpp
index d4cd7da44..10dc66ae8 100644
--- a/python_binding/scheduler/pybind_Scheduler.cpp
+++ b/python_binding/scheduler/pybind_Scheduler.cpp
@@ -32,6 +32,7 @@ void init_Scheduler(py::module& m){
     .def("graph_view", &Scheduler::graphView)
     .def("save_scheduling_diagram", &Scheduler::saveSchedulingDiagram, py::arg("file_name"))
     .def("save_static_scheduling_diagram", &Scheduler::saveStaticSchedulingDiagram, py::arg("file_name"))
+    .def("save_factorized_static_scheduling_diagram", &Scheduler::saveFactorizedStaticSchedulingDiagram, py::arg("file_name"))
     .def("resetScheduling", &Scheduler::resetScheduling)
     .def("generate_scheduling", &Scheduler::generateScheduling)
     .def("get_static_scheduling", &Scheduler::getStaticScheduling, py::arg("step") = 0, py::arg("sorting") = Scheduler::EarlyLateSort::Default)
diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp
index 396e90c09..58c23f708 100644
--- a/src/scheduler/Scheduler.cpp
+++ b/src/scheduler/Scheduler.cpp
@@ -427,6 +427,82 @@ void Aidge::Scheduler::generateEarlyLateScheduling(std::vector<StaticSchedulingE
     }
 }
 
+std::vector<std::pair<std::vector<Aidge::Scheduler::StaticSchedulingElement*>, size_t>>
+Aidge::Scheduler::getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule) const
+{
+    std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>> sequences;
+    size_t offset = 0;
+
+    for (size_t i = 0; i < schedule.size(); ) {
+        std::vector<StaticSchedulingElement*> seq;
+        seq.push_back(new StaticSchedulingElement(
+            schedule[i]->node,
+            schedule[i]->early - offset,
+            schedule[i]->late - offset));
+
+        // Find all the possible repetitive sequences starting from this element
+        std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>> longuestSeq = {std::make_pair(seq, 1)};
+        std::vector<size_t> longuestSeqOffset = {0};
+
+        for (size_t k = i + 1; k < schedule.size() - 1; ++k) {
+            // For each sequence length, starting from 2...
+            seq.push_back(new StaticSchedulingElement(
+                schedule[k]->node,
+                schedule[k]->early - offset,
+                schedule[k]->late - offset));
+
+            size_t start = k + 1;
+            size_t nbRepeats = 1;
+            bool repeat = true;
+            const auto seqOffset = schedule[start]->early - offset - seq[0]->early;
+
+            do {
+                // Count the number of consecutive sequences (repetitions)
+                for (size_t r = 0; r < seq.size(); ++r) {
+                    if (start + r >= schedule.size()
+                        || schedule[start + r]->node != seq[r]->node
+                        || schedule[start + r]->early - offset != seq[r]->early + seqOffset * nbRepeats
+                        || schedule[start + r]->late - offset != seq[r]->late + seqOffset * nbRepeats)
+                    {
+                        repeat = false;
+                        break;
+                    }
+                }
+
+                if (repeat) {
+                    start += seq.size();
+                    ++nbRepeats;
+                }
+            }
+            while (repeat);
+
+            if (nbRepeats > 1) {
+                // If repetitions exist for this sequence length, add it to the list
+                longuestSeq.push_back(std::make_pair(seq, nbRepeats));
+                longuestSeqOffset.push_back(seqOffset);
+            }
+        }
+
+        // Select the one with the best factorization
+        // i.e. which maximize the product sequence length * number of sequences
+        size_t maxS = 0;
+        size_t maxFactorization = 0;
+        for (size_t s = 0; s < longuestSeq.size(); ++s) {
+            const auto factor = longuestSeq[s].first.size() * longuestSeq[s].second;
+            if (factor > maxFactorization) {
+                maxFactorization = factor;
+                maxS = s;
+            }
+        }
+
+        sequences.push_back(longuestSeq[maxS]);
+        i += longuestSeq[maxS].first.size() * longuestSeq[maxS].second;
+        offset += longuestSeqOffset[maxS] * (longuestSeq[maxS].second - 1);
+    }
+
+    return sequences;
+}
+
 void Aidge::Scheduler::resetScheduling() {
     for (auto node : mGraphView->getNodes()) {
         node->getOperator()->resetConsummerProducer();
@@ -878,6 +954,48 @@ void Aidge::Scheduler::saveStaticSchedulingDiagram(const std::string& fileName)
     fmt::print(fp.get(), "\n");
 }
 
+void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string& fileName) const {
+    auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen((fileName + ".mmd").c_str(), "w"), &std::fclose);
+
+    if (!fp) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error,
+            "Could not create scheduling diagram log file: {}", fileName + ".mmd");
+    }
+
+    fmt::print(fp.get(), "gantt\ndateFormat x\naxisFormat %Q\n\n");
+
+    if (!mStaticSchedule.empty()) {
+        const std::map<std::shared_ptr<Node>, std::string> namePtrTable
+            = mGraphView->getRankedNodesName("{0} ({1}#{3})");
+
+        for (const auto& schedule : mStaticSchedule) {
+            const auto factorizedSchedule = getFactorizedScheduling(schedule);
+
+            size_t seq = 0;
+            for (const auto& sequence : factorizedSchedule) {
+                if (sequence.second > 1) {
+                    fmt::print(fp.get(), "section seq#{} (x{})\n", seq, sequence.second);
+                }
+                else {
+                    fmt::print(fp.get(), "section seq#{}\n", seq);
+                }
+
+                for (const auto& element : sequence.first) {
+                    auto name = namePtrTable.at(element->node);
+                    // Mermaid does not allow : character in task title
+                    std::replace(name.begin(), name.end(), ':', '_');
+
+                    fmt::print(fp.get(), "{} :{}, {}\n",
+                                name, element->early, element->late);
+                }
+                ++seq;
+            }
+        }
+    }
+
+    fmt::print(fp.get(), "\n");
+}
+
 std::vector<std::shared_ptr<Aidge::Node>> Aidge::Scheduler::getStaticScheduling(std::size_t step, EarlyLateSort sorting) const {
     AIDGE_ASSERT(!mStaticSchedule.empty(), "Scheduler::getStaticScheduling(): static scheduling is empty, did you generate scheduling first?");
     AIDGE_ASSERT(step < mStaticSchedule.size(), "Scheduler::getStaticScheduling(): no static scheduling at step {} (available steps: {})", mStaticSchedule.size(), step);
-- 
GitLab


From f222cd7c8de37f6c0fca5eb0bc2088f5231fe60c Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Thu, 30 Jan 2025 16:07:07 +0100
Subject: [PATCH 4/5] Improved display

---
 src/scheduler/Scheduler.cpp | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp
index 58c23f708..26bb357e7 100644
--- a/src/scheduler/Scheduler.cpp
+++ b/src/scheduler/Scheduler.cpp
@@ -945,8 +945,14 @@ void Aidge::Scheduler::saveStaticSchedulingDiagram(const std::string& fileName)
                 // Mermaid does not allow : character in task title
                 std::replace(name.begin(), name.end(), ':', '_');
 
-                fmt::print(fp.get(), "{} :{}, {}\n",
-                            name, element->early, element->late);
+                if (element->early == element->late) {
+                    fmt::print(fp.get(), "{} :milestone, {}, {}\n",
+                                name, element->early, element->late);
+                }
+                else {
+                    fmt::print(fp.get(), "{} :{}, {}\n",
+                                name, element->early, element->late);
+                }
             }
         }
     }
@@ -985,8 +991,14 @@ void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string&
                     // Mermaid does not allow : character in task title
                     std::replace(name.begin(), name.end(), ':', '_');
 
-                    fmt::print(fp.get(), "{} :{}, {}\n",
-                                name, element->early, element->late);
+                    if (element->early == element->late) {
+                        fmt::print(fp.get(), "{} :milestone, {}, {}\n",
+                                    name, element->early, element->late);
+                    }
+                    else {
+                        fmt::print(fp.get(), "{} :{}, {}\n",
+                                    name, element->early, element->late);
+                    }
                 }
                 ++seq;
             }
-- 
GitLab


From 1c5925053973884351e5040ec42b7bdb12dd57bc Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Thu, 30 Jan 2025 17:47:58 +0100
Subject: [PATCH 5/5] Small refinement in factorize

---
 include/aidge/scheduler/Scheduler.hpp         |  5 ++-
 python_binding/scheduler/pybind_Scheduler.cpp |  2 +-
 src/scheduler/Scheduler.cpp                   | 44 ++++++++++---------
 3 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/include/aidge/scheduler/Scheduler.hpp b/include/aidge/scheduler/Scheduler.hpp
index 75fc15778..db9b903cc 100644
--- a/include/aidge/scheduler/Scheduler.hpp
+++ b/include/aidge/scheduler/Scheduler.hpp
@@ -187,7 +187,7 @@ public:
      * @param fileName Name of the file to save the diagram (without extension).
      */
     void saveStaticSchedulingDiagram(const std::string& fileName) const;
-    void saveFactorizedStaticSchedulingDiagram(const std::string& fileName) const;
+    void saveFactorizedStaticSchedulingDiagram(const std::string& fileName, size_t minRepeat = 2) const;
 
     /**
      * @brief Save in a Mermaid file the order of layers execution.
@@ -239,11 +239,12 @@ protected:
      * in the scheduling.
      *
      * @param schedule Vector of shared pointers to StaticSchedulingElements to be processed
+     * @param size_t Minimum number repetitions to factorize the sequence
      * @return Vector containing the repetitive sequences, in order. The second
      * element of the pair is the number of repetitions.
      */
     std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>>
-        getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule) const;
+        getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule, size_t minRepeat = 2) const;
 
 private:
     /**
diff --git a/python_binding/scheduler/pybind_Scheduler.cpp b/python_binding/scheduler/pybind_Scheduler.cpp
index 10dc66ae8..1a3a4b6b2 100644
--- a/python_binding/scheduler/pybind_Scheduler.cpp
+++ b/python_binding/scheduler/pybind_Scheduler.cpp
@@ -32,7 +32,7 @@ void init_Scheduler(py::module& m){
     .def("graph_view", &Scheduler::graphView)
     .def("save_scheduling_diagram", &Scheduler::saveSchedulingDiagram, py::arg("file_name"))
     .def("save_static_scheduling_diagram", &Scheduler::saveStaticSchedulingDiagram, py::arg("file_name"))
-    .def("save_factorized_static_scheduling_diagram", &Scheduler::saveFactorizedStaticSchedulingDiagram, py::arg("file_name"))
+    .def("save_factorized_static_scheduling_diagram", &Scheduler::saveFactorizedStaticSchedulingDiagram, py::arg("file_name"), py::arg("min_repeat") = 2)
     .def("resetScheduling", &Scheduler::resetScheduling)
     .def("generate_scheduling", &Scheduler::generateScheduling)
     .def("get_static_scheduling", &Scheduler::getStaticScheduling, py::arg("step") = 0, py::arg("sorting") = Scheduler::EarlyLateSort::Default)
diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp
index 26bb357e7..bbbc3d807 100644
--- a/src/scheduler/Scheduler.cpp
+++ b/src/scheduler/Scheduler.cpp
@@ -428,24 +428,19 @@ void Aidge::Scheduler::generateEarlyLateScheduling(std::vector<StaticSchedulingE
 }
 
 std::vector<std::pair<std::vector<Aidge::Scheduler::StaticSchedulingElement*>, size_t>>
-Aidge::Scheduler::getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule) const
+Aidge::Scheduler::getFactorizedScheduling(const std::vector<StaticSchedulingElement*>& schedule, size_t minRepeat) const
 {
     std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>> sequences;
     size_t offset = 0;
 
     for (size_t i = 0; i < schedule.size(); ) {
-        std::vector<StaticSchedulingElement*> seq;
-        seq.push_back(new StaticSchedulingElement(
-            schedule[i]->node,
-            schedule[i]->early - offset,
-            schedule[i]->late - offset));
-
         // Find all the possible repetitive sequences starting from this element
-        std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>> longuestSeq = {std::make_pair(seq, 1)};
-        std::vector<size_t> longuestSeqOffset = {0};
+        std::vector<StaticSchedulingElement*> seq;
+        std::vector<std::pair<std::vector<StaticSchedulingElement*>, size_t>> longuestSeq;
+        std::vector<size_t> longuestSeqOffset;
 
-        for (size_t k = i + 1; k < schedule.size() - 1; ++k) {
-            // For each sequence length, starting from 2...
+        for (size_t k = i; k < schedule.size(); ++k) {
+            // For each sequence length, starting from 1...
             seq.push_back(new StaticSchedulingElement(
                 schedule[k]->node,
                 schedule[k]->early - offset,
@@ -454,7 +449,7 @@ Aidge::Scheduler::getFactorizedScheduling(const std::vector<StaticSchedulingElem
             size_t start = k + 1;
             size_t nbRepeats = 1;
             bool repeat = true;
-            const auto seqOffset = schedule[start]->early - offset - seq[0]->early;
+            const auto seqOffset = (start < schedule.size()) ? schedule[start]->early - offset - seq[0]->early : 0;
 
             do {
                 // Count the number of consecutive sequences (repetitions)
@@ -476,11 +471,17 @@ Aidge::Scheduler::getFactorizedScheduling(const std::vector<StaticSchedulingElem
             }
             while (repeat);
 
-            if (nbRepeats > 1) {
+            if (nbRepeats >= minRepeat) {
                 // If repetitions exist for this sequence length, add it to the list
                 longuestSeq.push_back(std::make_pair(seq, nbRepeats));
                 longuestSeqOffset.push_back(seqOffset);
             }
+            else if (k == i) {
+                // Ensure that at least the current element is in the list if no
+                // repetition is found
+                longuestSeq.push_back(std::make_pair(seq, 1));
+                longuestSeqOffset.push_back(0);
+            }
         }
 
         // Select the one with the best factorization
@@ -960,7 +961,7 @@ void Aidge::Scheduler::saveStaticSchedulingDiagram(const std::string& fileName)
     fmt::print(fp.get(), "\n");
 }
 
-void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string& fileName) const {
+void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string& fileName, size_t minRepeat) const {
     auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen((fileName + ".mmd").c_str(), "w"), &std::fclose);
 
     if (!fp) {
@@ -975,7 +976,7 @@ void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string&
             = mGraphView->getRankedNodesName("{0} ({1}#{3})");
 
         for (const auto& schedule : mStaticSchedule) {
-            const auto factorizedSchedule = getFactorizedScheduling(schedule);
+            const auto factorizedSchedule = getFactorizedScheduling(schedule, minRepeat);
 
             size_t seq = 0;
             for (const auto& sequence : factorizedSchedule) {
@@ -990,15 +991,18 @@ void Aidge::Scheduler::saveFactorizedStaticSchedulingDiagram(const std::string&
                     auto name = namePtrTable.at(element->node);
                     // Mermaid does not allow : character in task title
                     std::replace(name.begin(), name.end(), ':', '_');
+                    std::string tag = ":";
 
                     if (element->early == element->late) {
-                        fmt::print(fp.get(), "{} :milestone, {}, {}\n",
-                                    name, element->early, element->late);
+                        tag += "milestone, ";
                     }
-                    else {
-                        fmt::print(fp.get(), "{} :{}, {}\n",
-                                    name, element->early, element->late);
+
+                    if (sequence.second > 1) {
+                        tag += "active, ";
                     }
+
+                    fmt::print(fp.get(), "{} {}{}, {}\n",
+                                name, tag, element->early, element->late);
                 }
                 ++seq;
             }
-- 
GitLab