Skip to content

Instantly share code, notes, and snippets.

@adriangb
Last active November 24, 2021 08:59
Show Gist options
  • Save adriangb/9d4561fa9ac04eb59dafc324b0550a60 to your computer and use it in GitHub Desktop.
Save adriangb/9d4561fa9ac04eb59dafc324b0550a60 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 9,
"id": "bf58a19a",
"metadata": {},
"outputs": [],
"source": [
"from typing import *\n",
"\n",
"import di_lib\n",
"import graphlib"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c43d18d4",
"metadata": {},
"outputs": [],
"source": [
"from random import Random\n",
"import networkx as nx\n",
"\n",
"\n",
"random = Random(42)\n",
"\n",
"\n",
"def get_linear_graph(n: int) -> Dict[int, List[int]]:\n",
" graph: Dict[int, List[int]] = {}\n",
" i = 0\n",
" for i in range(n):\n",
" graph[i] = [i+1] # node 0 depends on node 1, etc.\n",
" graph[i+1] = [] # node n has no dependencies\n",
" return graph\n",
"\n",
"def get_random_graph(n: int) -> Dict[int, List[int]]:\n",
" G = nx.gnp_random_graph(n, 0.5, directed=True)\n",
" DAG = nx.DiGraph([(u,v,{'weight': random.randint(-10, 10)}) for (u,v) in G.edges() if u<v])\n",
" return nx.convert.to_dict_of_lists(DAG)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "fb648e52",
"metadata": {},
"outputs": [],
"source": [
"def run(t: Union[graphlib.TopologicalSorter, di_lib.Graph]) -> None:\n",
" to_remove = t.get_ready()\n",
" while t.is_active():\n",
" t.done(*to_remove)\n",
" to_remove = t.get_ready()\n",
"\n",
"\n",
"def setup_rust(n: int, graph_gen: Callable[[int], Dict[int, List[int]]]) -> di_lib.Graph:\n",
" return di_lib.Graph(graph_gen(n))\n",
"\n",
"\n",
"def setup_python(n: int, graph_gen: Callable[[int], Dict[int, List[int]]]) -> graphlib.TopologicalSorter:\n",
" t = graphlib.TopologicalSorter(graph_gen(n))\n",
" t.prepare()\n",
" return t"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "3191f318",
"metadata": {},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"import timeit\n",
"\n",
"lower = 0\n",
"upper = 100_000\n",
"length = 50\n",
"loops = 100\n",
"\n",
"glbls = {\n",
" \"setup_python\": setup_python,\n",
" \"setup_rust\": setup_rust,\n",
" \"run\": run,\n",
" \"get_linear_graph\": get_linear_graph,\n",
" \"get_random_graph\": get_random_graph\n",
"}\n",
"\n",
"\n",
"\n",
"def plot(upper: int, samples: int, graph_factory: str) -> None:\n",
" x = [round(lower + x*(upper-lower)/samples) for x in range(samples)]\n",
" y_python: List[float] = []\n",
" y_rust: List[float] = []\n",
"\n",
" for n in x:\n",
" # Time Python and get the # of loops and match that\n",
" timerpy = timeit.Timer(stmt=\"run(t)\", setup=f\"t = setup_python({n}, {graph_factory})\", globals=glbls)\n",
" pytime = timerpy.timeit(loops)\n",
" y_python.append(pytime)\n",
" timerust = timeit.Timer(stmt=\"run(t)\", setup=f\"t = setup_rust({n}, {graph_factory})\", globals=glbls)\n",
" rustime = timerust.timeit(loops)\n",
" y_rust.append(rustime)\n",
" plt.plot(x, y_python, label=\"python\")\n",
" plt.plot(x, y_rust, label=\"rust\")\n",
" plt.legend(loc=\"upper left\")\n",
" plt.xlabel(\"V (number of vertices)\")\n",
" plt.ylabel(\"Execution time (s)\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "fcafdd12",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"plot(upper=100_000, samples=50, graph_factory=\"get_linear_graph\")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "11ba828c",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"plot(upper=1_000, samples=50, graph_factory=\"get_random_graph\")"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "58237ce0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(8.739999902900309e-05,\n",
" 5.859998054802418e-06,\n",
" 2.9899980290792883e-06,\n",
" 5.649999366141856e-06)"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import statistics\n",
"\n",
"py_get_ready: List[float] = []\n",
"py_done: List[float] = []\n",
"\n",
"rust_get_ready: List[float] = []\n",
"rust_done: List[float] = []\n",
"\n",
"for _ in range(10):\n",
" t = setup_python(1_000, get_linear_graph)\n",
" start = timeit.default_timer()\n",
" ready = t.get_ready()\n",
" py_get_ready.append(timeit.default_timer()-start)\n",
" start = timeit.default_timer()\n",
" t.done(*ready)\n",
" py_done.append(timeit.default_timer()-start)\n",
"\n",
" t = setup_rust(1_000, get_linear_graph)\n",
" start = timeit.default_timer()\n",
" ready = t.get_ready()\n",
" rust_get_ready.append(timeit.default_timer()-start)\n",
" start = timeit.default_timer()\n",
" t.done(*ready)\n",
" rust_done.append(timeit.default_timer()-start)\n",
"\n",
"statistics.mean(py_get_ready),statistics.mean(py_done),statistics.mean(rust_get_ready),statistics.mean(rust_done)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "bcc43114",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(1.4799999189563095e-05,\n",
" 0.00011145999887958169,\n",
" 8.730002446100115e-06,\n",
" 6.569000106537715e-05)"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import statistics\n",
"\n",
"py_get_ready: List[float] = []\n",
"py_done: List[float] = []\n",
"\n",
"rust_get_ready: List[float] = []\n",
"rust_done: List[float] = []\n",
"\n",
"for _ in range(10):\n",
" t = setup_python(1_000, get_random_graph)\n",
" start = timeit.default_timer()\n",
" ready = t.get_ready()\n",
" py_get_ready.append(timeit.default_timer()-start)\n",
" start = timeit.default_timer()\n",
" t.done(*ready)\n",
" py_done.append(timeit.default_timer()-start)\n",
"\n",
" t = setup_rust(1_000, get_random_graph)\n",
" start = timeit.default_timer()\n",
" ready = t.get_ready()\n",
" rust_get_ready.append(timeit.default_timer()-start)\n",
" start = timeit.default_timer()\n",
" t.done(*ready)\n",
" rust_done.append(timeit.default_timer()-start)\n",
"\n",
"statistics.mean(py_get_ready),statistics.mean(py_done),statistics.mean(rust_get_ready),statistics.mean(rust_done)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aacf1192",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
use std::cmp;
use std::hash;
use std::fmt;
use pyo3::basic::CompareOp;
use pyo3::prelude::*;
// We can't put a Py<PyAny> directly into a HashMap key
// So to be able to hold references to arbitrary Python objects in HashMap as keys
// we wrap them in a struct that gets the hash() when it receives the object from Python
// and then just echoes back that hash when called Rust needs to hash it
#[derive(Clone)]
pub struct HashedAny {
pub o: Py<PyAny>,
pub hash: isize,
}
// Use the result of calling repr() on the Python object as the debug string value
impl fmt::Debug for HashedAny {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Python::with_gil(|py| -> PyResult<fmt::Result> {
let obj = self.o.as_ref(py);
let pystr = obj.repr()?;
let ruststr = pystr.to_str()?;
Ok(write!(f, "{}", ruststr))
}).unwrap()
}
}
impl <'source>FromPyObject<'source> for HashedAny
{
fn extract(ob: &'source PyAny) -> PyResult<Self> {
Ok(HashedAny{ o: ob.into(), hash: ob.hash()? })
}
}
impl hash::Hash for HashedAny {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.hash.hash(state)
}
}
impl cmp::PartialEq for HashedAny {
fn eq(&self, other: &Self) -> bool {
Python::with_gil(|py| -> PyResult<bool> {
let this_ref = self.o.as_ref(py);
let other_ref = other.o.as_ref(py);
if this_ref.eq(other_ref) {
Ok(true)
}
else {
Ok(this_ref.rich_compare(other_ref, CompareOp::Eq)?.is_true()?)
}
}).unwrap()
}
}
impl cmp::Eq for HashedAny {}
use std::collections::HashMap;
use pyo3::prelude::*;
use pyo3::exceptions;
use pyo3::types::PyTuple;
mod hashedany;
use crate::hashedany::HashedAny;
#[pyclass(module="di_lib",subclass)]
#[derive(Debug,Clone)]
struct Graph {
children: HashMap<HashedAny, Vec<HashedAny>>,
parents: HashMap<HashedAny, Vec<HashedAny>>,
child_counts: HashMap<HashedAny, usize>,
ready_nodes: Vec<Py<PyAny>>,
not_done_count: usize,
}
impl Graph {
fn remove_node(&mut self, node: &HashedAny, to_remove: &mut Vec<HashedAny>) -> () {
match self.child_counts.remove(&node) {
Some(_) => (),
// This node was already removed
// This happens if parents and children are passed in the nodes argument
None => return,
}
// Find all parents and reduce their dependency count by one
match self.parents.remove(&node) {
Some(parents) => {
for parent in parents {
match self.child_counts.get_mut(&parent) {
Some(v) => {
*v -= 1;
},
// This node was already removed
// This happens if parents and children are passed in the nodes argument
None => continue,
}
}
},
// this node was already removed
None => return,
};
// Push all children onto the stack for removal
match self.children.remove(&node) {
Some(children) => {
for child in children {
to_remove.push(child);
};
},
None => ()
};
}
}
#[pymethods]
impl Graph {
#[new]
fn new(graph: HashMap<HashedAny, Vec<HashedAny>>) -> Self {
let mut child_counts: HashMap<HashedAny, usize> = HashMap::new();
let mut parents: HashMap<HashedAny, Vec<HashedAny>> = HashMap::new();
let mut ready_nodes: Vec<Py<PyAny>> = Vec::new();
let mut child_count: usize;
for (node, children) in &graph {
parents.entry(node.clone()).or_insert_with(Vec::new);
child_count = (*children).len();
child_counts.insert(node.clone(), child_count);
if child_count == 0 {
ready_nodes.push(node.o.clone());
}
for child in children {
parents.entry(child.clone()).or_insert_with(Vec::new).push(node.clone());
}
}
Graph {
children: graph.clone(),
parents: parents,
child_counts: child_counts,
ready_nodes: ready_nodes,
not_done_count: graph.len(),
}
}
/// Returns string representation of the graph
fn __str__(&self) -> PyResult<String> {
Ok(format!("Graph({:?})", self.children))
}
fn __repr__(&self) -> PyResult<String> {
self.__str__()
}
/// Returns a deep copy of this graph
fn copy(&self) -> Graph {
self.clone()
}
/// Returns any nodes with no dependencies after marking `node` as done
/// # Arguments
///
/// * `node` - A node in the graph
#[args(args="*")]
fn done(&mut self, args: &PyTuple) -> PyResult<()> {
let mut node: HashedAny;
let mut v: usize;
for obj in args {
node = HashedAny::extract(obj)?;
// Check that this node is ready to be marked as done and mark it
v = *self.child_counts.get(&node).unwrap();
if v != 0 {
return Err(exceptions::PyException::new_err("Node still has children"));
}
self.not_done_count -= 1;
// Find all parents and reduce their dependency count by one,
// returning all parents w/o any further dependencies
for parent in self.parents.get(&node).unwrap() {
match self.child_counts.get_mut(parent) {
Some(v) => {
*v -= 1;
if *v == 0 {
self.ready_nodes.push(parent.o.clone());
}
},
None => return Err(exceptions::PyKeyError::new_err(format!("Parent node {:?} not found", parent)))
}
}
}
Ok(())
}
fn is_active(&self) -> bool {
self.not_done_count != 0 || !self.ready_nodes.is_empty()
}
/// Removes nodes from the graph and cleans up newly created disconnected components
/// # Arguments
///
/// * `nodes` - Nodes to be removed from the graph
fn remove(&mut self, nodes: &PyTuple) -> PyResult<()> {
let mut to_remove: Vec<HashedAny> = Vec::new();
for node in nodes {
self.remove_node(&HashedAny::extract(node)?, &mut to_remove);
}
let mut node: HashedAny;
loop {
node = match to_remove.pop() {
Some(v) => v,
None => return Ok(())
};
self.remove_node(&node, &mut to_remove);
}
}
/// Returns all nodes with no dependencies
fn get_ready(&mut self) -> Vec<Py<PyAny>> {
let ret = self.ready_nodes.clone();
self.ready_nodes.clear();
ret
}
}
#[pymodule]
fn di_lib(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Graph>()?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment