Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Create simple_paths_generator_with_score.rs #1284

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
368 changes: 368 additions & 0 deletions rustworkx-core/src/generators/simple_shortest_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

use crate::petgraph::algo::{Measure};
use crate::petgraph::graph::{DiGraph, NodeIndex};
use crate::petgraph::visit::{EdgeRef, IntoEdges, VisitMap, Visitable};

use std::cmp::Ordering;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{BinaryHeap, HashMap};
use std::fmt::Debug;
use std::hash::Hash;
use std::{f32, thread};
// use rand::Rng;
///////////
// SimplePath
// This is Structure which saves all the context about graph & iterating attributes
// Score : the total weightage of the current shortest path
// Path: Shorted path caulculated in this iteration
// index: used for iterating over edged to delete one and calculate path once again
// Source: to store the start point
// Target: to store goal of path
// graph: store the path to be used
// unique_path: stores all unique_paths to verify that we are not returning same path again after random edge removal
// switch : used for switching to next shortest path in case for one all possible paths are generated.
/////////////
ranjana-mishra marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug, Clone)]
pub struct SimplePath {
ranjana-mishra marked this conversation as resolved.
Show resolved Hide resolved
pub Score: f32,
pub Path: Vec<NodeIndex>,
index: usize,
source: NodeIndex,
target: NodeIndex,
graph: DiGraph<(), f32>,
ranjana-mishra marked this conversation as resolved.
Show resolved Hide resolved
unique_paths: Vec<Vec<NodeIndex>>,
switch: usize,
}

impl SimplePath {
fn new(
graph: &mut DiGraph<(), f32>,
source: NodeIndex,
target: NodeIndex,
) -> Option<SimplePath> {
let s = SimplePath {
switch: 0,
unique_paths: vec![],
Score: 0.0,
Path: vec!(),
index: 0,
source: source,
target: target,
graph: graph.clone(),
};
return Some(s);

}
}

impl Iterator for SimplePath {
type Item = SimplePath;
fn next(&mut self) -> Option<Self::Item> {

if self.unique_paths.len() == 0 {
let mut sim_path = get_simple_path(self);
match sim_path {
None => {
return None;
}
Some(mut s_path) => {
s_path.index = 0;
return Some(s_path)
}
}

}

let mut simple_graph = &self.unique_paths[self.switch];
let mut index: usize = self.index;

if index + 1 == simple_graph.len() {
if self.switch < self.unique_paths.len() - 1 {
self.switch = self.switch + 1;
simple_graph = &self.unique_paths[self.switch];
self.index = 0;
index = 0;
} else {
return None;
}
}

let edge = self.graph.find_edge(simple_graph[index], simple_graph[index + 1]);
match edge {
Some(edge) => {
let (s, t) = (simple_graph[index], simple_graph[index + 1]);
let Some(weight) = self.graph.edge_weight(edge) else {
return None;
};
let weight = *weight;

println!("Removing edge {:?} {:?}",s,t);
{
let graph = &mut self.graph;
graph.remove_edge(edge);
}

let sim_path = get_simple_path(self);




match sim_path {
None => {
self.index = self.index + 1;
let graph = &mut self.graph;
graph.add_edge(s, t, weight);
return self.next();
}
Some(mut s_path) => {
{
let graph = &mut s_path.graph;
graph.add_edge(s, t, weight);
}
return Some(s_path);
}
}
}
None => {
self.index = self.index + 1;
return self.next();
}
}
}
}
// The code provides the shortest distance cost of all Nodes from the start Node
#[derive(Copy, Clone, Debug)]
struct MinScored<K, T>(pub K, pub T);

impl<K: PartialOrd, T> PartialEq for MinScored<K, T> {
#[inline]
fn eq(&self, other: &MinScored<K, T>) -> bool {
self.cmp(other) == Ordering::Equal
}
}

impl<K: PartialOrd, T> Eq for MinScored<K, T> {}

impl<K: PartialOrd, T> PartialOrd for MinScored<K, T> {
#[inline]
fn partial_cmp(&self, other: &MinScored<K, T>) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<K: PartialOrd, T> Ord for MinScored<K, T> {
#[inline]
fn cmp(&self, other: &MinScored<K, T>) -> Ordering {
let a = &self.0;
let b = &other.0;
if a == b {
Ordering::Equal
} else if a < b {
Ordering::Greater
} else if a > b {
Ordering::Less
} else if a.ne(a) && b.ne(b) {
// these are the NaN cases
Ordering::Equal
} else if a.ne(a) {
// Order NaN less, so that it is last in the MinScore order
Ordering::Less
} else {
Ordering::Greater
}
}
}
ranjana-mishra marked this conversation as resolved.
Show resolved Hide resolved

// This is mutation of petgraph dijkastra to get full path between source to target
fn dijkstra<G, F, K>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not review this specific Dijkastra, but petgraph dijkastra returns only score of each node between source & target. but how to find the path from source -> target is not clear from that score. thats why i have updated the code to return the previous node as, that it followed to reach to the destination.
like - if the path for lowest score was A->B-->C->D, it will return the score, also Node (C), which is the previous node, similarly for C , it will return score & B as previous node, and for B it will return A as previous node, which we can use to reach till A from D.

Let me know if I am missing something on existing Dijkastra functioning.

graph: G,
start: G::NodeId,
goal: Option<G::NodeId>,
mut edge_cost: F,
) -> (HashMap<G::NodeId, K>, HashMap<G::NodeId,G::NodeId>)
where
G: IntoEdges + Visitable,
G::NodeId: Eq + Hash,
F: FnMut(G::EdgeRef) -> K,
K: Measure + Copy,
<G>::NodeId: Debug,
{
let mut visited = graph.visit_map();
let mut scores = HashMap::new();
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();
scores.insert(start, zero_score);
let mut tracing: HashMap<G::NodeId, G::NodeId> = HashMap::new();
visit_next.push(MinScored(zero_score, start));
while let Some(MinScored(node_score, node)) = visit_next.pop() {
if visited.is_visited(&node) {
continue;
}
if goal.as_ref() == Some(&node) {
break;
}
for edge in graph.edges(node) {
let next = edge.target();
if visited.is_visited(&next) {
continue;
}
let next_score = node_score + edge_cost(edge);
match scores.entry(next) {
Occupied(ent) => {
if next_score < *ent.get() {
*ent.into_mut() = next_score;
visit_next.push(MinScored(next_score, next));
if let Some(v) = tracing.get_mut(&next) {
*v = node;
} else {
tracing.insert(next, node);
todo!()
};

}
}
Vacant(ent) => {
ent.insert(next_score);
visit_next.push(MinScored(next_score, next));
tracing.insert(next, node);
}
}
}
visited.visit(node);
}
(scores, tracing)
}

