Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/layer name time #1593

Merged
merged 19 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 60 additions & 36 deletions python/tests/notebook.ipynb

Large diffs are not rendered by default.

64 changes: 52 additions & 12 deletions python/tests/test_graphdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,58 @@ def test_layer_name():
assert g.edge(0, 1).layer_names == ["_default"]
assert g.edge(0, 2).layer_names == ["awesome layer"]

error_msg = ("The layer_name function is only available once an edge has been exploded via .explode_layers() or "
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
".explode(). If you want to retrieve the layers for this edge you can use .layer_names")
with pytest.raises(Exception) as e:
g.edges.layer_name()
assert str(e.value) == error_msg

assert list(g.edges.explode().layer_name) == ['_default', 'awesome layer']

with pytest.raises(Exception) as e:
g.edge(0, 2).layer_name()
assert str(e.value) == error_msg

assert list(g.edge(0, 2).explode().layer_name) == ['awesome layer']

with pytest.raises(Exception) as e:
g.nodes.neighbours.edges.layer_name()
assert str(e.value) == error_msg

assert [list(iterator) for iterator in g.nodes.neighbours.edges.explode().layer_name] == [
["_default", "awesome layer"],
["_default", "awesome layer"],
["_default", "awesome layer"]
]

def test_time():
g = Graph()

g.add_edge(0, 0, 1)
g.add_edge(0, 0, 2)
g.add_edge(1, 0, 2)

error_msg = ("The time function is only available once an edge has been exploded via .explode_layers() or "
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
".explode(). You may want to retrieve the history for this edge via .history(), "
"or the earliest/latest time via earliest_time or latest_time")
with pytest.raises(Exception) as e:
g.edges.time()
assert str(e.value) == error_msg

assert list(g.edges.explode().time) == [0, 0, 1]

with pytest.raises(Exception) as e:
g.edge(0, 2).time()
assert str(e.value) == error_msg

assert list(g.edge(0, 2).explode().time) == [0, 1]

with pytest.raises(Exception) as e:
g.nodes.neighbours.edges.time()
assert str(e.value) == error_msg

assert [list(iterator) for iterator in g.nodes.neighbours.edges.explode().time] == [[0, 0, 1], [0, 0, 1], [0, 0, 1]]


def test_window_size():
g = Graph()
Expand Down Expand Up @@ -1810,11 +1862,6 @@ def test_starend_edges():
g.add_edge(2, 1, 2)
g.add_edge(3, 1, 2)

old_time_way = []
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
for e in g.edges:
old_time_way.append(e.time)
assert old_time_way == list(g.edges.time)

old_latest_time_way = []
for e in g.edges:
old_latest_time_way.append(e.latest_time)
Expand All @@ -1826,20 +1873,13 @@ def test_starend_edges():
old_earliest_time_way.append(e.earliest_time)
assert old_earliest_time_way == list(g.edges.earliest_time)

old_start_nested_way = []
old_end_nested_way = []
old_time_nested_way = []
old_latest_time_nested_way = []
old_earliest_time_nested_way = []
for edges in g.nodes.edges:
for edge in edges:
old_time_nested_way.append(edge.time)
old_latest_time_nested_way.append(edge.latest_time)
old_earliest_time_nested_way.append(edge.earliest_time)

