Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Commit

Permalink
Basic Graph infra in C++
Browse files Browse the repository at this point in the history
  • Loading branch information
iopapamanoglou committed Oct 30, 2024
1 parent 2f9eba9 commit 556fcb1
Show file tree
Hide file tree
Showing 8 changed files with 557 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/faebryk/core/cpp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def compile_and_load():
+ "\n"
+ "# This file is auto-generated by nanobind.\n"
+ "# Do not edit this file directly; edit the corresponding\n"
+ "# C++ file instead."
+ "# C++ file instead.\n"
+ "from typing import overload\n"
+ pyi_source.read_text(),
is_pyi=True,
)
Expand Down
52 changes: 51 additions & 1 deletion src/faebryk/core/cpp/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@
# This file is auto-generated by nanobind.
# Do not edit this file directly; edit the corresponding
# C++ file instead.
from typing import overload

def add(arg0: int, arg1: int, /) -> int:
class Graph:
def __init__(self) -> None: ...
def get_edges(self, arg: GraphInterface, /) -> set[GraphInterface]: ...
def invalidate(self) -> None: ...
@property
def node_count(self) -> int: ...
@property
def edge_count(self) -> int: ...
def __repr__(self) -> str: ...

class GraphInterface:
def __init__(self) -> None: ...
def __repr__(self) -> str: ...
def get_graph(self) -> Graph: ...
def get_edges(self) -> set[GraphInterface]: ...
@overload
def connect(self, arg: GraphInterface, /) -> None: ...
@overload
def connect(self, arg0: GraphInterface, arg1: Link, /) -> None: ...
@overload
def connect(self, arg0: GraphInterface, arg1: type, /) -> None: ...

class GraphInterfaceHierarchical(GraphInterface):
def __init__(self, is_parent: bool) -> None: ...

class GraphInterfaceReference(GraphInterface):
def __init__(self) -> None: ...

class GraphInterfaceSelf(GraphInterface):
def __init__(self) -> None: ...

class Link:
pass

class LinkDirect:
pass

class LinkNamedParent:
pass

class LinkParent:
pass

class LinkPointer:
pass

class LinkSibling:
pass

def add(i: int, j: int = 1) -> int:
"""A function that adds two numbers"""
258 changes: 258 additions & 0 deletions src/faebryk/core/cpp/include/graph/graph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/* This file is part of the faebryk project
* SPDX-License-Identifier: MIT
*/

#pragma once

#include "util.hpp"
#include <nanobind/stl/pair.h>
#include <nanobind/stl/shared_ptr.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/unordered_set.h>
#include <sstream>
#include <unordered_map>
#include <vector>

namespace nb = nanobind;

template <typename T> using Set = std::unordered_set<T>;
template <typename K, typename V> using Map = std::unordered_map<K, V>;

class Graph;
class GraphInterface;
class Link;

class Node {};

using GI_ref = std::shared_ptr<GraphInterface>;
using GI_ref_weak = GraphInterface *;
using Link_ref = std::shared_ptr<Link>;

// TODO: think about how to fix the cyclic shared_ptrs GraphInterface <-> Graph
// we want to keep both objects alive as long as either is in use in python
// https://nanobind.readthedocs.io/en/latest/typeslots.html#cyclic-garbage-collection

class GraphInterface {
protected:
void register_graph(std::shared_ptr<GraphInterface> gi);

public:
GraphInterface();

std::shared_ptr<Graph> G;

~GraphInterface();

static std::shared_ptr<GraphInterface> factory() {
auto gi = std::make_shared<GraphInterface>();
gi->register_graph(gi);
return gi;
}

// Graph stuff
std::unordered_set<GI_ref_weak> get_edges();

std::shared_ptr<Graph> get_graph() {
return this->G;
}

void connect(GI_ref_weak other);
void connect(GI_ref_weak other, nb::type_object link_type);
void connect(GI_ref_weak other, Link_ref link);

// TODO remove, gives class vtable
virtual void do_stuff() {};

std::string repr() {
std::stringstream ss;
ss << "<" << get_type_name(this) << " at "
<< this
//<< " #" << this->shared_from_this().use_count()
<< ">";
return ss.str();
}
};

class Link {
GI_ref_weak from;
GI_ref_weak to;

protected:
Link(GI_ref_weak from, GI_ref_weak to)
: from(from)
, to(to) {
}

public:
std::pair<GI_ref_weak, GI_ref_weak> get_connections() {
return {this->from, this->to};
}
};

/**
* @brief Represents a direct link between two interfaces of the same type
*
*/
class LinkDirect : public Link {
public:
LinkDirect(GI_ref_weak from, GI_ref_weak to)
: Link(from, to) {
}
};

#include "links.hpp"