// This function is private to this module, will call Dijkstra algo to get the possible path & Scores & returns a SimplePath as return value

fn get_simple_path(s: &mut SimplePath) -> Option<SimplePath> {
let (score, path) = dijkstra(&s.graph, s.source, Some(s.target), |e| *e.weight());
let mut score_target: f32 = 0.0;
let mut unique_paths = s.unique_paths.clone();
let mut paths :Vec<NodeIndex> = vec!();

if score.contains_key(&s.target) {
score_target = *score.get(&s.target).expect("Error");
}

if path.contains_key(&s.target) {
paths.push(s.target);
let mut node = &s.target;
loop {
let pre_node = path.get(node).expect("Error");
paths.push(*pre_node);
if *pre_node == s.source {
break;
}
node = pre_node;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general it'd be better to rework this as a while loop so the exit conditions are a bit more clear.

Copy link
Author

@ranjana-mishra ranjana-mishra Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general it'd be better to rework this as a while loop so the exit conditions are a bit more clear.

it actually back tracks the path till source, if the next node is source, then it exits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the current status, even though its same with comment one will get to know the exit state.

}

if paths.len() == 0 {
return None
}
paths.reverse();
let contains_target = unique_paths.iter().any(|v| *v == paths.to_vec());
if !contains_target {
unique_paths.push(paths.to_vec());
let s = SimplePath {
switch: s.switch,
unique_paths: unique_paths,
Score: score_target,
Path: paths.to_vec(),
index: s.index + 1,
source: s.source,
target: s.target,
graph: s.graph.clone(),
};
return Some(s);
}
None
}


// This function call get_simple_path for each graph after removing one of the edges in between.

// -------------------------------------------
// INPUTS
// -------------------------------------------
// you can call the function with Input Graph, Source Node, Target Node
// Create a SimplePath instance as -
// let path = SimplePath::new(&mut graph,source,target);
// Then iterate over it, as path.next() .
// The Return type is a Option, so you have to handle the None Part as the End of Iterator
////////////////////////////////////////////////

//////////////////////////////////////////////
// Testing Main function

//fn main() {
// let mut graph = DiGraph::new();
// let nodes: Vec<NodeIndex> = (0..50000).map(|_| graph.add_node(())).collect();
// let mut rng = rand::thread_rng();
// for _ in 0..100000 { // Adjust the number of edges as desired
// let a = rng.gen_range(0..nodes.len());
// let b = rng.gen_range(0..nodes.len());
// let weight = rng.gen_range(1..100); // Random weight between 1 and 100
// if a != b { // Prevent self-loops
// graph.add_edge(nodes[a], nodes[b], weight as f32);
// }
// }
// let source = nodes[10];
// let target = nodes[800];
// let mut result = SimplePath::new(&mut graph,source,target);
//
// while result.is_some() {
// let mut result_new = result.expect("REASON").next();
// if result_new.is_none() {
// break;
// }
// println!("New Path & Score {:#?}, {:#?}",result_new.clone().unwrap().Score, result_new.clone().unwrap().Path);
// result = result_new;
// }
// }

// ----------------------------------------------
// OUTPUT
// ----------------------------------------------
// The function simple_paths_generator will return the Vector of Type SimplePath, which is a structure which contains { Score, Path }
//
// Example :
//New Path & Score 614.0, [
// NodeIndex(10),
// NodeIndex(2636),
// NodeIndex(8612),
// NodeIndex(7513),
// NodeIndex(800),
//]
//New Path & Score 675.0, [
// NodeIndex(10),
// NodeIndex(2636),
// NodeIndex(8612),
// NodeIndex(7513),
// NodeIndex(5367),
// NodeIndex(6520),
// NodeIndex(5590),
// NodeIndex(5745),
// NodeIndex(2596),
// NodeIndex(4981),
// NodeIndex(2837),
// NodeIndex(6319),
// NodeIndex(4025),
// NodeIndex(5631),
// NodeIndex(6935),
// NodeIndex(2784),
// NodeIndex(800),
//]
ranjana-mishra marked this conversation as resolved.
Show resolved Hide resolved