Program Listing for File subgraph.hpp
↰ Return to documentation for file (include/holoscan/core/subgraph.hpp)
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef HOLOSCAN_CORE_SUBGRAPH_HPP
#define HOLOSCAN_CORE_SUBGRAPH_HPP
#include <fmt/format.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <utility>
#include <type_traits>
#include <vector>
#include "io_spec.hpp"
namespace holoscan {
// Forward declarations
class Fragment;
class Operator;
class OperatorSpec;
struct InterfacePort {
enum class PortType {
kData,
kExecution
};
std::shared_ptr<Operator> internal_operator;
std::string internal_port_name;
bool is_input;
PortType port_type = PortType::kData;
};
class Subgraph {
public:
Subgraph(Fragment* fragment, const std::string& instance_name);
virtual ~Subgraph() = default;
virtual void compose() = 0;
const std::string& instance_name() const { return instance_name_; }
Fragment* fragment() { return fragment_; }
const Fragment* fragment() const { return fragment_; }
std::string get_qualified_name(const std::string& object_name,
const std::string& type_name = "operator") const {
if (instance_name_.empty()) {
return object_name;
}
if (instance_name_ == object_name) {
auto err_msg = fmt::format(
"Subgraph: child {} name '{}' is the same as the parent subgraph name '{}'. "
"This is not allowed.",
type_name,
object_name,
instance_name_);
HOLOSCAN_LOG_ERROR(err_msg);
throw std::runtime_error(err_msg);
}
// Check if the operator name is already qualified with this instance name
std::string expected_prefix = instance_name_ + "_";
if (object_name.size() >= expected_prefix.size() &&
object_name.substr(0, expected_prefix.size()) == expected_prefix) {
HOLOSCAN_LOG_TRACE(
"Subgraph: {} name '{}' is already qualified with instance name '{}', returning "
"as-is",
type_name,
object_name,
instance_name_);
return object_name; // Already qualified, return as-is
}
return fmt::format("{}_{}", instance_name_, object_name);
}
// Fragment API compatibility - template method implementations in fragment.hpp
template <typename OperatorT, typename StringT, typename... ArgsT,
typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
std::shared_ptr<OperatorT> make_operator(StringT name, ArgsT&&... args);
template <typename OperatorT, typename... ArgsT>
std::shared_ptr<OperatorT> make_operator(ArgsT&&... args);
template <typename ConditionT, typename StringT, typename... ArgsT,
typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
std::shared_ptr<ConditionT> make_condition(StringT name, ArgsT&&... args);
template <typename ConditionT, typename... ArgsT>
std::shared_ptr<ConditionT> make_condition(ArgsT&&... args);
template <typename ResourceT, typename StringT, typename... ArgsT,
typename = std::enable_if_t<std::is_constructible_v<std::string, StringT>>>
std::shared_ptr<ResourceT> make_resource(StringT name, ArgsT&&... args);
template <typename ResourceT, typename... ArgsT>
std::shared_ptr<ResourceT> make_resource(ArgsT&&... args);
template <typename SubgraphT, typename... ArgsT>
std::shared_ptr<SubgraphT> make_subgraph(const std::string& child_instance_name,
ArgsT&&... args) {
// Check for duplicate nested subgraph instance names
if (nested_subgraph_instance_names_.find(child_instance_name) !=
nested_subgraph_instance_names_.end()) {
throw std::runtime_error(fmt::format(
"Subgraph::make_subgraph: Duplicate nested subgraph instance name '{}' in subgraph "
"'{}'. Each nested subgraph instance must have a unique name within the same parent "
"subgraph.",
child_instance_name,
instance_name_));
}
// Create hierarchical instance name: parent_instance + "_" + child_instance
std::string hierarchical_name = get_qualified_name(child_instance_name, "subgraph");
// Create nested subgraph with same Fragment* and hierarchical name
auto nested_subgraph =
std::make_shared<SubgraphT>(fragment_, hierarchical_name, std::forward<ArgsT>(args)...);
// Compose immediately - operators added directly to Fragment's main graph
if (!nested_subgraph->is_composed()) {
nested_subgraph->compose();
nested_subgraph->set_composed(true);
}
// Store for interface port resolution
nested_subgraphs_.push_back(nested_subgraph);
// Register the child instance name
nested_subgraph_instance_names_.insert(child_instance_name);
return nested_subgraph;
}
void add_operator(std::shared_ptr<Operator> op);
void add_flow(const std::shared_ptr<Operator>& upstream,
const std::shared_ptr<Operator>& downstream,
std::set<std::pair<std::string, std::string>> port_pairs = {});
void add_flow(const std::shared_ptr<Operator>& upstream_op,
const std::shared_ptr<Subgraph>& downstream_subgraph,
std::set<std::pair<std::string, std::string>> port_pairs = {});
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Operator>& downstream_op,
std::set<std::pair<std::string, std::string>> port_pairs = {});
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Subgraph>& downstream_subgraph,
std::set<std::pair<std::string, std::string>> port_pairs = {});
void add_flow(const std::shared_ptr<Operator>& upstream_op,
const std::shared_ptr<Subgraph>& downstream_subgraph,
const IOSpec::ConnectorType connector_type);
void add_flow(const std::shared_ptr<Operator>& upstream_op,
const std::shared_ptr<Subgraph>& downstream_subgraph,
std::set<std::pair<std::string, std::string>> port_pairs,
const IOSpec::ConnectorType connector_type);
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Operator>& downstream_op,
const IOSpec::ConnectorType connector_type);
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Operator>& downstream_op,
std::set<std::pair<std::string, std::string>> port_pairs,
const IOSpec::ConnectorType connector_type);
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Subgraph>& downstream_subgraph,
const IOSpec::ConnectorType connector_type);
void add_flow(const std::shared_ptr<Subgraph>& upstream_subgraph,
const std::shared_ptr<Subgraph>& downstream_subgraph,
std::set<std::pair<std::string, std::string>> port_pairs,
const IOSpec::ConnectorType connector_type);
void add_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op,
const std::string& internal_port, bool is_input);
void add_input_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op,
const std::string& internal_port);
void add_output_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op,
const std::string& internal_port);
void add_interface_port(const std::string& external_name,
const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port, bool is_input);
void add_input_interface_port(const std::string& external_name,
const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port);
void add_output_interface_port(const std::string& external_name,
const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port);
// ========== Execution Interface Port Methods ==========
void add_input_exec_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op);
void add_output_exec_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op);
void add_input_exec_interface_port(const std::string& external_name,
const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port);
void add_output_exec_interface_port(const std::string& external_name,
const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port);
const std::unordered_map<std::string, InterfacePort>& interface_ports() const {
return interface_ports_;
}
const std::unordered_map<std::string, InterfacePort>& exec_interface_ports() const {
return exec_interface_ports_;
}
std::pair<std::shared_ptr<Operator>, std::string> get_interface_operator_port(
const std::string& port_name) const;
std::pair<std::shared_ptr<Operator>, std::string> get_exec_interface_operator_port(
const std::string& port_name) const;
bool is_composed() const { return is_composed_; }
void set_composed(bool composed) { is_composed_ = composed; }
private:
std::unordered_map<std::string, InterfacePort> interface_ports_;
std::unordered_map<std::string, InterfacePort>
exec_interface_ports_;
std::vector<std::shared_ptr<Subgraph>>
nested_subgraphs_;
std::unordered_set<std::string>
nested_subgraph_instance_names_;
bool is_composed_ = false;
Fragment* fragment_;
const std::string instance_name_;
std::string format_port_list(
const std::unordered_map<std::string, std::shared_ptr<IOSpec>>& ports);
bool validate_operator_port(std::shared_ptr<Operator> op, const std::string& port_name,
bool expect_input);
bool validate_operator_exec_port(std::shared_ptr<Operator> op);
bool check_exec_port_name_available(const std::string& external_name);
void register_exec_interface_port(const std::string& external_name,
const std::shared_ptr<Operator>& internal_op,
const std::string& internal_port_name, bool is_input);
std::pair<std::shared_ptr<Operator>, std::string> resolve_nested_exec_port(
const std::string& external_name, const std::shared_ptr<Subgraph>& internal_subgraph,
const std::string& internal_interface_port, bool expect_input);
};
// Template method implementations moved to fragment.hpp to resolve circular dependency
} // namespace holoscan
#endif/* HOLOSCAN_CORE_SUBGRAPH_HPP */