class Graph {
Set<GI_ref> v;
std::vector<std::tuple<GI_ref_weak, GI_ref_weak, Link_ref>> e;
Map<GI_ref_weak, Map<GI_ref_weak, Link_ref>> e_cache = {};
Map<GI_ref_weak, Set<GI_ref_weak>> e_cache_simple = {};
bool invalidated = false;

public:
void hold(GI_ref gi) {
this->v.insert(gi);
}

void add_edge(GI_ref_weak from, GI_ref_weak to, Link_ref link) {
printf("add_edge from %s to %s\n", from->repr().c_str(), to->repr().c_str());

if (from->G.get() != this || to->G.get() != this) {
Graph *G_target = this;
Graph *G_source;
if (from->G.get() == G_target) {
G_source = to->G.get();
} else if (to->G.get() == G_target) {
G_source = from->G.get();
} else {
throw std::runtime_error("neither node in graph");
}
printf("Merging graphs: %p ---> %p\n", G_source, G_target);

assert(G_source->v.size() > 0);
// make shared_ptr, while in scope
auto G_source_hold = (*G_source->v.begin())->G;

for (auto &v : G_source->v) {
v->G = from->G;
}
G_target->merge(*G_source);
G_source->invalidate();
}

e_cache_simple[from].insert(to);
e_cache_simple[to].insert(from);
e_cache[from][to] = link;
e_cache[to][from] = link;
e.push_back(std::make_tuple(from, to, link));
}

void merge(Graph &other) {
this->v.merge(other.v);
this->e.insert(this->e.end(), other.e.begin(), other.e.end());
this->e_cache.merge(other.e_cache);
this->e_cache_simple.merge(other.e_cache_simple);
}

std::unordered_set<GI_ref_weak> get_edges(GI_ref_weak from) {
return this->e_cache_simple[from];
}

Graph() {
printf("Graph::Graph(): %p\n", this);
}

~Graph() {
printf("Graph::~Graph(): %p\n", this);
if (!this->invalidated) {
printf("WARNING: graph not invalidated\n");
// throw std::runtime_error("graph not invalidated");
}
}

void remove_node(GI_ref node) {
// basically never happens
auto node_ptr = node.get();
// v
this->v.erase(node);
// nb::dec_ref(node);

// e_cache_simple
for (auto &[from, tos] : this->e_cache_simple) {
tos.erase(node_ptr);
}
this->e_cache_simple.erase(node_ptr);

// e_cache
for (auto &[to, link] : this->e_cache[node_ptr]) {
this->e_cache[to].erase(node_ptr);
}
this->e_cache.erase(node_ptr);

// e
std::erase_if(this->e, [node_ptr](const auto &edge) {
return std::get<0>(edge) == node_ptr || std::get<1>(edge) == node_ptr;
});
}

void check_destruct() {
}

void invalidate() {
printf("Invalidating graph: %p\n", this);
this->invalidated = true;
this->v.clear();
}

int node_count() {
return this->v.size();
}

int edge_count() {
return this->e.size();
}

std::string repr() {
std::stringstream ss;
ss << "<Graph[V:" << this->node_count() << ", E:" << this->edge_count()
<< "] at " << this << ">";
return ss.str();
}
};

Set<GI_ref_weak> GraphInterface::get_edges() {
return this->G->get_edges(this);
}

void GraphInterface::connect(GI_ref_weak other) {
auto link = std::make_shared<LinkDirect>(this, other);
this->connect(other, link);
}

void GraphInterface::connect(GI_ref_weak other, nb::type_object link_type) {
// printf("Link type: %s\n", link_type)
// TODO
if (!link_type.is_type()) {
throw std::runtime_error("not a type");
}
throw std::runtime_error("not implemented");
auto link = std::make_shared<LinkDirect>(this, other);
this->connect(other, link);
}

void GraphInterface::connect(GI_ref_weak other, Link_ref link) {
this->G->add_edge(this, other, link);
}

GraphInterface::~GraphInterface() {
printf("%s::~GraphInterface(): %p\n", get_type_name(this).c_str(), this);
}

GraphInterface::GraphInterface()
: G(std::make_shared<Graph>()) {
printf("GraphInterface::GraphInterface(): %p\n", this);
}

void GraphInterface::register_graph(std::shared_ptr<GraphInterface> gi) {
this->G->hold(gi);
}
53 changes: 53 additions & 0 deletions src/faebryk/core/cpp/include/graphinterfaces.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* This file is part of the faebryk project
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <graph/graph.hpp>

class GraphInterfaceSelf : public GraphInterface {
public:
GraphInterfaceSelf()
: GraphInterface() {
}

static std::shared_ptr<GraphInterfaceSelf> factory() {
auto gi = std::make_shared<GraphInterfaceSelf>();
gi->register_graph(gi);
return gi;
}
};

class GraphInterfaceHierarchical : public GraphInterface {
bool is_parent;

public:
GraphInterfaceHierarchical(bool is_parent)
: GraphInterface()
, is_parent(is_parent) {
}

static std::shared_ptr<GraphInterfaceHierarchical> factory(bool is_parent) {
auto gi = std::make_shared<GraphInterfaceHierarchical>(is_parent);
gi->register_graph(gi);
return gi;
}

bool get_is_parent() {
return this->is_parent;
}
};

class GraphInterfaceReference : public GraphInterface {
public:
GraphInterfaceReference()
: GraphInterface() {
}

static std::shared_ptr<GraphInterfaceReference> factory() {
auto gi = std::make_shared<GraphInterfaceReference>();
gi->register_graph(gi);
return gi;
}
};
Loading

0 comments on commit 556fcb1

Please sign in to comment.