Compare commits

...

3 Commits

Author SHA1 Message Date
Niko Abeler 14a5c610f3 readme update 2022-11-25 20:24:15 +01:00
Niko Abeler 791946471a optional to pass initial positions 2022-11-25 20:22:58 +01:00
Niko Abeler acf34d4331 initial position 2022-11-25 20:22:17 +01:00
6 changed files with 55 additions and 11 deletions

2
Cargo.lock generated
View File

@ -33,7 +33,7 @@ dependencies = [
[[package]] [[package]]
name = "graph_force" name = "graph_force"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"pyo3", "pyo3",
"rand", "rand",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "graph_force" name = "graph_force"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -67,7 +67,7 @@ pos = graph_force.layout_from_edge_file("edges.bin", iter=50)
### Options ### Options
`iter`, `threads` and `model` are optional parameters, supported by `layout_from_edge_list` and `layout_from_edge_file`. `iter`, `threads` and `model`, `initial_pos` are optional parameters, supported by `layout_from_edge_list` and `layout_from_edge_file`.
```python ```python
pos = graph_force.layout_from_edge_list( pos = graph_force.layout_from_edge_list(
@ -76,6 +76,7 @@ pos = graph_force.layout_from_edge_list(
iter=500, # number of iterations, default 500 iter=500, # number of iterations, default 500
threads=0, # number of threads, default 0 (all available) threads=0, # number of threads, default 0 (all available)
model="spring_model", # model to use, default "spring_model", other option is "networkx_model" model="spring_model", # model to use, default "spring_model", other option is "networkx_model"
initial_pos=[(0.4, 0.7), (0.7, 0.2), ...], # initial positions, default None (random)
) )
``` ```
#### Available models #### Available models

View File

@ -6,6 +6,7 @@ mod runner;
mod spring_model; mod spring_model;
mod utils; mod utils;
use graph::NodeVector;
use pyo3::exceptions; use pyo3::exceptions;
use pyo3::{prelude::*, types::PyIterator}; use pyo3::{prelude::*, types::PyIterator};
@ -21,18 +22,41 @@ fn pick_model(model: &str) -> Result<Box<dyn model::ForceModel + Send + Sync>, P
} }
} }
#[pyfunction(file_path, "*", iter = 500, threads = 0, model = "\"spring_model\"")] fn initial_pos_to_node_vector(initial_pos: Option<Vec<(f32, f32)>>) -> Option<NodeVector> {
match initial_pos {
Some(pos) => {
let nodes = graph::new_node_vector(pos.len());
for (i, (x, y)) in pos.iter().enumerate() {
let mut node = nodes[i].write().unwrap();
node.x = *x;
node.y = *y;
}
Some(nodes)
}
None => None,
}
}
#[pyfunction(
file_path,
"*",
iter = 500,
threads = 0,
model = "\"spring_model\"",
initial_pos = "None"
)]
fn layout_from_edge_file( fn layout_from_edge_file(
file_path: &str, file_path: &str,
iter: usize, iter: usize,
threads: usize, threads: usize,
model: &str, model: &str,
initial_pos: Option<Vec<(f32, f32)>>,
) -> PyResult<Vec<(f32, f32)>> { ) -> PyResult<Vec<(f32, f32)>> {
let (size, matrix) = reader::read_graph(file_path); let (size, matrix) = reader::read_graph(file_path);
let model = pick_model(model)?; let model = pick_model(model)?;
let r = runner::Runner::new(iter, threads); let r = runner::Runner::new(iter, threads);
Ok(r.layout(size, matrix, model)) Ok(r.layout(size, matrix, model, initial_pos_to_node_vector(initial_pos)))
} }
#[pyfunction( #[pyfunction(
@ -41,7 +65,8 @@ fn layout_from_edge_file(
"*", "*",
iter = 500, iter = 500,
threads = 0, threads = 0,
model = "\"spring_model\"" model = "\"spring_model\"",
initial_pos = "None"
)] )]
fn layout_from_edge_list( fn layout_from_edge_list(
number_of_nodes: usize, number_of_nodes: usize,
@ -49,6 +74,7 @@ fn layout_from_edge_list(
iter: usize, iter: usize,
threads: usize, threads: usize,
model: &str, model: &str,
initial_pos: Option<Vec<(f32, f32)>>,
) -> PyResult<Vec<(f32, f32)>> { ) -> PyResult<Vec<(f32, f32)>> {
let model: Box<dyn model::ForceModel + Send + Sync> = pick_model(model)?; let model: Box<dyn model::ForceModel + Send + Sync> = pick_model(model)?;
@ -83,7 +109,12 @@ fn layout_from_edge_list(
} }
let r = runner::Runner::new(iter, threads); let r = runner::Runner::new(iter, threads);
Ok(r.layout(number_of_nodes, edge_matrix, model)) Ok(r.layout(
number_of_nodes,
edge_matrix,
model,
initial_pos_to_node_vector(initial_pos),
))
} }
#[pymodule] #[pymodule]

View File

@ -1,4 +1,4 @@
use crate::graph::{new_node_vector, EdgeMatrix}; use crate::graph::{new_node_vector, EdgeMatrix, NodeVector};
use crate::model::ForceModel; use crate::model::ForceModel;
use crate::utils; use crate::utils;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -35,9 +35,13 @@ impl Runner {
number_of_nodes: usize, number_of_nodes: usize,
edges: EdgeMatrix, edges: EdgeMatrix,
model: Box<dyn ForceModel + Send + Sync>, model: Box<dyn ForceModel + Send + Sync>,
initial_pos: Option<NodeVector>,
) -> Vec<(f32, f32)> { ) -> Vec<(f32, f32)> {
// let edges = connection_matrix(size); // let edges = connection_matrix(size);
let mut nodes = new_node_vector(number_of_nodes); let mut nodes = match initial_pos {
Some(pos) => pos,
None => new_node_vector(number_of_nodes),
};
let mut nodes_next = new_node_vector(number_of_nodes); let mut nodes_next = new_node_vector(number_of_nodes);
let model = Arc::new(RwLock::new(model)); let model = Arc::new(RwLock::new(model));
@ -114,7 +118,7 @@ mod test {
let model = Box::new(MockModel { counter: 0 }); let model = Box::new(MockModel { counter: 0 });
let runner = Runner::new(10, 1); let runner = Runner::new(10, 1);
let edges = graph::new_edge_matrix(3); let edges = graph::new_edge_matrix(3);
let result = runner.layout(3, edges, model); let result = runner.layout(3, edges, model, None);
assert_eq!(result, vec![(0.0, 10.0), (1.0, 10.0), (2.0, 10.0)]); assert_eq!(result, vec![(0.0, 10.0), (1.0, 10.0), (2.0, 10.0)]);
} }
} }

View File

@ -39,4 +39,12 @@ def test_from_file():
pos = graph_force.layout_from_edge_file('/tmp/edges.bin') pos = graph_force.layout_from_edge_file('/tmp/edges.bin')
assert pos is not None assert pos is not None
assert len(pos) == 10 assert len(pos) == 10
def test_initial_pos():
edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
initial = [(i, i) for i in range(7)]
pos = graph_force.layout_from_edge_list(7, edges, iter=0, initial_pos=initial)
assert pos is not None
assert len(pos) == 7
assert pos == initial