assert old_time_nested_way == [
item for sublist in g.nodes.edges.time.collect() for item in sublist
]
assert old_latest_time_nested_way == [
item for sublist in g.nodes.edges.latest_time.collect() for item in sublist
]
Expand Down
15 changes: 11 additions & 4 deletions raphtory-graphql/src/model/graph/edge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::model::graph::{node::Node, property::GqlProperties};
use async_graphql::Error;
use dynamic_graphql::{ResolvedObject, ResolvedObjectFields};
use itertools::Itertools;
use raphtory::{
Expand Down Expand Up @@ -94,8 +95,11 @@ impl Edge {
self.ee.history().last().cloned()
}

async fn time(&self) -> Option<i64> {
self.ee.time()
async fn time(&self) -> Result<i64, Error> {
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
match self.ee.time().map(|x| x.into()) {
Ok(name) => Ok(name),
Err(e) => Err(Error::new(e.to_string())),
}
}

async fn start(&self) -> Option<i64> {
Expand All @@ -122,8 +126,11 @@ impl Edge {
self.ee.layer_names().map(|x| x.into()).collect()
}

async fn layer_name(&self) -> Option<String> {
self.ee.layer_name().map(|x| x.into())
async fn layer_name(&self) -> Result<String, Error> {
match self.ee.layer_name().map(|x| x.into()) {
Ok(name) => Ok(name),
Err(e) => Err(Error::new(e.to_string())),
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
}
}

async fn explode(&self) -> Vec<Edge> {
Expand Down
1 change: 1 addition & 0 deletions raphtory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ tokio = { workspace = true } # for vector testing
dotenv = { workspace = true } # for vector testing
streaming-stats = { workspace = true }
proptest = { workspace = true }
futures-util = { workspace = true }
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved

[features]
default = []
Expand Down
6 changes: 6 additions & 0 deletions raphtory/src/core/utils/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ pub enum GraphError {
"Failed to load the graph as the bincode version {0} is different to installed version {1}"
)]
BincodeVersionError(u32, u32),

#[error("The layer_name function is only available once an edge has been exploded via .explode_layers() or .explode(). If you want to retrieve the layers for this edge you can use .layer_names")]
LayerNameAPIError,

#[error("The time function is only available once an edge has been exploded via .explode_layers() or .explode(). You may want to retrieve the history for this edge via .history(), or the earliest/latest time via earliest_time or latest_time")]
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
TimeAPIError,
}

#[derive(thiserror::Error, Debug, PartialEq)]
Expand Down
34 changes: 27 additions & 7 deletions raphtory/src/db/api/view/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
core::{
entities::{edges::edge_ref::EdgeRef, VID},
storage::timeindex::{AsTime, TimeIndexEntry},
utils::errors::GraphError,
ArcStr,
},
db::api::{
Expand Down Expand Up @@ -116,12 +117,12 @@ pub trait EdgeViewOps<'graph>: TimeOps<'graph> + LayerOps<'graph> + Clone {
fn latest_time(&self) -> Self::ValueType<Option<i64>>;

/// Gets the time stamp of the edge if it is exploded
fn time(&self) -> Self::ValueType<Option<i64>>;
fn time(&self) -> Self::ValueType<Result<i64, GraphError>>;

fn date_time(&self) -> Self::ValueType<Option<DateTime<Utc>>>;

/// Gets the layer name for the edge if it is restricted to a single layer
fn layer_name(&self) -> Self::ValueType<Option<ArcStr>>;
fn layer_name(&self) -> Self::ValueType<Result<ArcStr, GraphError>>;

/// Gets the TimeIndexEntry if the edge is exploded
fn time_and_index(&self) -> Self::ValueType<Option<TimeIndexEntry>>;
Expand Down Expand Up @@ -257,17 +258,21 @@ impl<'graph, E: BaseEdgeViewOps<'graph>> EdgeViewOps<'graph> for E {
}

/// Gets the time stamp of the edge if it is exploded
fn time(&self) -> Self::ValueType<Option<i64>> {
self.map(|_, e| e.time_t())
fn time(&self) -> Self::ValueType<Result<i64, GraphError>> {
self.map(|_, e| e.time_t().ok_or_else(|| GraphError::TimeAPIError))
}

fn date_time(&self) -> Self::ValueType<Option<DateTime<Utc>>> {
self.map(|_, e| e.time_t()?.dt())
}

/// Gets the layer name for the edge if it is restricted to a single layer
fn layer_name(&self) -> Self::ValueType<Option<ArcStr>> {
self.map(|g, e| e.layer().map(|l_id| g.get_layer_name(*l_id)))
fn layer_name(&self) -> Self::ValueType<Result<ArcStr, GraphError>> {
self.map(|g, e| {
e.layer()
.map(|l_id| g.get_layer_name(*l_id))
.ok_or_else(|| GraphError::LayerNameAPIError)
})
}

/// Gets the TimeIndexEntry if the edge is exploded
Expand Down Expand Up @@ -339,6 +344,21 @@ mod test_edge_view {
.collect();
assert_eq!(prop_values, expected_prop_values);
assert_eq!(actual_layers, expected_layers);

assert!(g.edge(1, 2).unwrap().layer_name().is_err());
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
assert!(g.edges().layer_name().all(|l| l.is_err()));
assert!(g
.edge(1, 2)
.unwrap()
.explode()
.layer_name()
.all(|l| l.is_ok()));
assert!(g.edges().explode().layer_name().all(|l| l.is_ok()));

assert!(g.edge(1, 2).unwrap().time().is_err());
assert!(g.edges().time().all(|l| l.is_err()));
assert!(g.edge(1, 2).unwrap().explode().time().all(|l| l.is_ok()));
assert!(g.edges().explode().time().all(|l| l.is_ok()));
}

#[test]
Expand Down Expand Up @@ -368,7 +388,7 @@ mod test_edge_view {
(2, 3, None),
(1, 2, None),
(1, 2, Some(true)),
(2, 3, Some(true))
(2, 3, Some(true)),
]
)
}
Expand Down
4 changes: 2 additions & 2 deletions raphtory/src/db/graph/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1767,7 +1767,7 @@ mod db_tests {
e.explode().iter().filter_map(|e| {
e.edge
.layer()
.zip(e.time())
.zip(Some(e.time().unwrap()))
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
.map(|(layer, t)| (t, e.src().id(), e.dst().id(), *layer))
})
})
Expand Down Expand Up @@ -1797,7 +1797,7 @@ mod db_tests {
e.explode().iter().filter_map(|e| {
e.edge
.layer()
.zip(e.time())
.zip(Some(e.time().unwrap()))
.map(|(layer, t)| (t, e.src().id(), e.dst().id(), *layer))
})
})
Expand Down
4 changes: 2 additions & 2 deletions raphtory/src/python/graph/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ impl PyEdge {
/// Returns:
/// (int) The time of an exploded edge
#[getter]
pub fn time(&self) -> Option<i64> {
pub fn time(&self) -> Result<i64, GraphError> {
self.edge.time()
}

Expand All @@ -281,7 +281,7 @@ impl PyEdge {
/// Returns:
/// (List<str>) The name of the layer
#[getter]
pub fn layer_name(&self) -> Option<ArcStr> {
pub fn layer_name(&self) -> Result<ArcStr, GraphError> {
self.edge.layer_name().map(|v| v.clone())
}

Expand Down
75 changes: 56 additions & 19 deletions raphtory/src/python/graph/edges.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
core::{ArcStr, Prop},
core::{utils::errors::GraphError, ArcStr, Prop},
db::{
api::view::{
internal::CoreGraphOps, BoxedIter, DynamicGraph, IntoDynBoxed, IntoDynamic,
Expand All @@ -16,11 +16,12 @@ use crate::{
types::{
repr::{iterator_repr, Repr},
wrappers::iterators::{
ArcStringVecIterable, BoolIterable, I64VecIterable, NestedArcStringVecIterable,
NestedBoolIterable, NestedI64VecIterable, NestedOptionArcStringIterable,
NestedOptionI64Iterable, NestedU64U64Iterable, NestedUtcDateTimeIterable,
NestedVecUtcDateTimeIterable, OptionArcStringIterable, OptionI64Iterable,
OptionUtcDateTimeIterable, OptionVecUtcDateTimeIterable, U64U64Iterable,
ArcStringIterable, ArcStringVecIterable, BoolIterable, I64Iterable, I64VecIterable,
NestedArcStringIterable, NestedArcStringVecIterable, NestedBoolIterable,
NestedI64VecIterable, NestedOptionI64Iterable, NestedU64U64Iterable,
NestedUtcDateTimeIterable, NestedVecUtcDateTimeIterable, OptionArcStringIterable,
OptionI64Iterable, OptionUtcDateTimeIterable, OptionVecUtcDateTimeIterable,
U64U64Iterable,
},
},
utils::{
Expand Down Expand Up @@ -145,9 +146,14 @@ impl PyEdges {
/// Returns:
/// Time of edge
#[getter]
fn time(&self) -> OptionI64Iterable {
let edges = self.edges.clone();
(move || edges.time()).into()
fn time(&self) -> Result<I64Iterable, GraphError> {
match self.edges.time().next() {
Some(Err(err)) => Err(err),
_ => {
let edges = self.edges.clone();
Ok((move || edges.time().map(|t| t.unwrap())).into())
}
}
}

/// Returns all properties of the edges
Expand Down Expand Up @@ -225,9 +231,14 @@ impl PyEdges {
/// Returns:
/// The name of the layer
#[getter]
fn layer_name(&self) -> OptionArcStringIterable {
let edges = self.edges.clone();
(move || edges.layer_name()).into()
fn layer_name(&self) -> Result<ArcStringIterable, GraphError> {
match self.edges.layer_name().next() {
Some(Err(err)) => Err(err),
_ => {
let edges = self.edges.clone();
Ok((move || edges.layer_name().map(|layer| layer.unwrap())).into())
}
}
}

/// Get the layer names that all edges belong to - assuming they only belong to one layer
Expand Down Expand Up @@ -256,7 +267,7 @@ impl PyEdges {
///
/// Returns:
/// If successful, this PyObject will be a Pandas DataFrame.
#[pyo3(signature = (include_property_history=true, convert_datetime=false, explode=false))]
#[pyo3(signature = (include_property_history = true, convert_datetime = false, explode = false))]
pub fn to_df(
&self,
include_property_history: bool,
Expand Down Expand Up @@ -406,16 +417,42 @@ impl PyNestedEdges {

/// Returns the times of exploded edges
#[getter]
fn time(&self) -> NestedOptionI64Iterable {
let edges = self.edges.clone();
(move || edges.time()).into()
fn time(&self) -> Result<NestedOptionI64Iterable, GraphError> {
match self.edges.time().flatten().next() {
Some(Err(err)) => Err(err),
_ => {
let edges = self.edges.clone();
Ok((move || {
edges
.time()
.map(|t_iter| t_iter.map(|t| t.unwrap()).into_dyn_boxed())
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
.into_dyn_boxed()
})
.into())
}
}
}

/// Returns the name of the layer the edges belong to - assuming they only belong to one layer
#[getter]
fn layer_name(&self) -> NestedOptionArcStringIterable {
let edges = self.edges.clone();
(move || edges.layer_name()).into()
fn layer_name(&self) -> Result<NestedArcStringIterable, GraphError> {
match self.edges.layer_name().flatten().next() {
Some(Err(err)) => Err(err),
_ => {
let edges = self.edges.clone();
Ok((move || {
edges
.layer_name()
.map(|layer_name_iter| {
layer_name_iter
.map(|layer_name| layer_name.unwrap())
shivamka1 marked this conversation as resolved.
Show resolved Hide resolved
.into_dyn_boxed()
})
.into_dyn_boxed()
})
.into())
}
}
}

/// Returns the names of the layers the edges belong to
Expand Down
8 changes: 3 additions & 5 deletions raphtory/src/python/graph/views/graph_view_modules/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,11 @@ impl PyGraphView {
}
}
}
let layer = e.layer_name();
if layer.is_some() {
properties.set_item("layer", layer)?;
}
let layer = e.layer_name()?;
properties.set_item("layer", layer)?;
if include_update_history.unwrap_or(true) {
if explode_edges.unwrap_or(true) {
properties.set_item("update_history", e.time())?;
properties.set_item("update_history", e.time()?)?;
} else {
properties.set_item("update_history", e.history())?;
}
Expand Down
Loading
Loading