Jelajahi Sumber

[webgraph] sort edges by centrality rank instead of raw centrality

this ensures consistent ordering of edges across segments/shards
Mikkel Denker 8 bulan lalu
induk
melakukan
3b2a5f7895

+ 3 - 1
crates/core/src/entrypoint/ampc/approximated_harmonic_centrality/coordinator.rs

@@ -214,7 +214,7 @@ pub fn run(config: ApproxHarmonicCoordinatorConfig) -> Result<()> {
 
     let norm = 1.0 / ((num_samples - 1) as f64);
 
-    let jobs = cluster
+    let jobs: Vec<_> = cluster
         .workers
         .iter()
         .map(|worker| ApproxCentralityJob {
@@ -230,6 +230,8 @@ pub fn run(config: ApproxHarmonicCoordinatorConfig) -> Result<()> {
         })
         .collect();
 
+    tracing::info!("starting {} jobs", jobs.len());
+
     let coordinator = build(
         &cluster.dht,
         cluster.workers.clone(),

+ 1 - 1
crates/core/src/entrypoint/webgraph.rs

@@ -152,7 +152,7 @@ impl WebgraphWorker {
                             to: destination,
                             rel_flags: link.rel,
                             label: link.text,
-                            sort_score: source_centrality + destination_centrality,
+                            sort_score: source_rank.saturating_add(destination_rank),
                             from_centrality: source_centrality,
                             to_centrality: destination_centrality,
                             from_rank: source_rank,

+ 2 - 2
crates/core/src/webgraph/document.rs → crates/core/src/webgraph/edge.rs

@@ -171,7 +171,7 @@ pub struct Edge {
     pub to: Node,
     pub rel_flags: RelFlags,
     pub label: String,
-    pub sort_score: f64,
+    pub sort_score: u64,
     pub from_centrality: f64,
     pub to_centrality: f64,
     pub from_rank: u64,
@@ -186,7 +186,7 @@ impl Edge {
             to: Node::empty(),
             rel_flags: RelFlags::default(),
             label: String::default(),
-            sort_score: 0.0,
+            sort_score: 0,
             from_centrality: 0.0,
             to_centrality: 0.0,
             from_rank: 0,

+ 2 - 2
crates/core/src/webgraph/mod.rs

@@ -27,7 +27,7 @@ use rustc_hash::FxHashSet;
 use store::EdgeStore;
 
 pub use builder::WebgraphBuilder;
-pub use document::*;
+pub use edge::*;
 pub use node::*;
 pub use shortest_path::ShortestPaths;
 
@@ -36,7 +36,7 @@ use searcher::Searcher;
 mod builder;
 pub mod centrality;
 mod doc_address;
-mod document;
+mod edge;
 mod node;
 pub mod query;
 pub mod remote;

+ 11 - 11
crates/core/src/webgraph/query/backlink.rs

@@ -26,7 +26,7 @@ use crate::{
     ampc::dht::ShardId,
     webgraph::{
         doc_address::DocAddress,
-        document::Edge,
+        edge::Edge,
         schema::{Field, FromHostId, FromId, RelFlags, SortScore, ToHostId, ToId},
         searcher::Searcher,
         EdgeLimit, Node, NodeID, SmallEdge, SmallEdgeWithLabel,
@@ -38,7 +38,7 @@ pub fn fetch_small_edges<F: Field>(
     searcher: &Searcher,
     mut doc_ids: Vec<DocAddress>,
     node_id_field: F,
-) -> Result<Vec<(NodeID, crate::webpage::RelFlags, f64)>> {
+) -> Result<Vec<(NodeID, crate::webpage::RelFlags, u64)>> {
     doc_ids.sort_unstable_by_key(|doc| doc.segment_ord);
     let mut prev_segment_id = None;
     let mut field_column = None;
@@ -58,7 +58,7 @@ pub fn fetch_small_edges<F: Field>(
             );
             field_column = Some(segment_column_fields.u128(node_id_field).unwrap());
             rel_flags_column = Some(segment_column_fields.u64(RelFlags).unwrap());
-            score_column = Some(segment_column_fields.f64(SortScore).unwrap());
+            score_column = Some(segment_column_fields.u64(SortScore).unwrap());
         }
 
         let Some(id) = field_column.as_ref().unwrap().first(doc.doc_id) else {
@@ -140,7 +140,7 @@ impl BacklinksQuery {
 impl Query for BacklinksQuery {
     type Collector = TopDocsCollector;
     type TantivyQuery = Box<dyn tantivy::query::Query>;
-    type IntermediateOutput = Vec<(f64, SmallEdge)>;
+    type IntermediateOutput = Vec<(u64, SmallEdge)>;
     type Output = Vec<SmallEdge>;
 
     fn tantivy_query(&self, searcher: &Searcher) -> Self::TantivyQuery {
@@ -221,7 +221,7 @@ impl Query for BacklinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|(a, _), (b, _)| b.total_cmp(a));
+        edges.sort_by(|(a, _), (b, _)| a.cmp(b));
         edges.into_iter().map(|(_, e)| e).collect()
     }
 }
@@ -270,7 +270,7 @@ impl HostBacklinksQuery {
 impl Query for HostBacklinksQuery {
     type Collector = TopDocsCollector<DefaultDocumentScorer, HostDeduplicator>;
     type TantivyQuery = Box<dyn tantivy::query::Query>;
-    type IntermediateOutput = Vec<(f64, SmallEdge)>;
+    type IntermediateOutput = Vec<(u64, SmallEdge)>;
     type Output = Vec<SmallEdge>;
 
     fn tantivy_query(&self, searcher: &Searcher) -> Self::TantivyQuery {
@@ -355,7 +355,7 @@ impl Query for HostBacklinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|(a, _), (b, _)| b.total_cmp(a));
+        edges.sort_by(|(a, _), (b, _)| a.cmp(b));
         edges.into_iter().map(|(_, e)| e).collect()
     }
 }
@@ -494,7 +494,7 @@ impl Query for FullBacklinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|a, b| b.sort_score.total_cmp(&a.sort_score));
+        edges.sort_by(|a, b| b.sort_score.cmp(&a.sort_score));
         edges
     }
 }
@@ -658,7 +658,7 @@ impl Query for FullHostBacklinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|a, b| b.sort_score.total_cmp(&a.sort_score));
+        edges.sort_by(|a, b| b.sort_score.cmp(&a.sort_score));
         edges
     }
 }
@@ -710,7 +710,7 @@ impl BacklinksWithLabelsQuery {
 impl Query for BacklinksWithLabelsQuery {
     type Collector = TopDocsCollector;
     type TantivyQuery = Box<dyn tantivy::query::Query>;
-    type IntermediateOutput = Vec<(f64, SmallEdgeWithLabel)>;
+    type IntermediateOutput = Vec<(u64, SmallEdgeWithLabel)>;
     type Output = Vec<SmallEdgeWithLabel>;
 
     fn tantivy_query(&self, searcher: &Searcher) -> Self::TantivyQuery {
@@ -782,7 +782,7 @@ impl Query for BacklinksWithLabelsQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|(a, _), (b, _)| b.total_cmp(a));
+        edges.sort_by(|(a, _), (b, _)| a.cmp(b));
         edges.into_iter().map(|(_, e)| e).collect()
     }
 }

+ 1 - 1
crates/core/src/webgraph/query/between.rs

@@ -140,7 +140,7 @@ impl Query for FullLinksBetweenQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|a, b| b.sort_score.total_cmp(&a.sort_score));
+        edges.sort_by(|a, b| a.sort_score.cmp(&b.sort_score));
         edges
     }
 }

