From a9290164dbd9ff713bc3190a29a86b4697c5f795 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Mon, 8 Jan 2024 11:39:15 +0100 Subject: [PATCH 01/13] change the windowing semantics to not enforce nesting by default and add helper methods for creating nested windows --- raphtory/src/db/api/view/time.rs | 26 ++++- raphtory/src/db/graph/views/window_graph.rs | 113 +++++--------------- 2 files changed, 52 insertions(+), 87 deletions(-) diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index 8654e961e5..1fe31d951f 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -5,7 +5,10 @@ use crate::{ graph::views::window_graph::WindowedGraph, }, }; -use std::marker::PhantomData; +use std::{ + cmp::{max, min}, + marker::PhantomData, +}; /// Trait defining time query operations pub trait TimeOps<'graph> { @@ -17,6 +20,27 @@ pub trait TimeOps<'graph> { /// Return the timestamp of the default for perspectives of the view (if any). fn end(&self) -> Option; + /// set the start of the window to the larger of `start` and `self.start()` + fn shrink_start(&self, start: i64) -> Self::WindowedViewType { + let start = max(start, self.start().unwrap_or(i64::MIN)); + let end = self.end().unwrap_or(start); + self.window(start, end) + } + + /// set the end of the winodw to the smaller of `end` and `self.end()` + fn shrink_end(&self, end: i64) -> Self::WindowedViewType { + let end = min(end, self.end().unwrap_or(i64::MAX)); + let start = self.start().unwrap_or(end); + self.window(start, end) + } + + /// shrink both the start and end of the window (same as calling `shrink_start` followed by `shrink_end` but more efficient) + fn shrink_window(&self, start: i64, end: i64) -> Self::WindowedViewType { + let start = max(start, self.start().unwrap_or(i64::MIN)); + let end = min(end, self.end().unwrap_or(i64::MAX)); + self.window(start, end) + } + /// Return the size of the window covered by this view fn window_size(&self) -> Option { match (self.start(), self.end()) { diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 509856325e..f13e811e21 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -191,26 +191,22 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { #[inline] fn earliest_time_window(&self, start: i64, end: i64) -> Option { - self.graph - .earliest_time_window(self.actual_start(start), self.actual_end(end)) + self.graph.earliest_time_window(start, end) } #[inline] fn latest_time_window(&self, start: i64, end: i64) -> Option { - self.graph - .latest_time_window(self.actual_start(start), self.actual_end(end)) + self.graph.latest_time_window(start, end) } #[inline] fn node_earliest_time_window(&self, v: VID, start: i64, end: i64) -> Option { - self.graph - .node_earliest_time_window(v, self.actual_start(start), self.actual_end(end)) + self.graph.node_earliest_time_window(v, start, end) } #[inline] fn node_latest_time_window(&self, v: VID, start: i64, end: i64) -> Option { - self.graph - .node_latest_time_window(v, self.actual_start(start), self.actual_end(end)) + self.graph.node_latest_time_window(v, start, end) } #[inline] @@ -221,12 +217,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { layer_ids: &LayerIds, edge_filter: Option<&EdgeFilter>, ) -> bool { - self.graph.include_node_window( - v, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - edge_filter, - ) + self.graph + .include_node_window(v, w.start..w.end, layer_ids, edge_filter) } #[inline] @@ -239,8 +231,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn node_history_window(&self, v: VID, w: Range) -> Vec { - self.graph - .node_history_window(v, self.actual_start(w.start)..self.actual_end(w.end)) + self.graph.node_history_window(v, w.start..w.end) } fn edge_history(&self, e: EdgeRef, layer_ids: LayerIds) -> Vec { @@ -249,11 +240,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn edge_history_window(&self, e: EdgeRef, layer_ids: LayerIds, w: Range) -> Vec { - self.graph.edge_history_window( - e, - layer_ids, - self.actual_start(w.start)..self.actual_end(w.end), - ) + self.graph.edge_history_window(e, layer_ids, w.start..w.end) } fn edge_exploded(&self, e: EdgeRef, layer_ids: LayerIds) -> BoxedIter { @@ -272,11 +259,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> BoxedIter { - self.graph.edge_window_exploded( - e, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph + .edge_window_exploded(e, w.start..w.end, layer_ids) } fn edge_window_layers( @@ -285,11 +269,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> BoxedIter { - self.graph.edge_window_layers( - e, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph.edge_window_layers(e, w.start..w.end, layer_ids) } fn edge_earliest_time(&self, e: EdgeRef, layer_ids: LayerIds) -> Option { @@ -303,11 +283,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> Option { - self.graph.edge_earliest_time_window( - e, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph + .edge_earliest_time_window(e, w.start..w.end, layer_ids) } fn edge_latest_time(&self, e: EdgeRef, layer_ids: LayerIds) -> Option { @@ -321,11 +298,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> Option { - self.graph.edge_latest_time_window( - e, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph + .edge_latest_time_window(e, w.start..w.end, layer_ids) } fn edge_deletion_history(&self, e: EdgeRef, layer_ids: LayerIds) -> Vec { @@ -339,11 +313,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> Vec { - self.graph.edge_deletion_history_window( - e, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph + .edge_deletion_history_window(e, w.start..w.end, layer_ids) } fn edge_is_valid(&self, e: EdgeRef, layer_ids: LayerIds) -> bool { @@ -366,13 +337,11 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn has_temporal_prop_window(&self, prop_id: usize, w: Range) -> bool { - self.graph - .has_temporal_prop_window(prop_id, self.actual_start(w.start)..self.actual_end(w.end)) + self.graph.has_temporal_prop_window(prop_id, w.start..w.end) } fn temporal_prop_vec_window(&self, prop_id: usize, start: i64, end: i64) -> Vec<(i64, Prop)> { - self.graph - .temporal_prop_vec_window(prop_id, self.actual_start(start), self.actual_end(end)) + self.graph.temporal_prop_vec_window(prop_id, start, end) } fn has_temporal_node_prop(&self, v: VID, prop_id: usize) -> bool { @@ -386,11 +355,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn has_temporal_node_prop_window(&self, v: VID, prop_id: usize, w: Range) -> bool { - self.graph.has_temporal_node_prop_window( - v, - prop_id, - self.actual_start(w.start)..self.actual_end(w.end), - ) + self.graph + .has_temporal_node_prop_window(v, prop_id, w.start..w.end) } fn temporal_node_prop_vec_window( @@ -400,12 +366,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { start: i64, end: i64, ) -> Vec<(i64, Prop)> { - self.graph.temporal_node_prop_vec_window( - v, - prop_id, - self.actual_start(start), - self.actual_end(end), - ) + self.graph + .temporal_node_prop_vec_window(v, prop_id, start, end) } fn has_temporal_edge_prop_window( @@ -415,12 +377,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { w: Range, layer_ids: LayerIds, ) -> bool { - self.graph.has_temporal_edge_prop_window( - e, - prop_id, - self.actual_start(w.start)..self.actual_end(w.end), - layer_ids, - ) + self.graph + .has_temporal_edge_prop_window(e, prop_id, w.start..w.end, layer_ids) } fn temporal_edge_prop_vec_window( @@ -431,13 +389,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { end: i64, layer_ids: LayerIds, ) -> Vec<(i64, Prop)> { - self.graph.temporal_edge_prop_vec_window( - e, - prop_id, - self.actual_start(start), - self.actual_end(end), - layer_ids, - ) + self.graph + .temporal_edge_prop_vec_window(e, prop_id, start, end, layer_ids) } fn has_temporal_edge_prop(&self, e: EdgeRef, prop_id: usize, layer_ids: LayerIds) -> bool { @@ -732,18 +685,6 @@ impl<'graph, G: GraphViewOps<'graph>> WindowedGraph { window_filter, } } - - /// the larger of `start` and `self.start()` (useful for creating nested windows) - #[inline] - fn actual_start(&self, start: i64) -> i64 { - max(start, self.start) - } - - /// the smaller of `end` and `self.end()` (useful for creating nested windows) - #[inline] - fn actual_end(&self, end: i64) -> i64 { - min(end, self.end) - } } #[cfg(test)] From a4aa4f985a8c652c69ef9c30285965a2ff13437d Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Mon, 8 Jan 2024 15:17:08 +0100 Subject: [PATCH 02/13] add shrink to python and gql --- python/tests/test_graphdb.py | 28 +++++------ raphtory-graphql/src/model/graph/edge.rs | 12 +++++ raphtory-graphql/src/model/graph/graph.rs | 12 +++++ raphtory-graphql/src/model/graph/node.rs | 12 +++++ raphtory/src/db/api/view/time.rs | 16 +++---- raphtory/src/python/types/macros/timeops.rs | 52 ++++++++++++++++----- 6 files changed, 98 insertions(+), 34 deletions(-) diff --git a/python/tests/test_graphdb.py b/python/tests/test_graphdb.py index f3a52a5afc..9656127493 100644 --- a/python/tests/test_graphdb.py +++ b/python/tests/test_graphdb.py @@ -1026,14 +1026,14 @@ def test_all_degrees_window(): view = g.before(5) v = view.node(2) assert v.window(0, 4).in_degree() == 3 - assert v.window(start=2).in_degree() == 2 - assert v.window(end=3).in_degree() == 2 + assert v.after(1).in_degree() == 2 + assert v.before(3).in_degree() == 2 assert v.window(0, 4).out_degree() == 1 - assert v.window(start=2).out_degree() == 1 - assert v.window(end=3).out_degree() == 1 + assert v.after(1).out_degree() == 1 + assert v.before(end=3).out_degree() == 1 assert v.window(0, 4).degree() == 3 - assert v.window(start=2).degree() == 2 - assert v.window(end=3).degree() == 2 + assert v.after(1).degree() == 2 + assert v.before(end=3).degree() == 2 def test_all_edge_window(): @@ -1049,24 +1049,24 @@ def test_all_edge_window(): view = g.before(5) v = view.node(2) assert sorted(v.window(0, 4).in_edges.src.id) == [1, 3, 4] - assert sorted(v.window(end=4).in_edges.src.id) == [1, 3, 4] - assert sorted(v.window(start=2).in_edges.src.id) == [3, 4] + assert sorted(v.before(end=4).in_edges.src.id) == [1, 3, 4] + assert sorted(v.after(start=1).in_edges.src.id) == [3, 4] assert sorted(v.window(0, 4).out_edges.dst.id) == [3] - assert sorted(v.window(end=3).out_edges.dst.id) == [3] - assert sorted(v.window(start=2).out_edges.dst.id) == [4] + assert sorted(v.before(end=3).out_edges.dst.id) == [3] + assert sorted(v.after(start=1).out_edges.dst.id) == [4] assert sorted((e.src.id, e.dst.id) for e in v.window(0, 4).edges) == [ (1, 2), (2, 3), (3, 2), (4, 2), ] - assert sorted((e.src.id, e.dst.id) for e in v.window(end=4).edges) == [ + assert sorted((e.src.id, e.dst.id) for e in v.before(end=4).edges) == [ (1, 2), (2, 3), (3, 2), (4, 2), ] - assert sorted((e.src.id, e.dst.id) for e in v.window(start=1).edges) == [ + assert sorted((e.src.id, e.dst.id) for e in v.after(start=0).edges) == [ (1, 2), (2, 3), (2, 4), @@ -1620,9 +1620,9 @@ def test_deletions(): for e in edges: assert g.at(e[0]).has_edge(e[1], e[2]) - assert not g.window(start=11).has_edge(edges[0][1], edges[0][2]) + assert not g.after(start=10).has_edge(edges[0][1], edges[0][2]) for e in edges[1:]: - assert g.window(start=11).has_edge(e[1], e[2]) + assert g.after(start=10).has_edge(e[1], e[2]) assert list(g.edge(edges[0][1], edges[0][2]).explode().latest_time) == [10] diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index ebf942a771..15b90d1ea7 100644 --- a/raphtory-graphql/src/model/graph/edge.rs +++ b/raphtory-graphql/src/model/graph/edge.rs @@ -54,6 +54,18 @@ impl Edge { self.ee.after(time).into() } + async fn shrink_window(&self, start: i64, end: i64) -> Self { + self.ee.shrink_window(start, end).into() + } + + async fn shrink_start(&self, start: i64) -> Self { + self.ee.shrink_start(start).into() + } + + async fn shrink_end(&self, end: i64) -> Self { + self.ee.shrink_end(end).into() + } + async fn earliest_time(&self) -> Option { self.ee.earliest_time() } diff --git a/raphtory-graphql/src/model/graph/graph.rs b/raphtory-graphql/src/model/graph/graph.rs index dd48164469..6441518e74 100644 --- a/raphtory-graphql/src/model/graph/graph.rs +++ b/raphtory-graphql/src/model/graph/graph.rs @@ -95,6 +95,18 @@ impl GqlGraph { GqlGraph::new(self.name.clone(), self.graph.after(time)) } + async fn shrink_window(&self, start: i64, end: i64) -> Self { + GqlGraph::new(self.name.clone(), self.graph.shrink_window(start, end)) + } + + async fn shrink_start(&self, start: i64) -> Self { + GqlGraph::new(self.name.clone(), self.graph.shrink_start(start)) + } + + async fn shrink_end(&self, end: i64) -> Self { + GqlGraph::new(self.name.clone(), self.graph.shrink_end(end)) + } + //////////////////////// //// TIME QUERIES ////// //////////////////////// diff --git a/raphtory-graphql/src/model/graph/node.rs b/raphtory-graphql/src/model/graph/node.rs index 77dce79d9d..74e7d3a239 100644 --- a/raphtory-graphql/src/model/graph/node.rs +++ b/raphtory-graphql/src/model/graph/node.rs @@ -64,6 +64,18 @@ impl Node { self.vv.after(time).into() } + async fn shrink_window(&self, start: i64, end: i64) -> Self { + self.vv.shrink_window(start, end).into() + } + + async fn shrink_start(&self, start: i64) -> Self { + self.vv.shrink_start(start).into() + } + + async fn shrink_end(&self, end: i64) -> Self { + self.vv.shrink_end(end).into() + } + //////////////////////// //// TIME QUERIES ////// //////////////////////// diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index 1fe31d951f..22dae73bfb 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -21,23 +21,23 @@ pub trait TimeOps<'graph> { fn end(&self) -> Option; /// set the start of the window to the larger of `start` and `self.start()` - fn shrink_start(&self, start: i64) -> Self::WindowedViewType { - let start = max(start, self.start().unwrap_or(i64::MIN)); + fn shrink_start(&self, start: T) -> Self::WindowedViewType { + let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); let end = self.end().unwrap_or(start); self.window(start, end) } - /// set the end of the winodw to the smaller of `end` and `self.end()` - fn shrink_end(&self, end: i64) -> Self::WindowedViewType { - let end = min(end, self.end().unwrap_or(i64::MAX)); + /// set the end of the window to the smaller of `end` and `self.end()` + fn shrink_end(&self, end: T) -> Self::WindowedViewType { + let end = min(end.into_time(), self.end().unwrap_or(i64::MAX)); let start = self.start().unwrap_or(end); self.window(start, end) } /// shrink both the start and end of the window (same as calling `shrink_start` followed by `shrink_end` but more efficient) - fn shrink_window(&self, start: i64, end: i64) -> Self::WindowedViewType { - let start = max(start, self.start().unwrap_or(i64::MIN)); - let end = min(end, self.end().unwrap_or(i64::MAX)); + fn shrink_window(&self, start: T, end: T) -> Self::WindowedViewType { + let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); + let end = min(end.into_time(), self.end().unwrap_or(i64::MAX)); self.window(start, end) } diff --git a/raphtory/src/python/types/macros/timeops.rs b/raphtory/src/python/types/macros/timeops.rs index 198424c84f..1809e84d55 100644 --- a/raphtory/src/python/types/macros/timeops.rs +++ b/raphtory/src/python/types/macros/timeops.rs @@ -59,7 +59,7 @@ macro_rules! impl_timeops { /// An expanding window is a window that grows by `step` size at each iteration. /// /// Arguments: - /// step (int): The step size of the window. + /// step (int | str): The step size of the window. /// /// Returns: /// A `WindowSet` object. @@ -72,8 +72,8 @@ macro_rules! impl_timeops { /// A rolling window is a window that moves forward by `step` size at each iteration. /// /// Arguments: - /// window: The size of the window. - /// step: The step size of the window. Defaults to the window size. + /// window (int | str): The size of the window. + /// step (int | str | None): The step size of the window. Defaults to `window`. /// /// Returns: /// A `WindowSet` object. @@ -88,25 +88,24 @@ macro_rules! impl_timeops { #[doc = concat!(r" Create a view of the ", $name, r" including all events between `start` (inclusive) and `end` (exclusive)")] /// /// Arguments: - #[doc = concat!(r" start: The start time of the window. Defaults to the start time of the ", $name, r".")] - #[doc = concat!(r" end: The end time of the window. Defaults to the end time of the ", $name, r".")] + /// start (int | DateTime | str): The start time of the window. + /// end (int | DateTime | str): The end time of the window. /// /// Returns: #[doc = concat!("r A ", $name, " object.")] - #[pyo3(signature = (start = None, end = None))] pub fn window( &self, - start: Option, - end: Option, + start: PyTime, + end: PyTime, ) -> <$base_type as TimeOps<'static>>::WindowedViewType { self.$field - .window(start.unwrap_or(PyTime::MIN), end.unwrap_or(PyTime::MAX)) + .window(start, end) } #[doc = concat!(r" Create a view of the ", $name, r" including all events at `time`.")] /// /// Arguments: - /// time: The time of the window. + /// time (int | DateTime | str): The time of the window. /// /// Returns: #[doc = concat!(r" A ", $name, r" object.")] @@ -117,7 +116,7 @@ macro_rules! impl_timeops { #[doc = concat!(r" Create a view of the ", $name, r" including all events before `end` (exclusive).")] /// /// Arguments: - /// end: The end time of the window. + /// end (int | DateTime | str): The end time of the window. /// /// Returns: #[doc = concat!(r" A ", $name, r" object.")] @@ -128,13 +127,42 @@ macro_rules! impl_timeops { #[doc = concat!(r" Create a view of the ", $name, r" including all events after `start` (exclusive).")] /// /// Arguments: - /// start: The start time of the window. + /// start (int | DateTime | str): The start time of the window. /// /// Returns: #[doc = concat!(r" A ", $name, r" object.")] pub fn after(&self, start: PyTime) -> <$base_type as TimeOps<'static>>::WindowedViewType { self.$field.after(start) } + + /// Set the start of the window to the larger of `start` and `self.start()` + /// + /// Arguments: + /// start (int | DateTime | str): the new start time of the window + /// + /// Returns: + #[doc = concat!(r" A ", $name, r" object.")] + pub fn shrink_start(&self, start: PyTime) -> <$base_type as TimeOps<'static>>::WindowedViewType { + self.$field.shrink_start(start) + } + + /// Set the end of the window to the smaller of `end` and `self.end()` + /// + /// Arguments: + /// end (int | DateTime | str): the new end time of the window + /// Returns: + #[doc = concat!(r" A ", $name, r" object.")] + fn shrink_end(&self, end: PyTime) -> <$base_type as TimeOps<'static>>::WindowedViewType { + self.$field.shrink_end(end) + } + + /// Shrink both the start and end of the window (same as calling `shrink_start` followed by `shrink_end` but more efficient) + /// + /// Arguments: + /// + fn shrink_window(&self, start: PyTime, end: PyTime) -> <$base_type as TimeOps<'static>>::WindowedViewType { + self.$field.shrink_window(start, end) + } } }; } From e529ee054cfa2670ec6a408f6b44491c3b499d06 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Tue, 9 Jan 2024 13:43:22 +0100 Subject: [PATCH 03/13] switch NaiveDateTime to DateTime for return types (WIP) --- raphtory/src/core/storage/timeindex.rs | 6 ++ raphtory/src/db/api/properties/internal.rs | 12 ++-- .../src/db/api/properties/temporal_props.rs | 6 +- raphtory/src/db/api/view/edge.rs | 72 +++++++++---------- .../src/db/api/view/internal/materialize.rs | 2 +- raphtory/src/db/api/view/node.rs | 25 +++---- raphtory/src/db/graph/edge.rs | 38 +++++----- raphtory/src/db/graph/node.rs | 9 +-- raphtory/src/db/graph/views/window_graph.rs | 9 +-- .../src/db/internal/temporal_properties.rs | 13 ++-- raphtory/src/db/task/edge/eval_edge.rs | 19 +++-- raphtory/src/python/graph/edge.rs | 56 +++++++-------- raphtory/src/python/graph/node.rs | 21 +++--- .../python/graph/properties/temporal_props.rs | 18 ++--- raphtory/src/python/graph/views/graph_view.rs | 10 ++- raphtory/src/python/types/macros/timeops.rs | 10 ++- raphtory/src/python/types/repr.rs | 8 ++- .../src/python/types/wrappers/iterators.rs | 34 ++++----- raphtory/src/python/utils/mod.rs | 7 +- 19 files changed, 182 insertions(+), 193 deletions(-) diff --git a/raphtory/src/core/storage/timeindex.rs b/raphtory/src/core/storage/timeindex.rs index 1963960b4b..0117c3f560 100644 --- a/raphtory/src/core/storage/timeindex.rs +++ b/raphtory/src/core/storage/timeindex.rs @@ -3,6 +3,7 @@ use crate::{ core::{entities::LayerIds, utils::time::error::ParseTimeError}, db::api::mutation::{internal::InternalAdditionOps, InputTime, TryIntoInputTime}, }; +use chrono::{DateTime, Utc}; use itertools::{Itertools, KMerge}; use num_traits::Saturating; use serde::{Deserialize, Serialize}; @@ -20,6 +21,11 @@ pub struct TimeIndexEntry(pub i64, pub usize); pub trait AsTime: Debug + Copy + Ord + Eq + Send + Sync { fn t(&self) -> &i64; + + fn dt(&self) -> Option> { + >::from_timestamp(*self.t(), 0) + } + fn range(w: Range) -> Range; } diff --git a/raphtory/src/db/api/properties/internal.rs b/raphtory/src/db/api/properties/internal.rs index b6bd45e816..109006bb07 100644 --- a/raphtory/src/db/api/properties/internal.rs +++ b/raphtory/src/db/api/properties/internal.rs @@ -1,8 +1,8 @@ use crate::{ - core::{ArcStr, Prop}, + core::{storage::timeindex::AsTime, ArcStr, Prop}, db::api::view::internal::Base, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use enum_dispatch::enum_dispatch; #[enum_dispatch] @@ -11,11 +11,11 @@ pub trait TemporalPropertyViewOps { self.temporal_values(id).last().cloned() } fn temporal_history(&self, id: usize) -> Vec; - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.temporal_history(id) .iter() - .map(|t| NaiveDateTime::from_timestamp_millis(*t)) - .collect::>>() + .map(|t| t.dt()) + .collect::>>() } fn temporal_values(&self, id: usize) -> Vec; fn temporal_value_at(&self, id: usize, t: i64) -> Option { @@ -90,7 +90,7 @@ where self.base().temporal_history(id) } #[inline] - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.base().temporal_history_date_time(id) } diff --git a/raphtory/src/db/api/properties/temporal_props.rs b/raphtory/src/db/api/properties/temporal_props.rs index b68daaf2fe..8d3e756aac 100644 --- a/raphtory/src/db/api/properties/temporal_props.rs +++ b/raphtory/src/db/api/properties/temporal_props.rs @@ -3,7 +3,7 @@ use crate::{ db::api::properties::internal::PropertiesOps, prelude::Graph, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use std::{collections::HashMap, iter::Zip, sync::Arc}; pub struct TemporalPropertyView { @@ -18,7 +18,7 @@ impl TemporalPropertyView