+ 22 - 31
crates/core/src/webgraph/query/collector/top_docs.rs

@@ -53,10 +53,7 @@ where
 pub trait Deduplicator: Clone + Send + Sync {
     type Doc: DeduplicatorDoc;
 
-    fn deduplicate(
-        &self,
-        docs: Vec<(tantivy::Score, Self::Doc)>,
-    ) -> Vec<(tantivy::Score, Self::Doc)>;
+    fn deduplicate(&self, docs: Vec<(u64, Self::Doc)>) -> Vec<(u64, Self::Doc)>;
 }
 
 impl DeduplicatorDoc for DocAddress {
@@ -75,10 +72,7 @@ pub struct NoDeduplicator;
 impl Deduplicator for NoDeduplicator {
     type Doc = DocAddress;
 
-    fn deduplicate(
-        &self,
-        docs: Vec<(tantivy::Score, Self::Doc)>,
-    ) -> Vec<(tantivy::Score, Self::Doc)> {
+    fn deduplicate(&self, docs: Vec<(u64, Self::Doc)>) -> Vec<(u64, Self::Doc)> {
         docs
     }
 }
@@ -124,10 +118,7 @@ pub struct HostDeduplicator;
 impl Deduplicator for HostDeduplicator {
     type Doc = DocAddressWithHost;
 
-    fn deduplicate(
-        &self,
-        docs: Vec<(tantivy::Score, Self::Doc)>,
-    ) -> Vec<(tantivy::Score, Self::Doc)> {
+    fn deduplicate(&self, docs: Vec<(u64, Self::Doc)>) -> Vec<(u64, Self::Doc)> {
         docs.into_iter().unique_by(|(_, doc)| doc.host).collect()
     }
 }
@@ -299,7 +290,7 @@ where
 }
 
 impl<S: DocumentScorer + 'static, D: Deduplicator + 'static> Collector for TopDocsCollector<S, D> {
-    type Fruit = Vec<(tantivy::Score, <D as Deduplicator>::Doc)>;
+    type Fruit = Vec<(u64, <D as Deduplicator>::Doc)>;
 
     type Child = TopDocsSegmentCollector<S, D>;
 
@@ -340,8 +331,8 @@ impl<S: DocumentScorer + 'static, D: Deduplicator + 'static> Collector for TopDo
         let before_deduplication: Vec<_> = segment_fruits.into_iter().flatten().collect();
         let deduplicated = self.deduplicator.deduplicate(before_deduplication);
 
-        for (score, doc) in deduplicated {
-            computer.push(score, doc);
+        for (rank, doc) in deduplicated {
+            computer.push(rank, doc);
         }
 
         let result = computer.harvest();
@@ -367,19 +358,19 @@ impl<S: DocumentScorer + 'static, D: Deduplicator + 'static> Collector for TopDo
 }
 
 enum Computer<D: Deduplicator> {
-    TopN(TopNComputer<tantivy::Score, <D as Deduplicator>::Doc>),
+    TopN(TopNComputer<u64, <D as Deduplicator>::Doc, false>),
     All(AllComputer<D>),
 }
 
 impl<D: Deduplicator> Computer<D> {
-    fn push(&mut self, score: tantivy::Score, doc: <D as Deduplicator>::Doc) {
+    fn push(&mut self, rank: u64, doc: <D as Deduplicator>::Doc) {
         match self {
-            Computer::TopN(computer) => computer.push(score, doc),
-            Computer::All(computer) => computer.push(score, doc),
+            Computer::TopN(computer) => computer.push(rank, doc),
+            Computer::All(computer) => computer.push(rank, doc),
         }
     }
 
-    fn harvest(self) -> Vec<(tantivy::Score, <D as Deduplicator>::Doc)> {
+    fn harvest(self) -> Vec<(u64, <D as Deduplicator>::Doc)> {
         match self {
             Computer::TopN(computer) => computer
                 .into_sorted_vec()
@@ -392,7 +383,7 @@ impl<D: Deduplicator> Computer<D> {
 }
 
 struct AllComputer<D: Deduplicator> {
-    docs: Vec<(tantivy::Score, <D as Deduplicator>::Doc)>,
+    docs: Vec<(u64, <D as Deduplicator>::Doc)>,
 }
 
 impl<D: Deduplicator> AllComputer<D> {
@@ -400,13 +391,13 @@ impl<D: Deduplicator> AllComputer<D> {
         Self { docs: Vec::new() }
     }
 
-    fn push(&mut self, score: tantivy::Score, doc: <D as Deduplicator>::Doc) {
-        self.docs.push((score, doc));
+    fn push(&mut self, rank: u64, doc: <D as Deduplicator>::Doc) {
+        self.docs.push((rank, doc));
     }
 
-    fn harvest(self) -> Vec<(tantivy::Score, <D as Deduplicator>::Doc)> {
+    fn harvest(self) -> Vec<(u64, <D as Deduplicator>::Doc)> {
         let mut docs = self.docs;
-        docs.sort_by(|(score1, _), (score2, _)| score2.total_cmp(score1));
+        docs.sort_by(|(rank1, _), (rank2, _)| rank1.cmp(rank2));
         docs
     }
 }
@@ -424,7 +415,7 @@ pub struct TopDocsSegmentCollector<S: DocumentScorer, D: Deduplicator> {
 impl<S: DocumentScorer + 'static, D: Deduplicator + 'static> SegmentCollector
     for TopDocsSegmentCollector<S, D>
 {
-    type Fruit = Vec<(tantivy::Score, <D as Deduplicator>::Doc)>;
+    type Fruit = Vec<(u64, <D as Deduplicator>::Doc)>;
 
     fn collect(&mut self, doc: DocId, _: tantivy::Score) {
         if doc == tantivy::TERMINATED {
@@ -437,9 +428,9 @@ impl<S: DocumentScorer + 'static, D: Deduplicator + 'static> SegmentCollector
             }
         }
 
-        let score = self.scorer.score(doc);
+        let rank = self.scorer.rank(doc);
         self.computer
-            .push(score, <D::Doc as DeduplicatorDoc>::new(self, doc));
+            .push(rank, <D::Doc as DeduplicatorDoc>::new(self, doc));
     }
 
     fn harvest(self) -> Self::Fruit {
@@ -549,7 +540,7 @@ mod tests {
             .insert(Edge {
                 from: Node::from("https://A.com/1"),
                 to: Node::from("https://B.com/1"),
-                sort_score: 0.1,
+                sort_score: 1,
                 ..Edge::empty()
             })
             .unwrap();
@@ -557,7 +548,7 @@ mod tests {
             .insert(Edge {
                 from: Node::from("https://A.com/2"),
                 to: Node::from("https://B.com/1"),
-                sort_score: 0.1,
+                sort_score: 1,
                 ..Edge::empty()
             })
             .unwrap();
@@ -565,7 +556,7 @@ mod tests {
             .insert(Edge {
                 from: Node::from("https://C.com/1"),
                 to: Node::from("https://B.com/1"),
-                sort_score: 0.0,
+                sort_score: 3,
                 ..Edge::empty()
             })
             .unwrap();

+ 7 - 7
crates/core/src/webgraph/query/document_scorer/mod.rs

@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-use tantivy::{columnar::Column, DocId, Score, SegmentReader};
+use tantivy::{columnar::Column, DocId, SegmentReader};
 
 use crate::webgraph::{
     schema::{Field, SortScore},
@@ -26,11 +26,11 @@ pub trait DocumentScorer: Send + Sync + Sized {
         segment: &SegmentReader,
         column_fields: &WarmedColumnFields,
     ) -> tantivy::Result<Self>;
-    fn score(&self, doc: DocId) -> Score;
+    fn rank(&self, doc: DocId) -> u64;
 }
 
 pub struct DefaultDocumentScorer {
-    column: Column<f64>,
+    column: Column<u64>,
 }
 
 impl DocumentScorer for DefaultDocumentScorer {
@@ -40,7 +40,7 @@ impl DocumentScorer for DefaultDocumentScorer {
     ) -> tantivy::Result<Self> {
         let column = column_fields
             .segment(&segment.segment_id())
-            .f64(SortScore)
+            .u64(SortScore)
             .ok_or(tantivy::TantivyError::FieldNotFound(format!(
                 "{} column not found",
                 SortScore.name()
@@ -48,11 +48,11 @@ impl DocumentScorer for DefaultDocumentScorer {
         Ok(Self { column })
     }
 
-    fn score(&self, doc: DocId) -> Score {
+    fn rank(&self, doc: DocId) -> u64 {
         if doc == tantivy::TERMINATED {
-            return 0.0;
+            return u64::MAX;
         }
 
-        self.column.first(doc).unwrap_or(0.0) as Score
+        self.column.first(doc).unwrap_or(u64::MAX)
     }
 }

+ 7 - 7
crates/core/src/webgraph/query/forwardlink.rs

@@ -23,7 +23,7 @@ use super::{
 };
 use crate::{
     webgraph::{
-        document::Edge,
+        edge::Edge,
         schema::{FromHostId, FromId, ToHostId, ToId},
         EdgeLimit, Node, NodeID, Searcher, SmallEdge,
     },
@@ -89,7 +89,7 @@ impl ForwardlinksQuery {
 impl Query for ForwardlinksQuery {
     type Collector = TopDocsCollector;
     type TantivyQuery = Box<dyn tantivy::query::Query>;
-    type IntermediateOutput = Vec<(f64, SmallEdge)>;
+    type IntermediateOutput = Vec<(u64, SmallEdge)>;
     type Output = Vec<SmallEdge>;
 
     fn tantivy_query(&self, searcher: &Searcher) -> Self::TantivyQuery {
@@ -174,7 +174,7 @@ impl Query for ForwardlinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|(a, _), (b, _)| b.total_cmp(a));
+        edges.sort_by(|(a, _), (b, _)| a.cmp(b));
         edges.into_iter().map(|(_, e)| e).collect()
     }
 }
@@ -234,7 +234,7 @@ impl HostForwardlinksQuery {
 impl Query for HostForwardlinksQuery {
     type Collector = TopDocsCollector<DefaultDocumentScorer, HostDeduplicator>;
     type TantivyQuery = Box<dyn tantivy::query::Query>;
-    type IntermediateOutput = Vec<(f64, SmallEdge)>;
+    type IntermediateOutput = Vec<(u64, SmallEdge)>;
     type Output = Vec<SmallEdge>;
 
     fn tantivy_query(&self, searcher: &Searcher) -> Self::TantivyQuery {
@@ -327,7 +327,7 @@ impl Query for HostForwardlinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|(a, _), (b, _)| b.total_cmp(a));
+        edges.sort_by(|(a, _), (b, _)| a.cmp(b));
         edges.into_iter().map(|(_, e)| e).collect()
     }
 }
@@ -464,7 +464,7 @@ impl Query for FullForwardlinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|a, b| b.sort_score.total_cmp(&a.sort_score));
+        edges.sort_by(|a, b| a.sort_score.cmp(&b.sort_score));
         edges
     }
 }
@@ -612,7 +612,7 @@ impl Query for FullHostForwardlinksQuery {
 
     fn merge_results(results: Vec<Self::IntermediateOutput>) -> Self::Output {
         let mut edges: Vec<_> = results.into_iter().flatten().collect();
-        edges.sort_by(|a, b| b.sort_score.total_cmp(&a.sort_score));
+        edges.sort_by(|a, b| a.sort_score.cmp(&b.sort_score));
         edges
     }
 }

+ 4 - 4
crates/core/src/webgraph/schema.rs

@@ -26,7 +26,7 @@ use tantivy::schema::Value;
 use crate::enum_dispatch_from_discriminant;
 use crate::Result;
 
-use super::document::{Edge, ReferenceValue};
+use super::edge::{Edge, ReferenceValue};
 use super::tokenizer;
 use super::tokenizer::Tokenizer;
 use super::tokenizer::TokenizerEnum;
@@ -298,13 +298,13 @@ impl Field for SortScore {
     }
 
     fn document_value<'a>(&self, edge: &'a Edge) -> ReferenceValue<'a> {
-        ReferenceValue::F64(edge.sort_score)
+        ReferenceValue::U64(edge.sort_score)
     }
 
     fn set_value(&self, edge: &mut Edge, value: OwnedValue) -> Result<()> {
         let sort_score = value
             .as_ref()
-            .as_f64()
+            .as_u64()
             .ok_or(anyhow::anyhow!("Invalid sort score"))?;
         edge.sort_score = sort_score;
 
@@ -312,7 +312,7 @@ impl Field for SortScore {
     }
 
     fn field_type(&self) -> FieldType {
-        FieldType::F64(
+        FieldType::U64(
             NumericOptions::default()
                 .set_indexed()
                 .set_stored()

+ 22 - 16
crates/core/src/webgraph/store.rs

@@ -22,7 +22,7 @@ use std::{
 };
 
 use super::{
-    document::SmallEdge,
+    edge::SmallEdge,
     query::collector::{Collector, TantivyCollector},
     schema::{self, create_schema, Field, FromHostId, FromId, ToHostId, ToId},
     searcher::Searcher,
@@ -67,7 +67,7 @@ impl EdgeStore {
             .settings(tantivy::IndexSettings {
                 sort_by_field: Some(tantivy::IndexSortByField {
                     field: schema::SortScore.name().to_string(),
-                    order: tantivy::Order::Desc,
+                    order: tantivy::Order::Asc,
                 }),
                 ..Default::default()
             })
@@ -465,6 +465,12 @@ mod tests {
         let b_centrality = 2.0;
         let c_centrality = 4.0;
         let d_centrality = 3.0;
+
+        let a_rank = 1;
+        let b_rank = 2;
+        let c_rank = 3;
+        let d_rank = 4;
+
         let mut store =
             EdgeStore::open(&temp_dir.as_ref().join("test-segment"), ShardId::new(0)).unwrap();
 
@@ -473,11 +479,11 @@ mod tests {
             to: a.clone(),
             label: "test".to_string(),
             rel_flags: RelFlags::default(),
-            sort_score: a_centrality + b_centrality,
+            sort_score: a_rank + b_rank,
             from_centrality: b_centrality,
             to_centrality: a_centrality,
-            from_rank: 2,
-            to_rank: 1,
+            from_rank: b_rank,
+            to_rank: a_rank,
             ..Edge::empty()
         };
 
@@ -486,11 +492,11 @@ mod tests {
             to: a.clone(),
             label: "2".to_string(),
             rel_flags: RelFlags::default(),
-            sort_score: a_centrality + c_centrality,
+            sort_score: a_rank + c_rank,
             from_centrality: c_centrality,
             to_centrality: a_centrality,
-            from_rank: 3,
-            to_rank: 1,
+            from_rank: c_rank,
+            to_rank: a_rank,
             ..Edge::empty()
         };
 
@@ -499,17 +505,17 @@ mod tests {
             to: a.clone(),
             label: "3".to_string(),
             rel_flags: RelFlags::default(),
-            sort_score: a_centrality + d_centrality,
+            sort_score: a_rank + d_rank,
             from_centrality: d_centrality,
             to_centrality: a_centrality,
-            from_rank: 4,
-            to_rank: 1,
+            from_rank: d_rank,
+            to_rank: a_rank,
             ..Edge::empty()
         };
 
-        store.insert(e1.clone()).unwrap();
-        store.insert(e2.clone()).unwrap();
         store.insert(e3.clone()).unwrap();
+        store.insert(e2.clone()).unwrap();
+        store.insert(e1.clone()).unwrap();
 
         store.commit().unwrap();
 
@@ -518,9 +524,9 @@ mod tests {
 
         assert_eq!(edges.len(), 3);
 
-        assert_eq!(edges[0].from, e2.from.id());
-        assert_eq!(edges[1].from, e3.from.id());
-        assert_eq!(edges[2].from, e1.from.id());
+        assert_eq!(edges[0].from, e1.from.id());
+        assert_eq!(edges[1].from, e2.from.id());
+        assert_eq!(edges[2].from, e3.from.id());
     }
 
     #[test]