{ pub fn history(&self) -> Vec { self.props.temporal_history(self.id) } - pub fn history_date_time(&self) -> Option> { + pub fn history_date_time(&self) -> Option>> { self.props.temporal_history_date_time(self.id) } pub fn values(&self) -> Vec { @@ -32,7 +32,7 @@ impl TemporalPropertyView

{ self.iter() } - pub fn histories_date_time(&self) -> Option> { + pub fn histories_date_time(&self) -> Option, Prop)>> { let hist = self.history_date_time()?; let vals = self.values(); Some(hist.into_iter().zip(vals)) diff --git a/raphtory/src/db/api/view/edge.rs b/raphtory/src/db/api/view/edge.rs index 78814fda6a..7c4a14ea37 100644 --- a/raphtory/src/db/api/view/edge.rs +++ b/raphtory/src/db/api/view/edge.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crate::{ core::{ @@ -71,14 +71,14 @@ pub trait EdgeViewOps<'graph>: /// List the activation timestamps for the edge fn history(&self) -> Vec; - /// List the activation timestamps for the edge as NaiveDateTime objects if parseable - fn history_date_time(&self) -> Option>; + /// List the activation timestamps for the edge as DateTime objects if parseable + fn history_date_time(&self) -> Option>>; /// List the deletion timestamps for the edge fn deletions(&self) -> Vec; - /// List the deletion timestamps for the edge as NaiveDateTime objects if parseable - fn deletions_date_time(&self) -> Option>; + /// List the deletion timestamps for the edge as DateTime objects if parseable + fn deletions_date_time(&self) -> Option>>; /// Check that the latest status of the edge is valid (i.e., not deleted) fn is_valid(&self) -> bool; @@ -118,17 +118,17 @@ pub trait EdgeViewOps<'graph>: /// Gets the first time an edge was seen fn earliest_time(&self) -> Option; - fn earliest_date_time(&self) -> Option; - fn latest_date_time(&self) -> Option; + fn earliest_date_time(&self) -> Option>; + fn latest_date_time(&self) -> Option>; /// Gets the latest time an edge was updated fn latest_time(&self) -> Option; - fn start_date_time(&self) -> Option; - fn end_date_time(&self) -> Option; + fn start_date_time(&self) -> Option>; + fn end_date_time(&self) -> Option>; /// Gets the time stamp of the edge if it is exploded fn time(&self) -> Option; - fn date_time(&self) -> Option; + fn date_time(&self) -> Option>; /// Gets the layer name for the edge if it is restricted to a single layer fn layer_name(&self) -> Option; @@ -152,11 +152,11 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.graph().edge_history(self.eref(), layer_ids) } - fn history_date_time(&self) -> Option> { + fn history_date_time(&self) -> Option>> { self.history() .into_iter() - .map(|t| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() + .map(|t| t.dt()) + .collect::>>() } fn deletions(&self) -> Vec { @@ -164,11 +164,11 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.graph().edge_deletion_history(self.eref(), layer_ids) } - fn deletions_date_time(&self) -> Option> { + fn deletions_date_time(&self) -> Option>> { self.deletions() .into_iter() - .map(|t| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() + .map(|t| t.dt()) + .collect::>>() } fn is_valid(&self) -> bool { @@ -238,16 +238,16 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.graph().edge_earliest_time(self.eref(), layer_ids) } - fn earliest_date_time(&self) -> Option { + fn earliest_date_time(&self) -> Option> { let layer_ids = self.graph().layer_ids().constrain_from_edge(self.eref()); let earliest_time = self.graph().edge_earliest_time(self.eref(), layer_ids); - NaiveDateTime::from_timestamp_millis(earliest_time?) + earliest_time?.dt() } - fn latest_date_time(&self) -> Option { + fn latest_date_time(&self) -> Option> { let layer_ids = self.graph().layer_ids().constrain_from_edge(self.eref()); let latest_time = self.graph().edge_latest_time(self.eref(), layer_ids); - NaiveDateTime::from_timestamp_millis(latest_time?) + latest_time?.dt() } /// Gets the latest time an edge was updated @@ -256,16 +256,12 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.graph().edge_latest_time(self.eref(), layer_ids) } - fn start_date_time(&self) -> Option { - self.graph() - .start() - .map(|t| NaiveDateTime::from_timestamp_millis(t).unwrap()) + fn start_date_time(&self) -> Option> { + self.graph().start().and_then(|t| t.dt()) } - fn end_date_time(&self) -> Option { - self.graph() - .end() - .map(|t| NaiveDateTime::from_timestamp_millis(t).unwrap()) + fn end_date_time(&self) -> Option> { + self.graph().end().and_then(|t| t.dt()) } /// Gets the time stamp of the edge if it is exploded @@ -273,10 +269,8 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.eref().time().map(|ti| *ti.t()) } - fn date_time(&self) -> Option { - self.eref() - .time() - .map(|ti| NaiveDateTime::from_timestamp_millis(*ti.t()).unwrap()) + fn date_time(&self) -> Option> { + self.eref().time().and_then(|t| t.dt()) } /// Gets the layer name for the edge if it is restricted to a single layer @@ -344,14 +338,14 @@ pub trait EdgeListOps<'graph>: /// Get the timestamp for the earliest activity of the edge fn earliest_time(self) -> Self::IterType>; - fn earliest_date_time(self) -> Self::IterType>; + fn earliest_date_time(self) -> Self::IterType>>; /// Get the timestamp for the latest activity of the edge fn latest_time(self) -> Self::IterType>; - fn latest_date_time(self) -> Self::IterType>; + fn latest_date_time(self) -> Self::IterType>>; - fn date_time(self) -> Self::IterType>; + fn date_time(self) -> Self::IterType>>; /// Get the timestamps of the edges if they are exploded fn time(self) -> Self::IterType>; @@ -363,11 +357,11 @@ pub trait EdgeListOps<'graph>: fn history(self) -> Self::IterType>; - fn history_date_time(self) -> Self::IterType>>; + fn history_date_time(self) -> Self::IterType>>>; fn deletions(self) -> Self::IterType>; - fn deletions_date_time(self) -> Self::IterType>>; + fn deletions_date_time(self) -> Self::IterType>>>; fn is_valid(self) -> Self::IterType; @@ -375,11 +369,11 @@ pub trait EdgeListOps<'graph>: fn start(self) -> Self::IterType>; - fn start_date_time(self) -> Self::IterType>; + fn start_date_time(self) -> Self::IterType>>; fn end(self) -> Self::IterType>; - fn end_date_time(self) -> Self::IterType>; + fn end_date_time(self) -> Self::IterType>>; fn at( self, diff --git a/raphtory/src/db/api/view/internal/materialize.rs b/raphtory/src/db/api/view/internal/materialize.rs index c9c267bf36..5408ae361d 100644 --- a/raphtory/src/db/api/view/internal/materialize.rs +++ b/raphtory/src/db/api/view/internal/materialize.rs @@ -33,7 +33,7 @@ use crate::{ }, prelude::{Layer, Prop}, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use enum_dispatch::enum_dispatch; use serde::{Deserialize, Serialize}; use std::path::Path; diff --git a/raphtory/src/db/api/view/node.rs b/raphtory/src/db/api/view/node.rs index dd5b0c9225..ed389b5ac3 100644 --- a/raphtory/src/db/api/view/node.rs +++ b/raphtory/src/db/api/view/node.rs @@ -1,6 +1,7 @@ use crate::{ core::{ entities::{edges::edge_ref::EdgeRef, VID}, + storage::timeindex::AsTime, Direction, }, db::{ @@ -18,7 +19,7 @@ use crate::{ }, prelude::{EdgeViewOps, GraphViewOps, LayerOps}, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; pub trait BaseNodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { type BaseGraph: GraphViewOps<'graph>; @@ -83,18 +84,18 @@ pub trait NodeViewOps<'graph>: Clone + TimeOps<'graph> + LayerOps<'graph> { /// Get the timestamp for the earliest activity of the node fn earliest_time(&self) -> Self::ValueType>; - fn earliest_date_time(&self) -> Self::ValueType>; + fn earliest_date_time(&self) -> Self::ValueType>>; /// Get the timestamp for the latest activity of the node fn latest_time(&self) -> Self::ValueType>; - fn latest_date_time(&self) -> Self::ValueType>; + fn latest_date_time(&self) -> Self::ValueType>>; /// Gets the history of the node (time that the node was added and times when changes were made to the node) fn history(&self) -> Self::ValueType>; - /// Gets the history of the node (time that the node was added and times when changes were made to the node) as NaiveDateTime objects if parseable - fn history_date_time(&self) -> Self::ValueType>>; + /// Gets the history of the node (time that the node was added and times when changes were made to the node) as DateTime objects if parseable + fn history_date_time(&self) -> Self::ValueType>>>; /// Get a view of the temporal properties of this node. /// @@ -189,8 +190,8 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { self.map(|g, v| g.node_earliest_time(v)) } #[inline] - fn earliest_date_time(&self) -> Self::ValueType> { - self.map(|g, v| NaiveDateTime::from_timestamp_millis(g.node_earliest_time(v)?)) + fn earliest_date_time(&self) -> Self::ValueType>> { + self.map(|g, v| g.node_earliest_time(v)?.dt()) } #[inline] @@ -199,8 +200,8 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { } #[inline] - fn latest_date_time(&self) -> Self::ValueType> { - self.map(|g, v| NaiveDateTime::from_timestamp_millis(g.node_latest_time(v)?)) + fn latest_date_time(&self) -> Self::ValueType>> { + self.map(|g, v| g.node_latest_time(v)?.dt()) } #[inline] @@ -208,12 +209,12 @@ impl<'graph, V: BaseNodeViewOps<'graph> + 'graph> NodeViewOps<'graph> for V { self.map(|g, v| g.node_history(v)) } #[inline] - fn history_date_time(&self) -> Self::ValueType>> { + fn history_date_time(&self) -> Self::ValueType>>> { self.map(|g, v| { g.node_history(v) .iter() - .map(|t| NaiveDateTime::from_timestamp_millis(*t)) - .collect::>>() + .map(|t| t.dt()) + .collect::>>() }) } diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs index 3b5cc9f146..0063c0f5ec 100644 --- a/raphtory/src/db/graph/edge.rs +++ b/raphtory/src/db/graph/edge.rs @@ -5,12 +5,12 @@ //! and can have properties associated with them. //! -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use crate::{ core::{ entities::{edges::edge_ref::EdgeRef, VID}, - storage::timeindex::TimeIndexEntry, + storage::timeindex::{AsTime, TimeIndexEntry}, utils::{errors::GraphError, time::IntoTime}, ArcStr, }, @@ -274,12 +274,12 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> TemporalProperty .map(|(t, _)| t) .collect() } - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.graph .temporal_edge_prop_vec(self.edge, id, self.graph.layer_ids()) .into_iter() - .map(|(t, _)| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() + .map(|(t, _)| t.dt()) + .collect() } fn temporal_values(&self, id: usize) -> Vec { @@ -404,7 +404,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.earliest_time())) } - fn earliest_date_time(self) -> Self::IterType> { + fn earliest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.earliest_date_time())) } @@ -413,11 +413,11 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.latest_time())) } - fn latest_date_time(self) -> Self::IterType> { + fn latest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.latest_date_time())) } - fn date_time(self) -> Self::IterType> { + fn date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.date_time())) } @@ -436,7 +436,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra fn history(self) -> Self::IterType> { Box::new(self.map(|e| e.history())) } - fn history_date_time(self) -> Self::IterType>> { + fn history_date_time(self) -> Self::IterType>>> { Box::new(self.map(|e| e.history_date_time())) } @@ -444,7 +444,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.deletions())) } - fn deletions_date_time(self) -> Self::IterType>> { + fn deletions_date_time(self) -> Self::IterType>>> { Box::new(self.map(|e| e.deletions_date_time())) } @@ -460,7 +460,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.start())) } - fn start_date_time(self) -> Self::IterType> { + fn start_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.start_date_time())) } @@ -468,7 +468,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.end())) } - fn end_date_time(self) -> Self::IterType> { + fn end_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.end_date_time())) } @@ -521,7 +521,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.earliest_time())) } - fn earliest_date_time(self) -> Self::IterType> { + fn earliest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.earliest_date_time())) } @@ -530,11 +530,11 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|e| e.latest_time())) } - fn latest_date_time(self) -> Self::IterType> { + fn latest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.latest_date_time())) } - fn date_time(self) -> Self::IterType> { + fn date_time(self) -> Self::IterType>> { Box::new(self.map(|it| it.date_time())) } @@ -553,7 +553,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|it| it.history())) } - fn history_date_time(self) -> Self::IterType>> { + fn history_date_time(self) -> Self::IterType>>> { Box::new(self.map(|it| it.history_date_time())) } @@ -561,7 +561,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|it| it.deletions())) } - fn deletions_date_time(self) -> Self::IterType>> { + fn deletions_date_time(self) -> Self::IterType>>> { Box::new(self.map(|it| it.deletions_date_time())) } @@ -577,7 +577,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|it| it.start())) } - fn start_date_time(self) -> Self::IterType> { + fn start_date_time(self) -> Self::IterType>> { Box::new(self.map(|it| it.start_date_time())) } @@ -585,7 +585,7 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> EdgeListOps<'gra Box::new(self.map(|it| it.end())) } - fn end_date_time(self) -> Self::IterType> { + fn end_date_time(self) -> Self::IterType>> { Box::new(self.map(|it| it.end_date_time())) } diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index dc62ebd753..3f06c73667 100644 --- a/raphtory/src/db/graph/node.rs +++ b/raphtory/src/db/graph/node.rs @@ -27,7 +27,8 @@ use crate::{ prelude::*, }; -use chrono::NaiveDateTime; +use crate::core::storage::timeindex::AsTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use std::{ fmt, hash::{Hash, Hasher}, @@ -211,12 +212,12 @@ impl TemporalPropertyViewOps for NodeView { .collect() } - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.graph .temporal_node_prop_vec(self.node, id) .into_iter() - .map(|(t, _)| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() + .map(|(t, _)| t.dt()) + .collect() } fn temporal_values(&self, id: usize) -> Vec { diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index f13e811e21..fac08745b2 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -40,6 +40,7 @@ use crate::{ core::{ entities::{edges::edge_ref::EdgeRef, nodes::node_ref::NodeRef, LayerIds, EID, VID}, + storage::timeindex::AsTime, utils::time::IntoTime, ArcStr, Direction, Prop, }, @@ -60,7 +61,7 @@ use crate::{ }, prelude::GraphViewOps, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use std::{ cmp::{max, min}, fmt::{Debug, Formatter}, @@ -126,11 +127,11 @@ impl<'graph, G: GraphViewOps<'graph>> TemporalPropertyViewOps for WindowedGraph< .collect() } - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.temporal_prop_vec(id) .into_iter() - .map(|(t, _)| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() + .map(|(t, _)| t.dt()) + .collect() } fn temporal_values(&self, id: usize) -> Vec { diff --git a/raphtory/src/db/internal/temporal_properties.rs b/raphtory/src/db/internal/temporal_properties.rs index 0e07b7a503..a3eea7008d 100644 --- a/raphtory/src/db/internal/temporal_properties.rs +++ b/raphtory/src/db/internal/temporal_properties.rs @@ -1,8 +1,8 @@ use crate::{ - core::{entities::graph::tgraph::InnerTemporalGraph, ArcStr, Prop}, + core::{entities::graph::tgraph::InnerTemporalGraph, storage::timeindex::AsTime, ArcStr, Prop}, db::api::properties::internal::{TemporalPropertiesOps, TemporalPropertyViewOps}, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; impl TemporalPropertyViewOps for InnerTemporalGraph { fn temporal_value(&self, id: usize) -> Option { @@ -18,15 +18,10 @@ impl TemporalPropertyViewOps for InnerTemporalGraph { .unwrap_or_default() } - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.inner() .get_temporal_prop(id) - .map(|prop| { - prop.iter() - .map(|(t, _)| NaiveDateTime::from_timestamp_millis(t)) - .collect::>>() - }) - .unwrap_or_default() + .and_then(|prop| prop.iter().map(|(t, _)| t.dt()).collect()) } fn temporal_values(&self, id: usize) -> Vec { diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index 057fe19da6..cd1082f50b 100644 --- a/raphtory/src/db/task/edge/eval_edge.rs +++ b/raphtory/src/db/task/edge/eval_edge.rs @@ -23,8 +23,7 @@ use crate::{ }, }, }; - -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use std::{cell::RefCell, rc::Rc}; pub struct EvalEdgeView<'graph, 'a, G, GH, CS: Clone, S> { @@ -178,7 +177,7 @@ impl< self.edge.temporal_history(id) } - fn temporal_history_date_time(&self, id: usize) -> Option> { + fn temporal_history_date_time(&self, id: usize) -> Option>> { self.edge.temporal_history_date_time(id) } @@ -295,7 +294,7 @@ impl< Box::new(self.map(|e| e.earliest_time())) } - fn earliest_date_time(self) -> Self::IterType> { + fn earliest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.earliest_date_time())) } @@ -303,11 +302,11 @@ impl< Box::new(self.map(|e| e.latest_time())) } - fn latest_date_time(self) -> Self::IterType> { + fn latest_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.latest_date_time())) } - fn date_time(self) -> Self::IterType> { + fn date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.date_time())) } @@ -327,7 +326,7 @@ impl< Box::new(self.map(|e| e.history())) } - fn history_date_time(self) -> Self::IterType>> { + fn history_date_time(self) -> Self::IterType>>> { Box::new(self.map(|e| e.history_date_time())) } @@ -335,7 +334,7 @@ impl< Box::new(self.map(|e| e.deletions())) } - fn deletions_date_time(self) -> Self::IterType>> { + fn deletions_date_time(self) -> Self::IterType>>> { Box::new(self.map(|e| e.deletions_date_time())) } @@ -351,7 +350,7 @@ impl< Box::new(self.map(|e| e.earliest_time())) } - fn start_date_time(self) -> Self::IterType> { + fn start_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.start_date_time())) } @@ -359,7 +358,7 @@ impl< Box::new(self.map(|e| e.latest_time())) } - fn end_date_time(self) -> Self::IterType> { + fn end_date_time(self) -> Self::IterType>> { Box::new(self.map(|e| e.end_date_time())) } diff --git a/raphtory/src/python/graph/edge.rs b/raphtory/src/python/graph/edge.rs index d7fd2a9607..81184838c3 100644 --- a/raphtory/src/python/graph/edge.rs +++ b/raphtory/src/python/graph/edge.rs @@ -35,16 +35,16 @@ use crate::{ repr::{iterator_repr, Repr}, wrappers::iterators::{ ArcStringVecIterable, BoolIterable, I64VecIterable, NestedArcStringVecIterable, - NestedBoolIterable, NestedI64VecIterable, NestedNaiveDateTimeIterable, - NestedOptionArcStringIterable, NestedOptionI64Iterable, NestedU64U64Iterable, - NestedVecNaiveDateTimeIterable, OptionArcStringIterable, OptionI64Iterable, - OptionNaiveDateTimeIterable, OptionVecNaiveDateTimeIterable, U64U64Iterable, + NestedBoolIterable, NestedI64VecIterable, NestedOptionArcStringIterable, + NestedOptionI64Iterable, NestedU64U64Iterable, NestedUtcDateTimeIterable, + NestedVecUtcDateTimeIterable, OptionArcStringIterable, OptionI64Iterable, + OptionUtcDateTimeIterable, OptionVecUtcDateTimeIterable, U64U64Iterable, }, }, utils::{PyGenericIterator, PyInterval, PyTime}, }, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use itertools::Itertools; use pyo3::{prelude::*, pyclass::CompareOp}; use std::{ @@ -214,7 +214,7 @@ impl PyEdge { /// Returns: /// A list of timestamps. /// - pub fn history_date_time(&self) -> Option> { + pub fn history_date_time(&self) -> Option>> { self.edge.history_date_time() } @@ -230,7 +230,7 @@ impl PyEdge { /// /// Returns: /// A list of DateTime objects - pub fn deletions_data_time(&self) -> Option> { + pub fn deletions_data_time(&self) -> Option>> { self.edge.deletions_date_time() } @@ -345,8 +345,8 @@ impl PyEdge { /// Returns: /// the earliest datetime of an edge #[getter] - pub fn earliest_date_time(&self) -> Option { - NaiveDateTime::from_timestamp_millis(self.edge.earliest_time()?) + pub fn earliest_date_time(&self) -> Option> { + self.edge.earliest_date_time() } /// Gets the latest time of an edge. @@ -363,9 +363,8 @@ impl PyEdge { /// Returns: /// (datetime) the latest datetime of an edge #[getter] - pub fn latest_date_time(&self) -> Option { - let latest_time = self.edge.latest_time()?; - NaiveDateTime::from_timestamp_millis(latest_time) + pub fn latest_date_time(&self) -> Option> { + self.edge.latest_date_time() } /// Gets the time of an exploded edge. @@ -400,9 +399,8 @@ impl PyEdge { /// Returns: /// (datetime) the datetime of an exploded edge #[getter] - pub fn date_time(&self) -> Option { - let date_time = self.edge.time()?; - NaiveDateTime::from_timestamp_millis(date_time) + pub fn date_time(&self) -> Option> { + self.edge.date_time() } /// Displays the Edge as a string. @@ -574,7 +572,7 @@ impl PyEdges { /// Returns: /// Earliest date time of the edges. #[getter] - fn earliest_date_time(&self) -> OptionNaiveDateTimeIterable { + fn earliest_date_time(&self) -> OptionUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().earliest_date_time()).into() } @@ -594,7 +592,7 @@ impl PyEdges { /// Returns: /// Latest date time of the edges. #[getter] - fn latest_date_time(&self) -> OptionNaiveDateTimeIterable { + fn latest_date_time(&self) -> OptionUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().latest_date_time()).into() } @@ -604,7 +602,7 @@ impl PyEdges { /// Returns: /// A list of date times. #[getter] - fn date_time(&self) -> OptionNaiveDateTimeIterable { + fn date_time(&self) -> OptionUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().date_time()).into() } @@ -648,7 +646,7 @@ impl PyEdges { /// Returns: /// A list of lists of timestamps. /// - fn history_date_time(&self) -> OptionVecNaiveDateTimeIterable { + fn history_date_time(&self) -> OptionVecUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().history_date_time()).into() } @@ -666,7 +664,7 @@ impl PyEdges { /// /// Returns: /// A list of lists of DateTime objects - fn deletions_date_time(&self) -> OptionVecNaiveDateTimeIterable { + fn deletions_date_time(&self) -> OptionVecUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().deletions_date_time()).into() } @@ -698,7 +696,7 @@ impl PyEdges { /// Returns: /// The start date time of all edges #[getter] - fn start_date_time(&self) -> OptionNaiveDateTimeIterable { + fn start_date_time(&self) -> OptionUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().start_date_time()).into() } @@ -718,7 +716,7 @@ impl PyEdges { /// Returns: /// The end date time of all edges #[getter] - fn end_date_time(&self) -> OptionNaiveDateTimeIterable { + fn end_date_time(&self) -> OptionUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().end_date_time()).into() } @@ -943,7 +941,7 @@ impl PyNestedEdges { /// Returns the earliest date time of the edges. #[getter] - fn earliest_date_time(&self) -> NestedNaiveDateTimeIterable { + fn earliest_date_time(&self) -> NestedUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().earliest_date_time()).into() } @@ -957,7 +955,7 @@ impl PyNestedEdges { /// Returns the latest date time of the edges. #[getter] - fn latest_date_time(&self) -> NestedNaiveDateTimeIterable { + fn latest_date_time(&self) -> NestedUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().latest_date_time()).into() } @@ -1042,7 +1040,7 @@ impl PyNestedEdges { } /// Returns all timestamps of edges, when an edge is added or change to an edge is made. - fn history_date_time(&self) -> NestedVecNaiveDateTimeIterable { + fn history_date_time(&self) -> NestedVecUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().history_date_time()).into() } @@ -1060,7 +1058,7 @@ impl PyNestedEdges { /// /// Returns: /// A list of lists of lists of DateTime objects - fn deletions_date_time(&self) -> NestedVecNaiveDateTimeIterable { + fn deletions_date_time(&self) -> NestedVecUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().deletions_date_time()).into() } @@ -1086,7 +1084,7 @@ impl PyNestedEdges { /// Get the start date time of all edges #[getter] - fn start_date_time(&self) -> NestedNaiveDateTimeIterable { + fn start_date_time(&self) -> NestedUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().start_date_time()).into() } @@ -1100,14 +1098,14 @@ impl PyNestedEdges { /// Get the end date time of all edges #[getter] - fn end_date_time(&self) -> NestedNaiveDateTimeIterable { + fn end_date_time(&self) -> NestedUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().end_date_time()).into() } /// Get the date times of exploded edges #[getter] - fn date_time(&self) -> NestedNaiveDateTimeIterable { + fn date_time(&self) -> NestedUtcDateTimeIterable { let edges = self.builder.clone(); (move || edges().date_time()).into() } diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 5f0d3f6eff..34b886136d 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -37,8 +37,7 @@ use crate::{ db::graph::path::{PathFromGraph, PathFromNode}, python::types::repr::StructReprBuilder, }; -use chrono::NaiveDateTime; - +use chrono::{DateTime, NaiveDateTime, Utc}; use pyo3::{ exceptions::{PyIndexError, PyKeyError}, prelude::*, @@ -149,7 +148,7 @@ impl PyNode { /// Returns: /// The earliest datetime that the node exists as an integer. #[getter] - pub fn earliest_date_time(&self) -> Option { + pub fn earliest_date_time(&self) -> Option> { self.node.earliest_date_time() } @@ -170,7 +169,7 @@ impl PyNode { /// Returns: /// The latest datetime that the node exists as an integer. #[getter] - pub fn latest_date_time(&self) -> Option { + pub fn latest_date_time(&self) -> Option> { self.node.latest_date_time() } @@ -301,7 +300,7 @@ impl PyNode { /// Returns: /// A list of timestamps of the event history of node. /// - pub fn history_date_time(&self) -> Option> { + pub fn history_date_time(&self) -> Option>> { self.node.history_date_time() } @@ -529,7 +528,7 @@ impl PyNodes { /// Returns: /// Earliest time of the nodes. #[getter] - fn earliest_date_time(&self) -> OptionNaiveDateTimeIterable { + fn earliest_date_time(&self) -> OptionUtcDateTimeIterable { let nodes = self.nodes.clone(); (move || nodes.earliest_date_time()).into() } @@ -546,7 +545,7 @@ impl PyNodes { /// Returns: /// Latest date time of the nodes. #[getter] - fn latest_date_time(&self) -> OptionNaiveDateTimeIterable { + fn latest_date_time(&self) -> OptionUtcDateTimeIterable { let nodes = self.nodes.clone(); (move || nodes.latest_date_time()).into() } @@ -567,7 +566,7 @@ impl PyNodes { /// Returns: /// An list of timestamps. /// - fn history_date_time(&self) -> OptionVecNaiveDateTimeIterable { + fn history_date_time(&self) -> OptionVecUtcDateTimeIterable { let nodes = self.nodes.clone(); (move || nodes.history_date_time()).into() } @@ -755,7 +754,7 @@ impl PyPathFromGraph { /// Returns the earliest date time of the nodes. #[getter] - fn earliest_date_time(&self) -> NestedNaiveDateTimeIterable { + fn earliest_date_time(&self) -> NestedUtcDateTimeIterable { let path = self.path.clone(); (move || path.earliest_date_time()).into() } @@ -768,7 +767,7 @@ impl PyPathFromGraph { /// Returns the latest date time of the nodes. #[getter] - fn latest_date_time(&self) -> NestedNaiveDateTimeIterable { + fn latest_date_time(&self) -> NestedUtcDateTimeIterable { let path = self.path.clone(); (move || path.latest_date_time()).into() } @@ -780,7 +779,7 @@ impl PyPathFromGraph { } /// Returns all timestamps of nodes, when an node is added or change to an node is made. - fn history_date_time(&self) -> NestedVecNaiveDateTimeIterable { + fn history_date_time(&self) -> NestedVecUtcDateTimeIterable { let path = self.path.clone(); (move || path.history_date_time()).into() } diff --git a/raphtory/src/python/graph/properties/temporal_props.rs b/raphtory/src/python/graph/properties/temporal_props.rs index 7f8570ab73..9a347447e1 100644 --- a/raphtory/src/python/graph/properties/temporal_props.rs +++ b/raphtory/src/python/graph/properties/temporal_props.rs @@ -23,7 +23,7 @@ use crate::{ utils::{PyGenericIterator, PyTime}, }, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; use itertools::Itertools; use pyo3::{ exceptions::{PyKeyError, PyTypeError}, @@ -126,18 +126,10 @@ impl PyTemporalProperties { /// /// Returns: /// dict[str, list[(datetime, Any)]]: the mapping of property keys to histories - fn histories_date_time(&self) -> HashMap>> { + fn histories_date_time(&self) -> HashMap, Prop)>>> { self.props .iter() - .map(|(k, v)| { - ( - k.clone(), - match v.histories_date_time() { - None => None, - Some(history) => Some(history.collect()), - }, - ) - }) + .map(|(k, v)| (k, v.histories_date_time().map(|h| h.collect()))) .collect() } @@ -234,7 +226,7 @@ impl PyTemporalProp { } /// Get the timestamps at which the property was updated - pub fn history_date_time(&self) -> Option> { + pub fn history_date_time(&self) -> Option>> { self.prop.history_date_time() } @@ -249,7 +241,7 @@ impl PyTemporalProp { } /// List update timestamps and corresponding property values - pub fn items_date_time(&self) -> Option> { + pub fn items_date_time(&self) -> Option, Prop)>> { Some(self.prop.histories_date_time()?.collect()) } diff --git a/raphtory/src/python/graph/views/graph_view.rs b/raphtory/src/python/graph/views/graph_view.rs index d3789e3c8a..ef45c20d61 100644 --- a/raphtory/src/python/graph/views/graph_view.rs +++ b/raphtory/src/python/graph/views/graph_view.rs @@ -121,9 +121,8 @@ impl PyGraphView { /// Returns: /// the datetime of the earliest activity in the graph #[getter] - pub fn earliest_date_time(&self) -> Option { - let earliest_time = self.graph.earliest_time()?; - NaiveDateTime::from_timestamp_millis(earliest_time) + pub fn earliest_date_time(&self) -> Option> { + self.graph.earliest_date_time() } /// Timestamp of latest activity in the graph @@ -140,9 +139,8 @@ impl PyGraphView { /// Returns: /// the datetime of the latest activity in the graph #[getter] - pub fn latest_date_time(&self) -> Option { - let latest_time = self.graph.latest_time()?; - NaiveDateTime::from_timestamp_millis(latest_time) + pub fn latest_date_time(&self) -> Option> { + self.graph.latest_date_time() } /// Number of edges in the graph diff --git a/raphtory/src/python/types/macros/timeops.rs b/raphtory/src/python/types/macros/timeops.rs index 1809e84d55..ca98585ef1 100644 --- a/raphtory/src/python/types/macros/timeops.rs +++ b/raphtory/src/python/types/macros/timeops.rs @@ -24,9 +24,8 @@ macro_rules! impl_timeops { /// Returns: #[doc = concat!(r" The earliest datetime that this ", $name, r" is valid or None if the ", $name, r" is valid for all times.")] #[getter] - pub fn start_date_time(&self) -> Option { - let start_time = self.$field.start()?; - NaiveDateTime::from_timestamp_millis(start_time) + pub fn start_date_time(&self) -> Option> { + self.$field.start_date_time() } #[doc = concat!(r" Gets the latest time that this ", $name, r" is valid.")] @@ -43,9 +42,8 @@ macro_rules! impl_timeops { /// Returns: #[doc = concat!(r" The latest datetime that this ", $name, r" is valid or None if the ", $name, r" is valid for all times.")] #[getter] - pub fn end_date_time(&self) -> Option { - let end_time = self.$field.end()?; - NaiveDateTime::from_timestamp_millis(end_time) + pub fn end_date_time(&self) -> Option> { + self.$field.end_date_time() } #[doc = concat!(r" Get the window size (difference between start and end) for this ", $name)] diff --git a/raphtory/src/python/types/repr.rs b/raphtory/src/python/types/repr.rs index 60dbe3356b..6648ec4c2e 100644 --- a/raphtory/src/python/types/repr.rs +++ b/raphtory/src/python/types/repr.rs @@ -1,5 +1,5 @@ use crate::core::{storage::locked_view::LockedView, ArcStr}; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, TimeZone}; use itertools::Itertools; use std::{collections::HashMap, ops::Deref}; @@ -144,6 +144,12 @@ impl Repr for NaiveDateTime { } } +impl Repr for DateTime { + fn repr(&self) -> String { + self.to_rfc2822() + } +} + impl Repr for Option { fn repr(&self) -> String { match &self { diff --git a/raphtory/src/python/types/wrappers/iterators.rs b/raphtory/src/python/types/wrappers/iterators.rs index 28c32cd9ad..f1aebb3c4c 100644 --- a/raphtory/src/python/types/wrappers/iterators.rs +++ b/raphtory/src/python/types/wrappers/iterators.rs @@ -1,5 +1,5 @@ use crate::{core::ArcStr, db::api::view::BoxedIter, prelude::Prop, python::types::repr::Repr}; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use itertools::Itertools; use num::cast::AsPrimitive; use pyo3::prelude::*; @@ -144,28 +144,28 @@ py_iterable_comp!( NestedI64VecIterableCmp ); -py_iterable!(OptionNaiveDateTimeIterable, Option); +py_iterable!(OptionUtcDateTimeIterable, Option>); py_iterable_comp!( - OptionNaiveDateTimeIterable, - Option, - OptionNaiveDateTimeIterableCmp + OptionUtcDateTimeIterable, + Option>, + OptionUtcDateTimeIterableCmp ); -py_nested_iterable!(NestedNaiveDateTimeIterable, Option); +py_nested_iterable!(NestedUtcDateTimeIterable, Option>); py_iterable_comp!( - NestedNaiveDateTimeIterable, - OptionNaiveDateTimeIterableCmp, - NestedNaiveDateTimeIterableCmp + NestedUtcDateTimeIterable, + OptionUtcDateTimeIterableCmp, + NestedUtcDateTimeIterableCmp ); -py_iterable!(OptionVecNaiveDateTimeIterable, Option>); +py_iterable!(OptionVecUtcDateTimeIterable, Option>>); py_iterable_comp!( - OptionVecNaiveDateTimeIterable, - Option>, - OptionVecNaiveDateTimeIterableCmp + OptionVecUtcDateTimeIterable, + Option>>, + OptionVecUtcDateTimeIterableCmp ); -py_nested_iterable!(NestedVecNaiveDateTimeIterable, Option>); +py_nested_iterable!(NestedVecUtcDateTimeIterable, Option>>); py_iterable_comp!( - NestedVecNaiveDateTimeIterable, - OptionVecNaiveDateTimeIterableCmp, - NestedVecNaiveDateTimeIterableCmp + NestedVecUtcDateTimeIterable, + OptionVecUtcDateTimeIterableCmp, + NestedVecUtcDateTimeIterableCmp ); diff --git a/raphtory/src/python/utils/mod.rs b/raphtory/src/python/utils/mod.rs index b76dee7287..baabf59070 100644 --- a/raphtory/src/python/utils/mod.rs +++ b/raphtory/src/python/utils/mod.rs @@ -5,12 +5,13 @@ use crate::{ core::{ entities::nodes::{input_node::InputNode, node_ref::NodeRef}, + storage::timeindex::AsTime, utils::time::{error::ParseTimeError, Interval, IntoTime, TryIntoTime}, }, db::api::view::*, python::graph::node::PyNode, }; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use pyo3::{exceptions::PyTypeError, prelude::*}; use std::{future::Future, thread}; @@ -204,11 +205,11 @@ where if window_set.temporal() { let iterable = move || { - let iter: Box + Send> = Box::new( + let iter: Box> + Send> = Box::new( window_set .clone() .time_index(center) - .map(|epoch| NaiveDateTime::from_timestamp_millis(epoch).unwrap()), + .map(|epoch| epoch.dt()), ); iter }; From 4615f58f581c854d48e9dc49db0ce56c4eb398bc Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Tue, 9 Jan 2024 13:57:55 +0100 Subject: [PATCH 04/13] fix inconsistent date time methods --- raphtory/src/db/api/view/edge.rs | 11 ----------- raphtory/src/db/api/view/graph.rs | 12 ++++++++++++ raphtory/src/db/api/view/time.rs | 14 +++++++++++++- raphtory/src/python/utils/mod.rs | 2 +- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/raphtory/src/db/api/view/edge.rs b/raphtory/src/db/api/view/edge.rs index 7c4a14ea37..c59b32811c 100644 --- a/raphtory/src/db/api/view/edge.rs +++ b/raphtory/src/db/api/view/edge.rs @@ -123,9 +123,6 @@ pub trait EdgeViewOps<'graph>: /// Gets the latest time an edge was updated fn latest_time(&self) -> Option; - fn start_date_time(&self) -> Option>; - fn end_date_time(&self) -> Option>; - /// Gets the time stamp of the edge if it is exploded fn time(&self) -> Option; fn date_time(&self) -> Option>; @@ -256,14 +253,6 @@ impl<'graph, E: EdgeViewInternalOps<'graph>> EdgeViewOps<'graph> for E { self.graph().edge_latest_time(self.eref(), layer_ids) } - fn start_date_time(&self) -> Option> { - self.graph().start().and_then(|t| t.dt()) - } - - fn end_date_time(&self) -> Option> { - self.graph().end().and_then(|t| t.dt()) - } - /// Gets the time stamp of the edge if it is exploded fn time(&self) -> Option { self.eref().time().map(|ti| *ti.t()) diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 4c17467027..5a7b6c49a2 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -1,6 +1,7 @@ use crate::{ core::{ entities::{graph::tgraph::InnerTemporalGraph, nodes::node_ref::NodeRef, LayerIds, VID}, + storage::timeindex::AsTime, utils::errors::GraphError, ArcStr, }, @@ -14,6 +15,7 @@ use crate::{ }, prelude::{DeletionOps, NO_PROPS}, }; +use chrono::{DateTime, Utc}; use rustc_hash::FxHashSet; /// This trait GraphViewOps defines operations for accessing @@ -41,8 +43,18 @@ pub trait GraphViewOps<'graph>: BoxableGraphView<'graph> + Sized + Clone + 'grap fn unique_layers(&self) -> BoxedIter; /// Timestamp of earliest activity in the graph fn earliest_time(&self) -> Option; + + /// UTC DateTime of earliest activity in the graph + fn earliest_date_time(&self) -> Option> { + self.earliest_time()?.dt() + } /// Timestamp of latest activity in the graph fn latest_time(&self) -> Option; + + /// UTC DateTime of latest activity in the graph + fn latest_date_time(&self) -> Option> { + self.latest_time()?.dt() + } /// Return the number of nodes in the graph. fn count_nodes(&self) -> usize; diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index 22dae73bfb..cdf3614762 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -1,10 +1,14 @@ use crate::{ - core::utils::time::{error::ParseTimeError, Interval, IntoTime}, + core::{ + storage::timeindex::AsTime, + utils::time::{error::ParseTimeError, Interval, IntoTime}, + }, db::{ api::view::internal::{OneHopFilter, TimeSemantics}, graph::views::window_graph::WindowedGraph, }, }; +use chrono::{DateTime, Utc}; use std::{ cmp::{max, min}, marker::PhantomData, @@ -17,9 +21,17 @@ pub trait TimeOps<'graph> { /// Return the timestamp of the default start for perspectives of the view (if any). fn start(&self) -> Option; + fn start_date_time(&self) -> Option> { + self.start()?.dt() + } + /// Return the timestamp of the default for perspectives of the view (if any). fn end(&self) -> Option; + fn end_date_time(&self) -> Option> { + self.end()?.dt() + } + /// set the start of the window to the larger of `start` and `self.start()` fn shrink_start(&self, start: T) -> Self::WindowedViewType { let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); diff --git a/raphtory/src/python/utils/mod.rs b/raphtory/src/python/utils/mod.rs index 82bbd429ec..2d3b5b2bf2 100644 --- a/raphtory/src/python/utils/mod.rs +++ b/raphtory/src/python/utils/mod.rs @@ -213,7 +213,7 @@ where window_set .clone() .time_index(center) - .map(|epoch| epoch.dt()), + .flat_map(|epoch| epoch.dt()), ); iter }; From 86505eb080dcf77e3e6dce19bb6352343875f7c7 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Tue, 9 Jan 2024 13:58:06 +0100 Subject: [PATCH 05/13] clean up warnings --- raphtory/src/db/graph/node.rs | 2 +- raphtory/src/db/graph/views/window_graph.rs | 2 +- raphtory/src/db/internal/temporal_properties.rs | 2 +- raphtory/src/python/graph/edge.rs | 2 +- raphtory/src/python/graph/node.rs | 2 +- raphtory/src/python/types/wrappers/iterators.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index 3f06c73667..4aca059486 100644 --- a/raphtory/src/db/graph/node.rs +++ b/raphtory/src/db/graph/node.rs @@ -28,7 +28,7 @@ use crate::{ }; use crate::core::storage::timeindex::AsTime; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use std::{ fmt, hash::{Hash, Hasher}, diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index fac08745b2..3d0a2ee66b 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -61,7 +61,7 @@ use crate::{ }, prelude::GraphViewOps, }; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use std::{ cmp::{max, min}, fmt::{Debug, Formatter}, diff --git a/raphtory/src/db/internal/temporal_properties.rs b/raphtory/src/db/internal/temporal_properties.rs index a3eea7008d..53a36049ec 100644 --- a/raphtory/src/db/internal/temporal_properties.rs +++ b/raphtory/src/db/internal/temporal_properties.rs @@ -2,7 +2,7 @@ use crate::{ core::{entities::graph::tgraph::InnerTemporalGraph, storage::timeindex::AsTime, ArcStr, Prop}, db::api::properties::internal::{TemporalPropertiesOps, TemporalPropertyViewOps}, }; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; impl TemporalPropertyViewOps for InnerTemporalGraph { fn temporal_value(&self, id: usize) -> Option { diff --git a/raphtory/src/python/graph/edge.rs b/raphtory/src/python/graph/edge.rs index 81184838c3..3c328c946f 100644 --- a/raphtory/src/python/graph/edge.rs +++ b/raphtory/src/python/graph/edge.rs @@ -44,7 +44,7 @@ use crate::{ utils::{PyGenericIterator, PyInterval, PyTime}, }, }; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use itertools::Itertools; use pyo3::{prelude::*, pyclass::CompareOp}; use std::{ diff --git a/raphtory/src/python/graph/node.rs b/raphtory/src/python/graph/node.rs index 34b886136d..f1cf430816 100644 --- a/raphtory/src/python/graph/node.rs +++ b/raphtory/src/python/graph/node.rs @@ -37,7 +37,7 @@ use crate::{ db::graph::path::{PathFromGraph, PathFromNode}, python::types::repr::StructReprBuilder, }; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use pyo3::{ exceptions::{PyIndexError, PyKeyError}, prelude::*, diff --git a/raphtory/src/python/types/wrappers/iterators.rs b/raphtory/src/python/types/wrappers/iterators.rs index f1aebb3c4c..39b77297a4 100644 --- a/raphtory/src/python/types/wrappers/iterators.rs +++ b/raphtory/src/python/types/wrappers/iterators.rs @@ -1,5 +1,5 @@ use crate::{core::ArcStr, db::api::view::BoxedIter, prelude::Prop, python::types::repr::Repr}; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use itertools::Itertools; use num::cast::AsPrimitive; use pyo3::prelude::*; From f6c9aa7701e2d48322fce040bbf49364dc089e59 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 13:11:16 +0100 Subject: [PATCH 06/13] fix time conversions --- python/tests/test_graphdb.py | 133 ++++++++++++------------- raphtory/src/core/storage/timeindex.rs | 5 +- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/python/tests/test_graphdb.py b/python/tests/test_graphdb.py index 7bccda19cb..3f5171ae81 100644 --- a/python/tests/test_graphdb.py +++ b/python/tests/test_graphdb.py @@ -9,11 +9,11 @@ from raphtory import graph_loader import tempfile from math import isclose -import datetime +from datetime import datetime, timezone import string edges = [(1, 1, 2), (2, 1, 3), (-1, 2, 1), (0, 1, 1), (7, 3, 2), (1, 1, 1)] - +utc = timezone.utc def create_graph(): g = Graph() @@ -248,8 +248,6 @@ def test_getitem(): def test_entity_history_date_time(): - import datetime - g = Graph() g.add_node(0, 1) g.add_node(1, 1) @@ -261,22 +259,22 @@ def test_entity_history_date_time(): e = g.add_edge(3, 1, 2) full_history_1 = [ - datetime.datetime(1970, 1, 1, 0, 0), - datetime.datetime(1970, 1, 1, 0, 0, 0, 1000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 2000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 3000), + datetime(1970, 1, 1, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 1000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 2000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 3000, tzinfo=utc), ] full_history_2 = [ - datetime.datetime(1970, 1, 1, 0, 0, 0, 4000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 5000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 6000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 7000), + datetime(1970, 1, 1, 0, 0, 0, 4000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 5000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 6000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc), ] windowed_history = [ - datetime.datetime(1970, 1, 1, 0, 0), - datetime.datetime(1970, 1, 1, 0, 0, 0, 1000), + datetime(1970, 1, 1, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 1000, tzinfo=utc), ] assert v.history_date_time() == full_history_1 @@ -297,32 +295,32 @@ def test_entity_history_date_time(): ] assert g.nodes.earliest_date_time == [ - datetime.datetime(1970, 1, 1, 0, 0), - datetime.datetime(1970, 1, 1, 0, 0), - datetime.datetime(1970, 1, 1, 0, 0, 0, 4000), + datetime(1970, 1, 1, tzinfo=utc), + datetime(1970, 1, 1, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 4000, tzinfo=utc), ] assert g.nodes.latest_date_time == [ - datetime.datetime(1970, 1, 1, 0, 0, 0, 7000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 3000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 7000), + datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 3000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc), ] assert g.nodes.neighbours.latest_date_time.collect() == [ [ - datetime.datetime(1970, 1, 1, 0, 0, 0, 3000), - datetime.datetime(1970, 1, 1, 0, 0, 0, 7000), + datetime(1970, 1, 1, 0, 0, 0, 3000, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc), ], - [datetime.datetime(1970, 1, 1, 0, 0, 0, 7000)], - [datetime.datetime(1970, 1, 1, 0, 0, 0, 7000)], + [datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc)], + [datetime(1970, 1, 1, 0, 0, 0, 7000, tzinfo=utc)], ] assert g.nodes.neighbours.earliest_date_time.collect() == [ [ - datetime.datetime(1970, 1, 1, 0, 0), - datetime.datetime(1970, 1, 1, 0, 0, 0, 4000), + datetime(1970, 1, 1, tzinfo=utc), + datetime(1970, 1, 1, 0, 0, 0, 4000, tzinfo=utc), ], - [datetime.datetime(1970, 1, 1, 0, 0)], - [datetime.datetime(1970, 1, 1, 0, 0)], + [datetime(1970, 1, 1, tzinfo=utc)], + [datetime(1970, 1, 1, tzinfo=utc)], ] @@ -1420,8 +1418,8 @@ def test_time_index(): rolling = w.rolling("1 day") time_index = rolling.time_index() assert list(time_index) == [ - datetime.datetime(2020, 1, 1, 23, 59, 59, 999000), - datetime.datetime(2020, 1, 2, 23, 59, 59, 999000), + datetime(2020, 1, 1, 23, 59, 59, 999000, tzinfo=utc), + datetime(2020, 1, 2, 23, 59, 59, 999000, tzinfo=utc), ] w = g.window(1, 3) @@ -1437,11 +1435,11 @@ def test_time_index(): def test_datetime_props(): g = Graph() - dt1 = datetime.datetime(2020, 1, 1, 23, 59, 59, 999000) + dt1 = datetime(2020, 1, 1, 23, 59, 59, 999000) g.add_node(0, 0, {"time": dt1}) assert g.node(0).properties.get("time") == dt1 - dt2 = datetime.datetime(2020, 1, 1, 23, 59, 59, 999999) + dt2 = datetime(2020, 1, 1, 23, 59, 59, 999999) g.add_node(0, 1, {"time": dt2}) assert g.node(1).properties.get("time") == dt2 @@ -1454,19 +1452,19 @@ def test_date_time(): g.add_edge("2014-02-04", 1, 4) g.add_edge("2014-02-05", 1, 2) - assert g.earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert g.latest_date_time == datetime.datetime(2014, 2, 5, 0, 0) + assert g.earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert g.latest_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) e = g.edge(1, 3) exploded_edges = [] for edge in e.explode(): exploded_edges.append(edge.date_time) - assert exploded_edges == [datetime.datetime(2014, 2, 3)] - assert g.edge(1, 2).earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert g.edge(1, 2).latest_date_time == datetime.datetime(2014, 2, 5, 0, 0) + assert exploded_edges == [datetime(2014, 2, 3)] + assert g.edge(1, 2).earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert g.edge(1, 2).latest_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) - assert g.node(1).earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert g.node(1).latest_date_time == datetime.datetime(2014, 2, 5, 0, 0) + assert g.node(1).earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert g.node(1).latest_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) def test_date_time_window(): @@ -1481,51 +1479,50 @@ def test_date_time_window(): view = g.window("2014-02-02", "2014-02-04") view2 = g.window("2014-02-02", "2014-02-05") - assert view.start_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view.end_date_time == datetime.datetime(2014, 2, 4, 0, 0) + assert view.start_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view.end_date_time == datetime(2014, 2, 4, 0, 0, tzinfo=utc) - assert view.earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view.latest_date_time == datetime.datetime(2014, 2, 3, 0, 0) + assert view.earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view.latest_date_time == datetime(2014, 2, 3, 0, 0, tzinfo=utc) - assert view2.edge(1, 2).start_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view2.edge(1, 2).end_date_time == datetime.datetime(2014, 2, 5, 0, 0) + assert view2.edge(1, 2).start_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view2.edge(1, 2).end_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) - assert view.node(1).earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view.node(1).latest_date_time == datetime.datetime(2014, 2, 3, 0, 0) + assert view.node(1).earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view.node(1).latest_date_time == datetime(2014, 2, 3, 0, 0, tzinfo=utc) e = view.edge(1, 2) exploded_edges = [] for edge in e.explode(): exploded_edges.append(edge.date_time) - assert exploded_edges == [datetime.datetime(2014, 2, 2)] + assert exploded_edges == [datetime(2014, 2, 2, tzinfo=utc)] def test_datetime_add_node(): g = Graph() - g.add_node(datetime.datetime(2014, 2, 2), 1) - g.add_node(datetime.datetime(2014, 2, 3), 2) - g.add_node(datetime.datetime(2014, 2, 4), 2) - g.add_node(datetime.datetime(2014, 2, 5), 4) - g.add_node(datetime.datetime(2014, 2, 6), 5) + g.add_node(datetime(2014, 2, 2), 1) + g.add_node(datetime(2014, 2, 3), 2) + g.add_node(datetime(2014, 2, 4), 2) + g.add_node(datetime(2014, 2, 5), 4) + g.add_node(datetime(2014, 2, 6), 5) view = g.window("2014-02-02", "2014-02-04") view2 = g.window("2014-02-02", "2014-02-05") - assert view.start_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view.end_date_time == datetime.datetime(2014, 2, 4, 0, 0) + assert view.start_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view.end_date_time == datetime(2014, 2, 4, 0, 0, tzinfo=utc) - assert view2.earliest_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view2.latest_date_time == datetime.datetime(2014, 2, 4, 0, 0) + assert view2.earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view2.latest_date_time == datetime(2014, 2, 4, 0, 0, tzinfo=utc) - assert view2.node(1).start_date_time == datetime.datetime(2014, 2, 2, 0, 0) - assert view2.node(1).end_date_time == datetime.datetime(2014, 2, 5, 0, 0) + assert view2.node(1).start_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) + assert view2.node(1).end_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) - assert view.node(2).earliest_date_time == datetime.datetime(2014, 2, 3, 0, 0) - assert view.node(2).latest_date_time == datetime.datetime(2014, 2, 3, 0, 0) + assert view.node(2).earliest_date_time == datetime(2014, 2, 3, 0, 0, tzinfo=utc) + assert view.node(2).latest_date_time == datetime(2014, 2, 3, 0, 0, tzinfo=utc) def test_datetime_with_timezone(): - from datetime import datetime from raphtory import Graph import pytz @@ -1540,12 +1537,12 @@ def test_datetime_with_timezone(): "Africa/Johannesburg", ] results = [ - datetime(2024, 1, 5, 1, 0), - datetime(2024, 1, 5, 6, 30), - datetime(2024, 1, 5, 10, 0), - datetime(2024, 1, 5, 12, 0), - datetime(2024, 1, 5, 17, 0), - datetime(2024, 1, 5, 18, 0), + datetime(2024, 1, 5, 1, 0, tzinfo=utc), + datetime(2024, 1, 5, 6, 30, tzinfo=utc), + datetime(2024, 1, 5, 10, 0, tzinfo=utc), + datetime(2024, 1, 5, 12, 0, tzinfo=utc), + datetime(2024, 1, 5, 17, 0, tzinfo=utc), + datetime(2024, 1, 5, 18, 0, tzinfo=utc), ] for tz in timezones: diff --git a/raphtory/src/core/storage/timeindex.rs b/raphtory/src/core/storage/timeindex.rs index 0117c3f560..aa951ac6f9 100644 --- a/raphtory/src/core/storage/timeindex.rs +++ b/raphtory/src/core/storage/timeindex.rs @@ -3,7 +3,7 @@ use crate::{ core::{entities::LayerIds, utils::time::error::ParseTimeError}, db::api::mutation::{internal::InternalAdditionOps, InputTime, TryIntoInputTime}, }; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use itertools::{Itertools, KMerge}; use num_traits::Saturating; use serde::{Deserialize, Serialize}; @@ -23,7 +23,8 @@ pub trait AsTime: Debug + Copy + Ord + Eq + Send + Sync { fn t(&self) -> &i64; fn dt(&self) -> Option> { - >::from_timestamp(*self.t(), 0) + let t = *self.t(); + NaiveDateTime::from_timestamp_millis(t).map(|dt| dt.and_utc()) } fn range(w: Range) -> Range; From 36ab07e541d62c877b8352a39a0e9b7b3fbcda58 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 13:11:58 +0100 Subject: [PATCH 07/13] update windowing semantics to allow resetting on one-hop only and support unbounded windows --- raphtory/src/core/utils/time.rs | 16 +++ raphtory/src/db/api/view/graph.rs | 9 +- .../db/api/view/internal/one_hop_filter.rs | 11 +- raphtory/src/db/api/view/layer.rs | 2 +- raphtory/src/db/api/view/time.rs | 133 ++++++++++++------ raphtory/src/db/graph/edge.rs | 9 +- raphtory/src/db/graph/graph.rs | 47 +++++++ raphtory/src/db/graph/node.rs | 9 +- raphtory/src/db/graph/nodes.rs | 9 +- raphtory/src/db/graph/path.rs | 18 ++- raphtory/src/db/graph/views/deletion_graph.rs | 43 ++++-- raphtory/src/db/graph/views/window_graph.rs | 117 +++++++++------ raphtory/src/db/internal/time_semantics.rs | 4 +- raphtory/src/db/task/edge/eval_edge.rs | 10 +- raphtory/src/db/task/node/eval_node.rs | 18 ++- 15 files changed, 331 insertions(+), 124 deletions(-) diff --git a/raphtory/src/core/utils/time.rs b/raphtory/src/core/utils/time.rs index 09d8fcfa2b..c64221294f 100644 --- a/raphtory/src/core/utils/time.rs +++ b/raphtory/src/core/utils/time.rs @@ -34,6 +34,22 @@ pub trait IntoTime { fn into_time(self) -> i64; } +pub trait IntoOptTime { + fn into_opt_time(self) -> Option; +} + +impl IntoOptTime for T { + fn into_opt_time(self) -> Option { + Some(self.into_time()) + } +} + +impl IntoOptTime for Option { + fn into_opt_time(self) -> Option { + self + } +} + impl IntoTime for i64 { fn into_time(self) -> i64 { self diff --git a/raphtory/src/db/api/view/graph.rs b/raphtory/src/db/api/view/graph.rs index 5a7b6c49a2..3437be200f 100644 --- a/raphtory/src/db/api/view/graph.rs +++ b/raphtory/src/db/api/view/graph.rs @@ -239,10 +239,15 @@ pub trait StaticGraphViewOps: for<'graph> GraphViewOps<'graph> + 'static {} impl GraphViewOps<'graph> + 'static> StaticGraphViewOps for G {} impl<'graph, G: GraphViewOps<'graph> + 'graph> OneHopFilter<'graph> for G { - type Graph = G; + type BaseGraph = G; + type FilteredGraph = G; type Filtered + 'graph> = GH; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { + &self + } + + fn base_graph(&self) -> &Self::BaseGraph { &self } diff --git a/raphtory/src/db/api/view/internal/one_hop_filter.rs b/raphtory/src/db/api/view/internal/one_hop_filter.rs index 11456cc41d..9c449f6f30 100644 --- a/raphtory/src/db/api/view/internal/one_hop_filter.rs +++ b/raphtory/src/db/api/view/internal/one_hop_filter.rs @@ -1,9 +1,14 @@ use crate::prelude::GraphViewOps; pub trait OneHopFilter<'graph> { - type Graph: GraphViewOps<'graph> + 'graph; - type Filtered + 'graph>: OneHopFilter<'graph> + 'graph; - fn current_filter(&self) -> &Self::Graph; + type BaseGraph: GraphViewOps<'graph> + 'graph; + type FilteredGraph: GraphViewOps<'graph> + 'graph; + + type Filtered + 'graph>: OneHopFilter<'graph, FilteredGraph = GH> + + 'graph; + fn current_filter(&self) -> &Self::FilteredGraph; + + fn base_graph(&self) -> &Self::BaseGraph; fn one_hop_filtered + 'graph>( &self, diff --git a/raphtory/src/db/api/view/layer.rs b/raphtory/src/db/api/view/layer.rs index 63a8c52b5f..80bb7148f1 100644 --- a/raphtory/src/db/api/view/layer.rs +++ b/raphtory/src/db/api/view/layer.rs @@ -21,7 +21,7 @@ pub trait LayerOps<'graph> { } impl<'graph, V: OneHopFilter<'graph> + InternalLayerOps + 'graph> LayerOps<'graph> for V { - type LayeredViewType = V::Filtered>; + type LayeredViewType = V::Filtered>; fn default_layer(&self) -> Self::LayeredViewType { self.one_hop_filtered(LayeredGraph::new(self.current_filter().clone(), 0.into())) diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index cdf3614762..56ee23bb4b 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -1,12 +1,13 @@ use crate::{ core::{ storage::timeindex::AsTime, - utils::time::{error::ParseTimeError, Interval, IntoTime}, + utils::time::{error::ParseTimeError, Interval, IntoOptTime, IntoTime}, }, db::{ api::view::internal::{OneHopFilter, TimeSemantics}, graph::views::window_graph::WindowedGraph, }, + prelude::GraphViewOps, }; use chrono::{DateTime, Utc}; use std::{ @@ -18,20 +19,26 @@ use std::{ pub trait TimeOps<'graph> { type WindowedViewType: TimeOps<'graph> + 'graph; - /// Return the timestamp of the default start for perspectives of the view (if any). + /// Return the timestamp of the start of the view or None if the view start is unbounded. fn start(&self) -> Option; fn start_date_time(&self) -> Option> { self.start()?.dt() } - /// Return the timestamp of the default for perspectives of the view (if any). + /// Return the timestamp of the of the view or None if the view end is unbounded. fn end(&self) -> Option; fn end_date_time(&self) -> Option> { self.end()?.dt() } + /// Return the start timestamp for WindowSets or None if the timeline is empty + fn timeline_start(&self) -> Option; + + /// Return the end timestamp for WindowSets or None if the timeline is empty + fn timeline_end(&self) -> Option; + /// set the start of the window to the larger of `start` and `self.start()` fn shrink_start(&self, start: T) -> Self::WindowedViewType { let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); @@ -53,7 +60,7 @@ pub trait TimeOps<'graph> { self.window(start, end) } - /// Return the size of the window covered by this view + /// Return the size of the window covered by this view or None if the window is unbounded fn window_size(&self) -> Option { match (self.start(), self.end()) { (Some(start), Some(end)) => Some((end - start) as u64), @@ -62,7 +69,11 @@ pub trait TimeOps<'graph> { } /// Create a view including all events between `start` (inclusive) and `end` (exclusive) - fn window(&self, start: T, end: T) -> Self::WindowedViewType; + fn window( + &self, + start: T1, + end: T2, + ) -> Self::WindowedViewType; /// Create a view that only includes events at `time` fn at(&self, time: T) -> Self::WindowedViewType { @@ -73,15 +84,13 @@ pub trait TimeOps<'graph> { /// Create a view that only includes events after `start` (exclusive) fn after(&self, start: T) -> Self::WindowedViewType { let start = start.into_time().saturating_add(1); - let end = i64::MAX; - self.window(start, end) + self.window(start, None) } /// Create a view that only includes events before `end` (exclusive) fn before(&self, end: T) -> Self::WindowedViewType { let end = end.into_time(); - let start = i64::MIN; - self.window(start, end) + self.window(None, end) } /// Creates a `WindowSet` with the given `step` size @@ -94,7 +103,7 @@ pub trait TimeOps<'graph> { I: TryInto, { let parent = self.clone(); - match (self.start(), self.end()) { + match (self.timeline_start(), self.timeline_end()) { (Some(start), Some(end)) => { let step: Interval = step.try_into()?; @@ -118,7 +127,7 @@ pub trait TimeOps<'graph> { I: TryInto, { let parent = self.clone(); - match (self.start(), self.end()) { + match (self.timeline_start(), self.timeline_end()) { (Some(start), Some(end)) => { let window: Interval = window.try_into()?; let step: Interval = match step { @@ -133,7 +142,7 @@ pub trait TimeOps<'graph> { } impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { - type WindowedViewType = V::Filtered>; + type WindowedViewType = V::Filtered>; fn start(&self) -> Option { self.current_filter().view_start() @@ -143,11 +152,44 @@ impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { self.current_filter().view_end() } - fn window(&self, start: T, end: T) -> Self::WindowedViewType { + fn timeline_start(&self) -> Option { + self.start() + .or_else(|| self.current_filter().earliest_time()) + } + + fn timeline_end(&self) -> Option { + self.end().or_else(|| { + self.current_filter() + .latest_time() + .map(|v| v.saturating_add(1)) + }) + } + + fn window( + &self, + start: T1, + end: T2, + ) -> Self::WindowedViewType { + let base_start = self.base_graph().start(); + let base_end = self.base_graph().end(); + let actual_start = match (base_start, start.into_opt_time()) { + (Some(base), Some(start)) => Some(max(base, start)), + (None, v) => v, + (v, None) => v, + }; + let actual_end = match (base_end, end.into_opt_time()) { + (Some(base), Some(end)) => Some(min(base, end)), + (None, v) => v, + (v, None) => v, + }; + let actual_end = match (actual_end, actual_start) { + (Some(end), Some(start)) => Some(max(end, start)), + _ => actual_end, + }; self.one_hop_filtered(WindowedGraph::new( self.current_filter().clone(), - start, - end, + actual_start, + actual_end, )) } } @@ -223,10 +265,7 @@ impl<'graph, T: TimeOps<'graph> + Clone + 'graph> Iterator for WindowSet<'graph, fn next(&mut self) -> Option { if self.cursor < self.end + self.step { let window_end = self.cursor; - let window_start = self - .window - .map(|w| window_end - w) - .unwrap_or(self.view.start().unwrap_or(window_end)); + let window_start = self.window.map(|w| window_end - w); let window = self.view.window(window_start, window_end); self.cursor = self.cursor + self.step; Some(window) @@ -256,18 +295,18 @@ mod time_tests { let g = Graph::new(); g.add_node(start, 0, NO_PROPS).unwrap(); g.add_node(end - 1, 0, NO_PROPS).unwrap(); - assert_eq!(g.start().unwrap(), start); - assert_eq!(g.end().unwrap(), end); + assert_eq!(g.timeline_start().unwrap(), start); + assert_eq!(g.timeline_end().unwrap(), end); g } - fn assert_bounds<'graph, G>(windows: WindowSet<'graph, G>, expected: Vec<(i64, i64)>) - where + fn assert_bounds<'graph, G>( + windows: WindowSet<'graph, G>, + expected: Vec<(Option, Option)>, + ) where G: GraphViewOps<'graph>, { - let window_bounds = windows - .map(|w| (w.start().unwrap(), w.end().unwrap())) - .collect_vec(); + let window_bounds = windows.map(|w| (w.start(), w.end())).collect_vec(); assert_eq!(window_bounds, expected) } @@ -275,34 +314,40 @@ mod time_tests { fn rolling() { let g = graph_with_timeline(1, 7); let windows = g.rolling(2, None).unwrap(); - let expected = vec![(1, 3), (3, 5), (5, 7)]; + let expected = vec![(Some(1), Some(3)), (Some(3), Some(5)), (Some(5), Some(7))]; assert_bounds(windows, expected); let g = graph_with_timeline(1, 6); let windows = g.rolling(3, Some(2)).unwrap(); - let expected = vec![(0, 3), (2, 5), (4, 7)]; + let expected = vec![(Some(0), Some(3)), (Some(2), Some(5)), (Some(4), Some(7))]; assert_bounds(windows, expected.clone()); let g = graph_with_timeline(0, 9).window(1, 6); let windows = g.rolling(3, Some(2)).unwrap(); - assert_bounds(windows, expected); + assert_bounds( + windows, + vec![(Some(1), Some(3)), (Some(2), Some(5)), (Some(4), Some(6))], + ); } #[test] fn expanding() { let g = graph_with_timeline(1, 7); let windows = g.expanding(2).unwrap(); - let expected = vec![(1, 3), (1, 5), (1, 7)]; + let expected = vec![(None, Some(3)), (None, Some(5)), (None, Some(7))]; assert_bounds(windows, expected); let g = graph_with_timeline(1, 6); let windows = g.expanding(2).unwrap(); - let expected = vec![(1, 3), (1, 5), (1, 7)]; + let expected = vec![(None, Some(3)), (None, Some(5)), (None, Some(7))]; assert_bounds(windows, expected.clone()); let g = graph_with_timeline(0, 9).window(1, 6); let windows = g.expanding(2).unwrap(); - assert_bounds(windows, expected); + assert_bounds( + windows, + vec![(Some(1), Some(3)), (Some(1), Some(5)), (Some(1), Some(6))], + ); } #[test] @@ -313,12 +358,12 @@ mod time_tests { let windows = g.rolling("1 day", None).unwrap(); let expected = vec![ ( - "2020-06-06 00:00:00".try_into_time().unwrap(), // entire 2020-06-06 - "2020-06-07 00:00:00".try_into_time().unwrap(), + "2020-06-06 00:00:00".try_into_time().ok(), // entire 2020-06-06 + "2020-06-07 00:00:00".try_into_time().ok(), ), ( - "2020-06-07 00:00:00".try_into_time().unwrap(), // entire 2020-06-06 - "2020-06-08 00:00:00".try_into_time().unwrap(), + "2020-06-07 00:00:00".try_into_time().ok(), // entire 2020-06-06 + "2020-06-08 00:00:00".try_into_time().ok(), ), ]; assert_bounds(windows, expected); @@ -329,12 +374,12 @@ mod time_tests { let windows = g.rolling("1 day", None).unwrap(); let expected = vec![ ( - "2020-06-06 00:00:00".try_into_time().unwrap(), // entire 2020-06-06 - "2020-06-07 00:00:00".try_into_time().unwrap(), + "2020-06-06 00:00:00".try_into_time().ok(), // entire 2020-06-06 + "2020-06-07 00:00:00".try_into_time().ok(), ), ( - "2020-06-07 00:00:00".try_into_time().unwrap(), // entire 2020-06-07 - "2020-06-08 00:00:00".try_into_time().unwrap(), + "2020-06-07 00:00:00".try_into_time().ok(), // entire 2020-06-07 + "2020-06-08 00:00:00".try_into_time().ok(), ), ]; assert_bounds(windows, expected); @@ -364,8 +409,8 @@ mod time_tests { let g = graph_with_timeline(start, end); let windows = g.expanding("1 day").unwrap(); let expected = vec![ - (start, "2020-06-07 00:00:00".try_into_time().unwrap()), - (start, "2020-06-08 00:00:00".try_into_time().unwrap()), + (None, "2020-06-07 00:00:00".try_into_time().ok()), + (None, "2020-06-08 00:00:00".try_into_time().ok()), ]; assert_bounds(windows, expected); @@ -374,8 +419,8 @@ mod time_tests { let g = graph_with_timeline(start, end); let windows = g.expanding("1 day").unwrap(); let expected = vec![ - (start, "2020-06-07 00:00:00".try_into_time().unwrap()), - (start, "2020-06-08 00:00:00".try_into_time().unwrap()), + (None, "2020-06-07 00:00:00".try_into_time().ok()), + (None, "2020-06-08 00:00:00".try_into_time().ok()), ]; assert_bounds(windows, expected); } diff --git a/raphtory/src/db/graph/edge.rs b/raphtory/src/db/graph/edge.rs index 0063c0f5ec..dd09286734 100644 --- a/raphtory/src/db/graph/edge.rs +++ b/raphtory/src/db/graph/edge.rs @@ -345,13 +345,18 @@ impl From> for EdgeRef { impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> OneHopFilter<'graph> for EdgeView { - type Graph = GH; + type BaseGraph = G; + type FilteredGraph = GH; type Filtered> = EdgeView; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.base_graph + } + fn one_hop_filtered + 'graph>( &self, filtered_graph: GHH, diff --git a/raphtory/src/db/graph/graph.rs b/raphtory/src/db/graph/graph.rs index cd5ab83834..5cb2f051ce 100644 --- a/raphtory/src/db/graph/graph.rs +++ b/raphtory/src/db/graph/graph.rs @@ -59,6 +59,53 @@ pub fn graph_equal<'graph1, 'graph2, G1: GraphViewOps<'graph1>, G2: GraphViewOps } } +pub fn assert_graph_equal< + 'graph1, + 'graph2, + G1: GraphViewOps<'graph1>, + G2: GraphViewOps<'graph2>, +>( + g1: &G1, + g2: &G2, +) { + assert_eq!( + g1.count_nodes(), + g2.count_nodes(), + "mismatched number of nodes: left {}, right {}", + g1.count_nodes(), + g2.count_nodes() + ); + assert_eq!( + g1.count_edges(), + g2.count_edges(), + "mismatched number of edges: left {}, right {}", + g1.count_edges(), + g2.count_edges() + ); + assert_eq!( + g1.count_temporal_edges(), + g2.count_temporal_edges(), + "mismatched number of temporal edges: left {}, right {}", + g1.count_temporal_edges(), + g2.count_temporal_edges() + ); + for n_id in g1.nodes().id() { + assert!(g2.has_node(n_id), "missing node {n_id}"); + } + for e in g1.edges().explode() { + // all exploded edges exist in other + let e2 = g2 + .edge(e.src().id(), e.dst().id()) + .expect(&format!("missing edge {:?}", e.id())); + assert!( + e2.active(e.time().unwrap()), + "exploded edge {:?} not active as expected at time {}", + e2.id(), + e.time().unwrap() + ) + } +} + impl Display for Graph { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) diff --git a/raphtory/src/db/graph/node.rs b/raphtory/src/db/graph/node.rs index 4aca059486..5b8bfe084b 100644 --- a/raphtory/src/db/graph/node.rs +++ b/raphtory/src/db/graph/node.rs @@ -143,13 +143,18 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> NodeView impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> OneHopFilter<'graph> for NodeView { - type Graph = GH; + type BaseGraph = G; + type FilteredGraph = GH; type Filtered> = NodeView; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, diff --git a/raphtory/src/db/graph/nodes.rs b/raphtory/src/db/graph/nodes.rs index 019340c14c..2b1b284c35 100644 --- a/raphtory/src/db/graph/nodes.rs +++ b/raphtory/src/db/graph/nodes.rs @@ -155,13 +155,18 @@ impl<'graph, G: GraphViewOps<'graph> + 'graph, GH: GraphViewOps<'graph> + 'graph impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> OneHopFilter<'graph> for Nodes<'graph, G, GH> { - type Graph = GH; + type BaseGraph = G; + type FilteredGraph = GH; type Filtered> = Nodes<'graph, G, GHH>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, diff --git a/raphtory/src/db/graph/path.rs b/raphtory/src/db/graph/path.rs index 498852c77a..e423e408ad 100644 --- a/raphtory/src/db/graph/path.rs +++ b/raphtory/src/db/graph/path.rs @@ -169,13 +169,18 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> IntoIterator impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> OneHopFilter<'graph> for PathFromGraph<'graph, G, GH> { - type Graph = GH; + type BaseGraph = G; + type FilteredGraph = GH; type Filtered> = PathFromGraph<'graph, G, GHH>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, @@ -329,13 +334,18 @@ impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> IntoIterator impl<'graph, G: GraphViewOps<'graph>, GH: GraphViewOps<'graph>> OneHopFilter<'graph> for PathFromNode<'graph, G, GH> { - type Graph = GH; + type BaseGraph = G; + type FilteredGraph = GH; type Filtered> = PathFromNode<'graph, G, GHH>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, diff --git a/raphtory/src/db/graph/views/deletion_graph.rs b/raphtory/src/db/graph/views/deletion_graph.rs index d874d4407b..e1c3bf1676 100644 --- a/raphtory/src/db/graph/views/deletion_graph.rs +++ b/raphtory/src/db/graph/views/deletion_graph.rs @@ -350,7 +350,7 @@ impl TimeSemantics for GraphWithDeletions { .collect(); alive_layers .into_iter() - .map(move |l| e.at(i64::MIN.into()).at_layer(l)) + .map(move |l| e.at(w.start.into()).at_layer(l)) .chain(self.graph.edge_window_exploded(e, w, layer_ids)) .into_dyn_boxed() } @@ -624,7 +624,10 @@ impl TimeSemantics for GraphWithDeletions { #[cfg(test)] mod test_deletions { - use crate::{db::graph::views::deletion_graph::GraphWithDeletions, prelude::*}; + use crate::{ + db::graph::{graph::assert_graph_equal, views::deletion_graph::GraphWithDeletions}, + prelude::*, + }; use itertools::Itertools; #[test] @@ -774,7 +777,7 @@ mod test_deletions { .unwrap() .into_persistent() .unwrap(); - assert_eq!(gm, g.window(3, 5)) + assert_graph_equal(&gm, &g.window(3, 5)) } #[test] @@ -786,6 +789,22 @@ mod test_deletions { assert_eq!(e.explode().latest_time().collect_vec(), vec![Some(10)]); } + #[test] + fn test_exploded_window() { + let g = GraphWithDeletions::new(); + let e = g.add_edge(0, 1, 2, NO_PROPS, None).unwrap(); + for t in [5, 10, 15] { + e.add_updates(t, NO_PROPS, None).unwrap(); + } + assert_eq!( + e.window(3, None) + .explode() + .map(|ee| ee.time().unwrap()) + .collect_vec(), + [3, 5, 10, 15] + ); + } + #[test] fn test_edge_properties() { let g = GraphWithDeletions::new(); @@ -946,20 +965,24 @@ mod test_deletions { fn test_view_start_end() { let g = GraphWithDeletions::new(); let e = g.add_edge(0, 1, 2, NO_PROPS, None).unwrap(); - assert_eq!(g.start(), Some(0)); - assert_eq!(g.end(), Some(1)); + assert_eq!(g.start(), None); + assert_eq!(g.timeline_start(), Some(0)); + assert_eq!(g.end(), None); + assert_eq!(g.timeline_end(), Some(1)); e.delete(2, None).unwrap(); - assert_eq!(g.start(), Some(0)); - assert_eq!(g.end(), Some(3)); - let w = g.window(g.start().unwrap(), g.end().unwrap()); + assert_eq!(g.timeline_start(), Some(0)); + assert_eq!(g.timeline_end(), Some(3)); + let w = g.window(g.timeline_start().unwrap(), g.timeline_end().unwrap()); assert!(g.has_edge(1, 2, Layer::All)); assert!(w.has_edge(1, 2, Layer::All)); assert_eq!(w.start(), Some(0)); + assert_eq!(w.timeline_start(), Some(0)); assert_eq!(w.end(), Some(3)); + assert_eq!(w.timeline_end(), Some(3)); e.add_updates(4, NO_PROPS, None).unwrap(); - assert_eq!(g.start(), Some(0)); - assert_eq!(g.end(), Some(5)); + assert_eq!(g.timeline_start(), Some(0)); + assert_eq!(g.timeline_end(), Some(5)); } #[test] diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 3d0a2ee66b..22ea745462 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -41,7 +41,7 @@ use crate::{ core::{ entities::{edges::edge_ref::EdgeRef, nodes::node_ref::NodeRef, LayerIds, EID, VID}, storage::timeindex::AsTime, - utils::time::IntoTime, + utils::time::{IntoOptTime, IntoTime}, ArcStr, Direction, Prop, }, db::{ @@ -75,11 +75,10 @@ pub struct WindowedGraph { /// The underlying `Graph` object. pub graph: G, /// The inclusive start time of the window. - pub start: i64, + pub start: Option, /// The exclusive end time of the window. - pub end: i64, + pub end: Option, filter: EdgeFilter, - window_filter: EdgeWindowFilter, } impl Static for WindowedGraph {} @@ -88,8 +87,8 @@ impl<'graph, G: Debug + 'graph> Debug for WindowedGraph { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "WindowedGraph({:?}, {}..{})", - self.graph, self.start, self.end + "WindowedGraph(start={:?}, end={:?}, graph={:?})", + self.start, self.end, self.graph, ) } } @@ -110,6 +109,16 @@ impl<'graph, G: GraphViewOps<'graph>> Base for WindowedGraph { } } +impl WindowedGraph { + fn start_bound(&self) -> i64 { + self.start.unwrap_or(i64::MIN) + } + + fn end_bound(&self) -> i64 { + self.end.unwrap_or(i64::MAX) + } +} + impl<'graph, G: GraphViewOps<'graph>> Immutable for WindowedGraph {} impl<'graph, G: GraphViewOps<'graph>> InheritCoreOps for WindowedGraph {} @@ -165,29 +174,32 @@ impl<'graph, G: GraphViewOps<'graph>> TemporalPropertiesOps for WindowedGraph impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn node_earliest_time(&self, v: VID) -> Option { self.graph - .node_earliest_time_window(v, self.start, self.end) + .node_earliest_time_window(v, self.start_bound(), self.end_bound()) } fn node_latest_time(&self, v: VID) -> Option { - self.graph.node_latest_time_window(v, self.start, self.end) + self.graph + .node_latest_time_window(v, self.start_bound(), self.end_bound()) } fn view_start(&self) -> Option { - Some(self.start) + self.start } fn view_end(&self) -> Option { - Some(self.end) + self.end } #[inline] fn earliest_time_global(&self) -> Option { - self.graph.earliest_time_window(self.start, self.end) + self.graph + .earliest_time_window(self.start_bound(), self.end_bound()) } #[inline] fn latest_time_global(&self) -> Option { - self.graph.latest_time_window(self.start, self.end) + self.graph + .latest_time_window(self.start_bound(), self.end_bound()) } #[inline] @@ -224,11 +236,12 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { #[inline] fn include_edge_window(&self) -> &EdgeWindowFilter { - &self.window_filter + self.graph.include_edge_window() } fn node_history(&self, v: VID) -> Vec { - self.graph.node_history_window(v, self.start..self.end) + self.graph + .node_history_window(v, self.start_bound()..self.end_bound()) } fn node_history_window(&self, v: VID, w: Range) -> Vec { @@ -237,7 +250,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn edge_history(&self, e: EdgeRef, layer_ids: LayerIds) -> Vec { self.graph - .edge_history_window(e, layer_ids, self.start..self.end) + .edge_history_window(e, layer_ids, self.start_bound()..self.end_bound()) } fn edge_history_window(&self, e: EdgeRef, layer_ids: LayerIds, w: Range) -> Vec { @@ -246,12 +259,12 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn edge_exploded(&self, e: EdgeRef, layer_ids: LayerIds) -> BoxedIter { self.graph - .edge_window_exploded(e, self.start..self.end, layer_ids) + .edge_window_exploded(e, self.start_bound()..self.end_bound(), layer_ids) } fn edge_layers(&self, e: EdgeRef, layer_ids: LayerIds) -> BoxedIter { self.graph - .edge_window_layers(e, self.start..self.end, layer_ids) + .edge_window_layers(e, self.start_bound()..self.end_bound(), layer_ids) } fn edge_window_exploded( @@ -275,7 +288,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn edge_earliest_time(&self, e: EdgeRef, layer_ids: LayerIds) -> Option { self.graph - .edge_earliest_time_window(e, self.start..self.end, layer_ids) + .edge_earliest_time_window(e, self.start_bound()..self.end_bound(), layer_ids) } fn edge_earliest_time_window( @@ -290,7 +303,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn edge_latest_time(&self, e: EdgeRef, layer_ids: LayerIds) -> Option { self.graph - .edge_latest_time_window(e, self.start..self.end, layer_ids) + .edge_latest_time_window(e, self.start_bound()..self.end_bound(), layer_ids) } fn edge_latest_time_window( @@ -305,7 +318,7 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn edge_deletion_history(&self, e: EdgeRef, layer_ids: LayerIds) -> Vec { self.graph - .edge_deletion_history_window(e, self.start..self.end, layer_ids) + .edge_deletion_history_window(e, self.start_bound()..self.end_bound(), layer_ids) } fn edge_deletion_history_window( @@ -319,7 +332,8 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn edge_is_valid(&self, e: EdgeRef, layer_ids: LayerIds) -> bool { - self.graph.edge_is_valid_at_end(e, layer_ids, self.end) + self.graph + .edge_is_valid_at_end(e, layer_ids, self.end_bound()) } fn edge_is_valid_at_end(&self, e: EdgeRef, layer_ids: LayerIds, t: i64) -> bool { @@ -329,12 +343,12 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn has_temporal_prop(&self, prop_id: usize) -> bool { self.graph - .has_temporal_prop_window(prop_id, self.start..self.end) + .has_temporal_prop_window(prop_id, self.start_bound()..self.end_bound()) } fn temporal_prop_vec(&self, prop_id: usize) -> Vec<(i64, Prop)> { self.graph - .temporal_prop_vec_window(prop_id, self.start, self.end) + .temporal_prop_vec_window(prop_id, self.start_bound(), self.end_bound()) } fn has_temporal_prop_window(&self, prop_id: usize, w: Range) -> bool { @@ -347,12 +361,12 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { fn has_temporal_node_prop(&self, v: VID, prop_id: usize) -> bool { self.graph - .has_temporal_node_prop_window(v, prop_id, self.start..self.end) + .has_temporal_node_prop_window(v, prop_id, self.start_bound()..self.end_bound()) } fn temporal_node_prop_vec(&self, v: VID, prop_id: usize) -> Vec<(i64, Prop)> { self.graph - .temporal_node_prop_vec_window(v, prop_id, self.start, self.end) + .temporal_node_prop_vec_window(v, prop_id, self.start_bound(), self.end_bound()) } fn has_temporal_node_prop_window(&self, v: VID, prop_id: usize, w: Range) -> bool { @@ -395,8 +409,12 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { } fn has_temporal_edge_prop(&self, e: EdgeRef, prop_id: usize, layer_ids: LayerIds) -> bool { - self.graph - .has_temporal_edge_prop_window(e, prop_id, self.start..self.end, layer_ids) + self.graph.has_temporal_edge_prop_window( + e, + prop_id, + self.start_bound()..self.end_bound(), + layer_ids, + ) } fn temporal_edge_prop_vec( @@ -405,8 +423,13 @@ impl<'graph, G: GraphViewOps<'graph>> TimeSemantics for WindowedGraph { prop_id: usize, layer_ids: LayerIds, ) -> Vec<(i64, Prop)> { - self.graph - .temporal_edge_prop_vec_window(e, prop_id, self.start, self.end, layer_ids) + self.graph.temporal_edge_prop_vec_window( + e, + prop_id, + self.start_bound(), + self.end_bound(), + layer_ids, + ) } } @@ -431,7 +454,12 @@ impl<'graph, G: GraphViewOps<'graph>> GraphOps<'graph> for WindowedGraph { self.graph .node_refs(layers.clone(), filter) .filter(move |v| { - g.include_node_window(*v, g.start..g.end, &layers, filter_cloned.as_ref()) + g.include_node_window( + *v, + g.start_bound()..g.end_bound(), + &layers, + filter_cloned.as_ref(), + ) }), ) } @@ -489,9 +517,9 @@ impl<'graph, G: GraphViewOps<'graph>> GraphOps<'graph> for WindowedGraph { layers: &LayerIds, filter: Option<&EdgeFilter>, ) -> Option { - self.graph - .internal_node_ref(v, layers, filter) - .filter(|v| self.include_node_window(*v, self.start..self.end, layers, filter)) + self.graph.internal_node_ref(v, layers, filter).filter(|v| { + self.include_node_window(*v, self.start_bound()..self.end_bound(), layers, filter) + }) } #[inline] @@ -660,30 +688,27 @@ impl<'graph, G: GraphViewOps<'graph>> WindowedGraph { /// Returns: /// /// A new windowed graph - pub fn new(graph: G, start: T, end: T) -> Self { - let start = start.into_time(); - let end = end.into_time(); + pub fn new(graph: G, start: T, end: T) -> Self { + let start = start.into_opt_time(); + let start_bound = start.unwrap_or(i64::MIN); + let end = end.into_opt_time(); + let end_bound = end.unwrap_or(i64::MAX); let base_filter = graph.edge_filter_window().cloned(); let base_window_filter = graph.include_edge_window().clone(); let filter: EdgeFilter = match base_filter { - Some(f) => { - Arc::new(move |e, layers| f(e, layers) && base_window_filter(e, layers, start..end)) + Some(f) => Arc::new(move |e, layers| { + f(e, layers) && base_window_filter(e, layers, start_bound..end_bound) + }), + None => { + Arc::new(move |e, layers| base_window_filter(e, layers, start_bound..end_bound)) } - None => Arc::new(move |e, layers| base_window_filter(e, layers, start..end)), }; - let base_window_filter = graph.include_edge_window().clone(); - let window_filter: EdgeWindowFilter = Arc::new(move |e, layers, w| { - let start = max(w.start, start); - let end = max(start, min(w.end, end)); - base_window_filter(e, layers, start..end) - }); WindowedGraph { graph, start, end, filter, - window_filter, } } } diff --git a/raphtory/src/db/internal/time_semantics.rs b/raphtory/src/db/internal/time_semantics.rs index 95d2579b3d..70e2b8a67f 100644 --- a/raphtory/src/db/internal/time_semantics.rs +++ b/raphtory/src/db/internal/time_semantics.rs @@ -28,11 +28,11 @@ impl TimeSemantics for InnerTemporalGraph { } fn view_start(&self) -> Option { - self.earliest_time_global() + None } fn view_end(&self) -> Option { - self.latest_time_global().map(|t| t.saturating_add(1)) // so it is exclusive + None } fn earliest_time_global(&self) -> Option { diff --git a/raphtory/src/db/task/edge/eval_edge.rs b/raphtory/src/db/task/edge/eval_edge.rs index cd1082f50b..565412df73 100644 --- a/raphtory/src/db/task/edge/eval_edge.rs +++ b/raphtory/src/db/task/edge/eval_edge.rs @@ -235,12 +235,18 @@ impl< CS: ComputeState + 'a, > OneHopFilter<'graph> for EvalEdgeView<'graph, 'a, G, GH, CS, S> { - type Graph = GH; + type BaseGraph = &'graph G; + type FilteredGraph = GH; type Filtered> = EvalEdgeView<'graph, 'a, G, GHH, CS, S>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.edge.graph } + + fn base_graph(&self) -> &Self::BaseGraph { + &self.edge.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, diff --git a/raphtory/src/db/task/node/eval_node.rs b/raphtory/src/db/task/node/eval_node.rs index 5e6e1875bc..1be3407563 100644 --- a/raphtory/src/db/task/node/eval_node.rs +++ b/raphtory/src/db/task/node/eval_node.rs @@ -420,13 +420,18 @@ impl< GH: GraphViewOps<'graph>, > OneHopFilter<'graph> for EvalPathFromNode<'graph, 'a, G, GH, CS, S> { - type Graph = GH; + type BaseGraph = &'graph G; + type FilteredGraph = GH; type Filtered> = EvalPathFromNode<'graph, 'a, G, GHH, CS, S>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { self.path.current_filter() } + fn base_graph(&self) -> &Self::BaseGraph { + &self.path.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, @@ -453,13 +458,18 @@ impl< GH: GraphViewOps<'graph>, > OneHopFilter<'graph> for EvalNodeView<'graph, 'a, G, S, GH, CS> { - type Graph = GH; + type BaseGraph = &'graph G; + type FilteredGraph = GH; type Filtered> = EvalNodeView<'graph, 'a, G, S, GHH, CS>; - fn current_filter(&self) -> &Self::Graph { + fn current_filter(&self) -> &Self::FilteredGraph { &self.node.graph } + fn base_graph(&self) -> &Self::BaseGraph { + &self.node.base_graph + } + fn one_hop_filtered>( &self, filtered_graph: GHH, From 6ffe1c2f85af53858f976dd221b5d9eb2193f11c Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 13:50:56 +0100 Subject: [PATCH 08/13] fix python tests --- python/tests/test_graphdb.py | 6 ++++-- raphtory/src/python/types/macros/timeops.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/python/tests/test_graphdb.py b/python/tests/test_graphdb.py index 3f5171ae81..3a672f7f72 100644 --- a/python/tests/test_graphdb.py +++ b/python/tests/test_graphdb.py @@ -1408,7 +1408,9 @@ def test_window_size(): g.add_node(1, 1) g.add_node(4, 4) - assert g.window_size == 4 + assert g.window_size is None + assert g.window(1, 5).window_size == 4 + def test_time_index(): @@ -1459,7 +1461,7 @@ def test_date_time(): exploded_edges = [] for edge in e.explode(): exploded_edges.append(edge.date_time) - assert exploded_edges == [datetime(2014, 2, 3)] + assert exploded_edges == [datetime(2014, 2, 3, tzinfo=utc)] assert g.edge(1, 2).earliest_date_time == datetime(2014, 2, 2, 0, 0, tzinfo=utc) assert g.edge(1, 2).latest_date_time == datetime(2014, 2, 5, 0, 0, tzinfo=utc) diff --git a/raphtory/src/python/types/macros/timeops.rs b/raphtory/src/python/types/macros/timeops.rs index ca98585ef1..0149efbbfd 100644 --- a/raphtory/src/python/types/macros/timeops.rs +++ b/raphtory/src/python/types/macros/timeops.rs @@ -86,16 +86,18 @@ macro_rules! impl_timeops { #[doc = concat!(r" Create a view of the ", $name, r" including all events between `start` (inclusive) and `end` (exclusive)")] /// /// Arguments: - /// start (int | DateTime | str): The start time of the window. - /// end (int | DateTime | str): The end time of the window. + /// start (int | DateTime | str | None): The start time of the window (unbounded if `None`). + /// end (int | DateTime | str | None): The end time of the window (unbounded if `None`). /// /// Returns: #[doc = concat!("r A ", $name, " object.")] pub fn window( &self, - start: PyTime, - end: PyTime, + start: Option, + end: Option, ) -> <$base_type as TimeOps<'static>>::WindowedViewType { + let start = start.map(|t| t.into_time()); + let end = end.map(|t| t.into_time()); self.$field .window(start, end) } From c85ba4196ddaca2547cc1836c25ebf0eaf4815c5 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 15:13:54 +0100 Subject: [PATCH 09/13] make some of TimeOps internal-only and remove IntoOptTime --- raphtory/src/core/utils/time.rs | 16 --- raphtory/src/db/api/view/mod.rs | 2 +- raphtory/src/db/api/view/time.rs | 106 +++++++++++------- raphtory/src/db/graph/views/deletion_graph.rs | 7 +- raphtory/src/db/graph/views/window_graph.rs | 24 ++-- raphtory/src/python/types/macros/timeops.rs | 6 +- raphtory/src/search/into_indexed.rs | 9 +- 7 files changed, 88 insertions(+), 82 deletions(-) diff --git a/raphtory/src/core/utils/time.rs b/raphtory/src/core/utils/time.rs index c64221294f..09d8fcfa2b 100644 --- a/raphtory/src/core/utils/time.rs +++ b/raphtory/src/core/utils/time.rs @@ -34,22 +34,6 @@ pub trait IntoTime { fn into_time(self) -> i64; } -pub trait IntoOptTime { - fn into_opt_time(self) -> Option; -} - -impl IntoOptTime for T { - fn into_opt_time(self) -> Option { - Some(self.into_time()) - } -} - -impl IntoOptTime for Option { - fn into_opt_time(self) -> Option { - self - } -} - impl IntoTime for i64 { fn into_time(self) -> i64 { self diff --git a/raphtory/src/db/api/view/mod.rs b/raphtory/src/db/api/view/mod.rs index d1048b9233..86137b1a31 100644 --- a/raphtory/src/db/api/view/mod.rs +++ b/raphtory/src/db/api/view/mod.rs @@ -5,7 +5,7 @@ mod graph; pub(crate) mod internal; mod layer; mod node; -mod time; +pub(crate) mod time; pub(crate) use edge::EdgeViewInternalOps; pub use edge::{EdgeListOps, EdgeViewOps}; diff --git a/raphtory/src/db/api/view/time.rs b/raphtory/src/db/api/view/time.rs index 56ee23bb4b..d2e26a7785 100644 --- a/raphtory/src/db/api/view/time.rs +++ b/raphtory/src/db/api/view/time.rs @@ -1,10 +1,13 @@ use crate::{ core::{ storage::timeindex::AsTime, - utils::time::{error::ParseTimeError, Interval, IntoOptTime, IntoTime}, + utils::time::{error::ParseTimeError, Interval, IntoTime}, }, db::{ - api::view::internal::{OneHopFilter, TimeSemantics}, + api::view::{ + internal::{OneHopFilter, TimeSemantics}, + time::internal::InternalTimeOps, + }, graph::views::window_graph::WindowedGraph, }, prelude::GraphViewOps, @@ -15,10 +18,31 @@ use std::{ marker::PhantomData, }; +pub(crate) mod internal { + use crate::prelude::TimeOps; + + pub trait InternalTimeOps<'graph> { + type InternalWindowedViewType: TimeOps<'graph> + 'graph; + + /// Return the start timestamp for WindowSets or None if the timeline is empty + fn timeline_start(&self) -> Option; + + /// Return the end timestamp for WindowSets or None if the timeline is empty + fn timeline_end(&self) -> Option; + + fn internal_window( + &self, + start: Option, + end: Option, + ) -> Self::InternalWindowedViewType; + } +} + /// Trait defining time query operations -pub trait TimeOps<'graph> { +pub trait TimeOps<'graph>: + InternalTimeOps<'graph, InternalWindowedViewType = Self::WindowedViewType> +{ type WindowedViewType: TimeOps<'graph> + 'graph; - /// Return the timestamp of the start of the view or None if the view start is unbounded. fn start(&self) -> Option; @@ -33,31 +57,23 @@ pub trait TimeOps<'graph> { self.end()?.dt() } - /// Return the start timestamp for WindowSets or None if the timeline is empty - fn timeline_start(&self) -> Option; - - /// Return the end timestamp for WindowSets or None if the timeline is empty - fn timeline_end(&self) -> Option; - /// set the start of the window to the larger of `start` and `self.start()` fn shrink_start(&self, start: T) -> Self::WindowedViewType { - let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); - let end = self.end().unwrap_or(start); - self.window(start, end) + let start = Some(max(start.into_time(), self.start().unwrap_or(i64::MIN))); + self.internal_window(start, self.end()) } /// set the end of the window to the smaller of `end` and `self.end()` fn shrink_end(&self, end: T) -> Self::WindowedViewType { - let end = min(end.into_time(), self.end().unwrap_or(i64::MAX)); - let start = self.start().unwrap_or(end); - self.window(start, end) + let end = Some(min(end.into_time(), self.end().unwrap_or(i64::MAX))); + self.internal_window(self.start(), end) } /// shrink both the start and end of the window (same as calling `shrink_start` followed by `shrink_end` but more efficient) fn shrink_window(&self, start: T, end: T) -> Self::WindowedViewType { let start = max(start.into_time(), self.start().unwrap_or(i64::MIN)); let end = min(end.into_time(), self.end().unwrap_or(i64::MAX)); - self.window(start, end) + self.internal_window(Some(start), Some(end)) } /// Return the size of the window covered by this view or None if the window is unbounded @@ -69,28 +85,26 @@ pub trait TimeOps<'graph> { } /// Create a view including all events between `start` (inclusive) and `end` (exclusive) - fn window( - &self, - start: T1, - end: T2, - ) -> Self::WindowedViewType; + fn window(&self, start: T1, end: T2) -> Self::WindowedViewType { + self.internal_window(Some(start.into_time()), Some(end.into_time())) + } /// Create a view that only includes events at `time` fn at(&self, time: T) -> Self::WindowedViewType { let start = time.into_time(); - self.window(start, start.saturating_add(1)) + self.internal_window(Some(start), Some(start.saturating_add(1))) } /// Create a view that only includes events after `start` (exclusive) fn after(&self, start: T) -> Self::WindowedViewType { let start = start.into_time().saturating_add(1); - self.window(start, None) + self.internal_window(Some(start), None) } /// Create a view that only includes events before `end` (exclusive) fn before(&self, end: T) -> Self::WindowedViewType { let end = end.into_time(); - self.window(None, end) + self.internal_window(None, Some(end)) } /// Creates a `WindowSet` with the given `step` size @@ -141,16 +155,8 @@ pub trait TimeOps<'graph> { } } -impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { - type WindowedViewType = V::Filtered>; - - fn start(&self) -> Option { - self.current_filter().view_start() - } - - fn end(&self) -> Option { - self.current_filter().view_end() - } +impl<'graph, V: OneHopFilter<'graph> + 'graph> InternalTimeOps<'graph> for V { + type InternalWindowedViewType = V::Filtered>; fn timeline_start(&self) -> Option { self.start() @@ -165,19 +171,19 @@ impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { }) } - fn window( + fn internal_window( &self, - start: T1, - end: T2, - ) -> Self::WindowedViewType { + start: Option, + end: Option, + ) -> Self::InternalWindowedViewType { let base_start = self.base_graph().start(); let base_end = self.base_graph().end(); - let actual_start = match (base_start, start.into_opt_time()) { + let actual_start = match (base_start, start) { (Some(base), Some(start)) => Some(max(base, start)), (None, v) => v, (v, None) => v, }; - let actual_end = match (base_end, end.into_opt_time()) { + let actual_end = match (base_end, end) { (Some(base), Some(end)) => Some(min(base, end)), (None, v) => v, (v, None) => v, @@ -194,6 +200,17 @@ impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { } } +impl<'graph, V: OneHopFilter<'graph> + 'graph> TimeOps<'graph> for V { + type WindowedViewType = Self::InternalWindowedViewType; + fn start(&self) -> Option { + self.current_filter().view_start() + } + + fn end(&self) -> Option { + self.current_filter().view_end() + } +} + #[derive(Clone)] pub struct WindowSet<'graph, T> { view: T, @@ -266,7 +283,7 @@ impl<'graph, T: TimeOps<'graph> + Clone + 'graph> Iterator for WindowSet<'graph, if self.cursor < self.end + self.step { let window_end = self.cursor; let window_start = self.window.map(|w| window_end - w); - let window = self.view.window(window_start, window_end); + let window = self.view.internal_window(window_start, Some(window_end)); self.cursor = self.cursor + self.step; Some(window) } else { @@ -282,7 +299,10 @@ mod time_tests { db::{ api::{ mutation::AdditionOps, - view::{time::WindowSet, TimeOps}, + view::{ + time::{internal::InternalTimeOps, WindowSet}, + TimeOps, + }, }, graph::graph::Graph, }, diff --git a/raphtory/src/db/graph/views/deletion_graph.rs b/raphtory/src/db/graph/views/deletion_graph.rs index e1c3bf1676..51c66ab81f 100644 --- a/raphtory/src/db/graph/views/deletion_graph.rs +++ b/raphtory/src/db/graph/views/deletion_graph.rs @@ -625,7 +625,10 @@ impl TimeSemantics for GraphWithDeletions { #[cfg(test)] mod test_deletions { use crate::{ - db::graph::{graph::assert_graph_equal, views::deletion_graph::GraphWithDeletions}, + db::{ + api::view::time::internal::InternalTimeOps, + graph::{graph::assert_graph_equal, views::deletion_graph::GraphWithDeletions}, + }, prelude::*, }; use itertools::Itertools; @@ -797,7 +800,7 @@ mod test_deletions { e.add_updates(t, NO_PROPS, None).unwrap(); } assert_eq!( - e.window(3, None) + e.after(2) .explode() .map(|ee| ee.time().unwrap()) .collect_vec(), diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index 22ea745462..b5da05f2bc 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -41,7 +41,6 @@ use crate::{ core::{ entities::{edges::edge_ref::EdgeRef, nodes::node_ref::NodeRef, LayerIds, EID, VID}, storage::timeindex::AsTime, - utils::time::{IntoOptTime, IntoTime}, ArcStr, Direction, Prop, }, db::{ @@ -63,7 +62,6 @@ use crate::{ }; use chrono::{DateTime, Utc}; use std::{ - cmp::{max, min}, fmt::{Debug, Formatter}, ops::Range, sync::Arc, @@ -688,10 +686,8 @@ impl<'graph, G: GraphViewOps<'graph>> WindowedGraph { /// Returns: /// /// A new windowed graph - pub fn new(graph: G, start: T, end: T) -> Self { - let start = start.into_opt_time(); + pub(crate) fn new(graph: G, start: Option, end: Option) -> Self { let start_bound = start.unwrap_or(i64::MIN); - let end = end.into_opt_time(); let end_bound = end.unwrap_or(i64::MAX); let base_filter = graph.edge_filter_window().cloned(); let base_window_filter = graph.include_edge_window().clone(); @@ -744,7 +740,7 @@ mod views_test { g.add_edge(*t, *src, *dst, NO_PROPS, None).unwrap(); } - let wg = WindowedGraph::new(g, -1, 1); + let wg = g.window(-1, 1); let actual = wg .nodes() @@ -796,7 +792,7 @@ mod views_test { g.add_edge(*t, *src, *dst, NO_PROPS, None).unwrap(); } - let wg = WindowedGraph::new(g, -1, 1); + let wg = g.window(-1, 1); assert_eq!(wg.node(1).unwrap().id(), 1); } @@ -819,7 +815,7 @@ mod views_test { .ok(); } - let wg = WindowedGraph::new(g, 1, 2); + let wg = g.window(1, 2); assert!(!wg.has_node(262)) } @@ -847,7 +843,7 @@ mod views_test { let start = vs.get(rand_start_index).expect("start index in range").0; let end = vs.get(rand_end_index).expect("end index in range").0; - let wg = WindowedGraph::new(g, start, end); + let wg = g.window(start, end); let rand_test_index: usize = thread_rng().gen_range(0..vs.len()); @@ -891,7 +887,7 @@ mod views_test { let start = edges.get(rand_start_index).expect("start index in range").0; let end = edges.get(rand_end_index).expect("end index in range").0; - let wg = WindowedGraph::new(g, start, end); + let wg = g.window(start, end); let rand_test_index: usize = thread_rng().gen_range(0..edges.len()); @@ -933,7 +929,7 @@ mod views_test { .unwrap(); } - let wg = WindowedGraph::new(g, window.start, window.end); + let wg = g.window(window.start, window.end); if wg.count_edges() != true_edge_count { println!( "failed, g.num_edges() = {}, true count = {}", @@ -1109,9 +1105,9 @@ mod views_test { fn test_reference() { let g = Graph::new(); g.add_edge(0, 1, 2, NO_PROPS, None).unwrap(); - let mut w = WindowedGraph::new(&g, 0, 1); + let mut w = WindowedGraph::new(&g, Some(0), Some(1)); assert_eq!(w, g); - w = WindowedGraph::new(&g, 1, 2); + w = WindowedGraph::new(&g, Some(1), Some(2)); assert_eq!(w, Graph::new()); } @@ -1120,7 +1116,7 @@ mod views_test { fn test_algorithm_on_windowed_graph() { let g = Graph::new(); g.add_edge(0, 1, 2, NO_PROPS, None).unwrap(); - let w = WindowedGraph::new(g, 0, 1); + let w = WindowedGraph::new(g, Some(0), Some(1)); let res = degree_centrality(&w, None); println!("{:?}", res) diff --git a/raphtory/src/python/types/macros/timeops.rs b/raphtory/src/python/types/macros/timeops.rs index 0149efbbfd..cda39eae89 100644 --- a/raphtory/src/python/types/macros/timeops.rs +++ b/raphtory/src/python/types/macros/timeops.rs @@ -93,11 +93,9 @@ macro_rules! impl_timeops { #[doc = concat!("r A ", $name, " object.")] pub fn window( &self, - start: Option, - end: Option, + start: PyTime, + end: PyTime, ) -> <$base_type as TimeOps<'static>>::WindowedViewType { - let start = start.map(|t| t.into_time()); - let end = end.map(|t| t.into_time()); self.$field .window(start, end) } diff --git a/raphtory/src/search/into_indexed.rs b/raphtory/src/search/into_indexed.rs index 733501f0de..0a5d3aa543 100644 --- a/raphtory/src/search/into_indexed.rs +++ b/raphtory/src/search/into_indexed.rs @@ -7,6 +7,7 @@ use crate::{ }, view::{ internal::{DynamicGraph, IntoDynamic, OneHopFilter}, + time::internal::InternalTimeOps, StaticGraphViewOps, }, }, @@ -14,7 +15,7 @@ use crate::{ layer_graph::LayeredGraph, node_subgraph::NodeSubgraph, window_graph::WindowedGraph, }, }, - prelude::{GraphViewOps, TimeOps}, + prelude::GraphViewOps, search::IndexedGraph, }; use std::sync::Arc; @@ -25,7 +26,11 @@ pub trait DynamicIndexedGraph { impl DynamicIndexedGraph for WindowedGraph> { fn into_dynamic_indexed(self) -> IndexedGraph { IndexedGraph { - graph: self.graph.graph.window(self.start, self.end).into_dynamic(), + graph: self + .graph + .graph + .internal_window(self.start, self.end) + .into_dynamic(), node_index: self.graph.node_index, edge_index: self.graph.edge_index, reader: self.graph.reader, From 87d7fa9fe2b0809b8dc6585bed5630aa7f2a9e2c Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 15:48:14 +0100 Subject: [PATCH 10/13] fix the benchmarks --- raphtory-benchmark/benches/base.rs | 4 ++-- raphtory-benchmark/benches/graph_ops.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/raphtory-benchmark/benches/base.rs b/raphtory-benchmark/benches/base.rs index 29bcd29543..fb178a534b 100644 --- a/raphtory-benchmark/benches/base.rs +++ b/raphtory-benchmark/benches/base.rs @@ -34,8 +34,8 @@ pub fn base(c: &mut Criterion) { ); graph_window_group_100.finish(); let mut graph_window_group_10 = c.benchmark_group("lotr_graph_window_10"); - let latest = graph.end().expect("non-empty graph"); - let earliest = graph.start().expect("non-empty graph"); + let latest = graph.latest_time().expect("non-empty graph"); + let earliest = graph.earliest_time().expect("non-empty graph"); let start = latest - (latest - earliest) / 10; graph_window_group_10.sample_size(10); run_analysis_benchmarks( diff --git a/raphtory-benchmark/benches/graph_ops.rs b/raphtory-benchmark/benches/graph_ops.rs index 962d009b42..9643f5edd4 100644 --- a/raphtory-benchmark/benches/graph_ops.rs +++ b/raphtory-benchmark/benches/graph_ops.rs @@ -18,8 +18,8 @@ pub fn graph(c: &mut Criterion) { ); graph_window_group_100.finish(); let mut graph_window_group_10 = c.benchmark_group("analysis_graph_window_10"); - let latest = graph.end().expect("non-empty graph"); - let earliest = graph.start().expect("non-empty graph"); + let latest = graph.latest_time().expect("non-empty graph"); + let earliest = graph.earliest_time().expect("non-empty graph"); let start = latest - (latest - earliest) / 10; graph_window_group_10.sample_size(10); run_analysis_benchmarks( From 628ee18b0569ccaaea17951c1211fbb893bcac1f Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Wed, 10 Jan 2024 17:57:48 +0100 Subject: [PATCH 11/13] add test --- raphtory/src/db/graph/views/window_graph.rs | 30 ++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/raphtory/src/db/graph/views/window_graph.rs b/raphtory/src/db/graph/views/window_graph.rs index b5da05f2bc..32ea35a903 100644 --- a/raphtory/src/db/graph/views/window_graph.rs +++ b/raphtory/src/db/graph/views/window_graph.rs @@ -714,7 +714,8 @@ mod views_test { use super::*; use crate::{ - algorithms::centrality::degree_centrality::degree_centrality, db::api::view::Layer, + algorithms::centrality::degree_centrality::degree_centrality, + db::{api::view::Layer, graph::graph::assert_graph_equal}, prelude::*, }; use itertools::Itertools; @@ -1122,6 +1123,33 @@ mod views_test { println!("{:?}", res) } + #[test] + fn test_view_resetting() { + let g = Graph::new(); + for t in 0..10 { + let t1 = t * 3; + let t2 = t * 3 + 1; + let t3 = t * 3 + 2; + g.add_edge(t1, 1, 2, NO_PROPS, None).unwrap(); + g.add_edge(t2, 2, 3, NO_PROPS, None).unwrap(); + g.add_edge(t3, 3, 1, NO_PROPS, None).unwrap(); + } + assert_graph_equal(&g.before(9).after(2), &g.window(3, 9)); + let res = g + .window(3, 9) + .nodes() + .before(6) + .edges() + .window(1, 9) + .earliest_time() + .map(|it| it.collect_vec()) + .collect_vec(); + assert_eq!( + res, + [[Some(3), Some(5)], [Some(3), Some(4)], [Some(5), Some(4)]] + ); + } + #[test] fn test_entity_history() { let g = Graph::new(); From 84b658c51a26fa774144d95180fcc98e3d65b7b3 Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Thu, 11 Jan 2024 10:43:27 +0100 Subject: [PATCH 12/13] fix latest time for windowed edges in GraphWithDeletions --- raphtory/src/db/graph/views/deletion_graph.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/raphtory/src/db/graph/views/deletion_graph.rs b/raphtory/src/db/graph/views/deletion_graph.rs index 51c66ab81f..a0105e888c 100644 --- a/raphtory/src/db/graph/views/deletion_graph.rs +++ b/raphtory/src/db/graph/views/deletion_graph.rs @@ -445,10 +445,19 @@ impl TimeSemantics for GraphWithDeletions { None => { let entry = self.core_edge(e.pid()); if edge_alive_at_end(entry.deref(), w.end, &layer_ids) { - Some(w.end - 1) - } else { - self.edge_deletions(e, layer_ids).range(w).last_t() + return Some(w.end - 1); } + entry + .updates_iter(&layer_ids) + .flat_map(|(_, additions, deletions)| { + let last_deletion = deletions.range(w.clone()).last()?; + if last_deletion.t() > &w.start || additions.active(w.clone()) { + Some(*last_deletion.t()) + } else { + None + } + }) + .max() } } } @@ -957,6 +966,9 @@ mod test_deletions { let g = GraphWithDeletions::new(); let e = g.add_edge(0, 1, 2, NO_PROPS, None).unwrap(); e.delete(2, None).unwrap(); + assert_eq!(e.at(2).earliest_time(), None); + assert_eq!(e.at(2).latest_time(), None); + assert!(e.at(2).is_deleted()); assert_eq!(e.latest_time(), Some(2)); e.add_updates(4, NO_PROPS, None).unwrap(); assert_eq!(e.latest_time(), Some(i64::MAX)); From 91553e50f743f5d479aaffd001c0256f524bd04d Mon Sep 17 00:00:00 2001 From: Lucas Jeub Date: Thu, 11 Jan 2024 11:06:32 +0100 Subject: [PATCH 13/13] more python tests for deletions --- python/tests/test_graphdb.py | 38 ++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/python/tests/test_graphdb.py b/python/tests/test_graphdb.py index 3a672f7f72..b36c14641f 100644 --- a/python/tests/test_graphdb.py +++ b/python/tests/test_graphdb.py @@ -1648,14 +1648,48 @@ def test_materialize_graph(): def test_deletions(): g = create_graph_with_deletions() + deleted_edge = g.edge(edges[0][1], edges[0][2]) for e in edges: assert g.at(e[0]).has_edge(e[1], e[2]) - + assert g.after(e[0]).has_edge(e[1], e[2]) + + for e in edges[:-1]: + # last update is an existing edge + assert not g.before(e[0]).has_edge(e[1], e[2]) + + # deleted at window start + assert deleted_edge.window(10, 20).is_deleted() + assert not deleted_edge.window(10, 20).is_valid() + assert deleted_edge.window(10, 20).earliest_time is None + assert deleted_edge.window(10, 20).latest_time is None + + # deleted before window start + assert deleted_edge.window(15, 20).is_deleted() + assert not deleted_edge.window(15, 20).is_valid() + assert deleted_edge.window(15, 20).earliest_time is None + assert deleted_edge.window(15, 20).latest_time is None + + # deleted in window + assert deleted_edge.window(5, 20).is_deleted() + assert not deleted_edge.window(5, 20).is_valid() + assert deleted_edge.window(5, 20).earliest_time == 5 + assert deleted_edge.window(5, 20).latest_time == 10 + + # check deleted edge is gone at 10 assert not g.after(start=10).has_edge(edges[0][1], edges[0][2]) + assert not g.at(10).has_edge(edges[0][1], edges[0][2]) + assert g.before(10).has_edge(edges[0][1], edges[0][2]) + + # check not deleted edges are still there for e in edges[1:]: assert g.after(start=10).has_edge(e[1], e[2]) - assert list(g.edge(edges[0][1], edges[0][2]).explode().latest_time) == [10] + assert list(deleted_edge.explode().latest_time) == [10] + assert list(deleted_edge.explode().earliest_time) == [edges[0][0]] + + # check rolling and expanding behaviour + assert not list(g.before(1).node(1).after(1).rolling(1)) + assert not list(g.after(0).edge(1, 1).before(1).expanding(1)) def test_edge_layer():