@@ -619,15 +619,142 @@ where
619619 Ok ( graph)
620620}
621621
622+ /// Generate a hyperbolic random undirected graph (also called hyperbolic geometric graph).
623+ ///
624+ /// The hyperbolic random graph model connects pairs of nodes with a probability
625+ /// that decreases as their hyperbolic distance increases.
626+ ///
627+ /// The number of nodes and the dimension are inferred from the coordinates `pos` of the
628+ /// hyperboloid model (at least 3-dimensional). If `beta` is `None`, all pairs of nodes
629+ /// with a distance smaller than ``r`` are connected.
630+ ///
631+ /// Arguments:
632+ ///
633+ /// * `pos` - Hyperboloid model coordinates of the nodes `[p_1, p_2, ...]` where `p_i` is the
634+ /// position of node i. The first dimension corresponds to the negative term in the metric
635+ /// and so for each node i, `p_i[0]` must be at least 1.
636+ /// * `beta` - Sigmoid sharpness (nonnegative) of the connection probability.
637+ /// * `r` - Distance at which the connection probability is 0.5 for the probabilistic model.
638+ /// Threshold when `beta` is `None`.
639+ /// * `seed` - An optional seed to use for the random number generator.
640+ /// * `default_node_weight` - A callable that will return the weight to use
641+ /// for newly created nodes.
642+ /// * `default_edge_weight` - A callable that will return the weight object
643+ /// to use for newly created edges.
644+ ///
645+ /// # Example
646+ /// ```rust
647+ /// use rustworkx_core::petgraph;
648+ /// use rustworkx_core::generators::hyperbolic_random_graph;
649+ ///
650+ /// let g: petgraph::graph::UnGraph<(), ()> = hyperbolic_random_graph(
651+ /// &[vec![1_f64.cosh(), 3_f64.sinh(), 0.],
652+ /// vec![0.5_f64.cosh(), -0.5_f64.sinh(), 0.],
653+ /// vec![1_f64.cosh(), -1_f64.sinh(), 0.]],
654+ /// None,
655+ /// 2.,
656+ /// None,
657+ /// || {()},
658+ /// || {()},
659+ /// ).unwrap();
660+ /// assert_eq!(g.node_count(), 3);
661+ /// assert_eq!(g.edge_count(), 1);
662+ /// ```
663+ pub fn hyperbolic_random_graph < G , T , F , H , M > (
664+ pos : & [ Vec < f64 > ] ,
665+ beta : Option < f64 > ,
666+ r : f64 ,
667+ seed : Option < u64 > ,
668+ mut default_node_weight : F ,
669+ mut default_edge_weight : H ,
670+ ) -> Result < G , InvalidInputError >
671+ where
672+ G : Build + Create + Data < NodeWeight = T , EdgeWeight = M > + NodeIndexable + GraphProp ,
673+ F : FnMut ( ) -> T ,
674+ H : FnMut ( ) -> M ,
675+ G :: NodeId : Eq + Hash ,
676+ {
677+ let num_nodes = pos. len ( ) ;
678+ if num_nodes == 0 {
679+ return Err ( InvalidInputError { } ) ;
680+ }
681+ if pos. iter ( ) . any ( |xs| xs. iter ( ) . any ( |x| x. is_nan ( ) ) ) {
682+ return Err ( InvalidInputError { } ) ;
683+ }
684+ let dim = pos[ 0 ] . len ( ) ;
685+ if dim < 3 || pos. iter ( ) . any ( |x| x. len ( ) != dim || x[ 0 ] < 1. ) {
686+ return Err ( InvalidInputError { } ) ;
687+ }
688+ if beta. is_some_and ( |b| b < 0. || b. is_nan ( ) ) {
689+ return Err ( InvalidInputError { } ) ;
690+ }
691+ if r < 0. || r. is_nan ( ) {
692+ return Err ( InvalidInputError { } ) ;
693+ }
694+
695+ let mut rng: Pcg64 = match seed {
696+ Some ( seed) => Pcg64 :: seed_from_u64 ( seed) ,
697+ None => Pcg64 :: from_entropy ( ) ,
698+ } ;
699+ let mut graph = G :: with_capacity ( num_nodes, num_nodes) ;
700+ if graph. is_directed ( ) {
701+ return Err ( InvalidInputError { } ) ;
702+ }
703+
704+ for _ in 0 ..num_nodes {
705+ graph. add_node ( default_node_weight ( ) ) ;
706+ }
707+
708+ let between = Uniform :: new ( 0.0 , 1.0 ) ;
709+ for ( v, p1) in pos. iter ( ) . enumerate ( ) . take ( num_nodes - 1 ) {
710+ for ( w, p2) in pos. iter ( ) . enumerate ( ) . skip ( v + 1 ) {
711+ let dist = hyperbolic_distance ( p1, p2) ;
712+ let is_edge = match beta {
713+ Some ( b) => {
714+ let prob_inverse = ( b / 2. * ( dist - r) ) . exp ( ) + 1. ;
715+ let u: f64 = between. sample ( & mut rng) ;
716+ prob_inverse * u < 1.
717+ }
718+ None => dist < r,
719+ } ;
720+ if is_edge {
721+ graph. add_edge (
722+ graph. from_index ( v) ,
723+ graph. from_index ( w) ,
724+ default_edge_weight ( ) ,
725+ ) ;
726+ }
727+ }
728+ }
729+ Ok ( graph)
730+ }
731+
732+ #[ inline]
733+ fn hyperbolic_distance ( p1 : & [ f64 ] , p2 : & [ f64 ] ) -> f64 {
734+ if p1. iter ( ) . chain ( p2. iter ( ) ) . any ( |x| x. is_infinite ( ) ) {
735+ f64:: INFINITY
736+ } else {
737+ ( p1[ 0 ] * p2[ 0 ]
738+ - p1. iter ( )
739+ . skip ( 1 )
740+ . zip ( p2. iter ( ) . skip ( 1 ) )
741+ . map ( |( & x, & y) | x * y)
742+ . sum :: < f64 > ( ) )
743+ . acosh ( )
744+ }
745+ }
746+
622747#[ cfg( test) ]
623748mod tests {
624749 use crate :: generators:: InvalidInputError ;
625750 use crate :: generators:: {
626- barabasi_albert_graph, gnm_random_graph, gnp_random_graph, path_graph ,
627- random_bipartite_graph, random_geometric_graph,
751+ barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph ,
752+ path_graph , random_bipartite_graph, random_geometric_graph,
628753 } ;
629754 use crate :: petgraph;
630755
756+ use super :: hyperbolic_distance;
757+
631758 // Test gnp_random_graph
632759
633760 #[ test]
@@ -916,4 +1043,174 @@ mod tests {
9161043 Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
9171044 } ;
9181045 }
1046+
1047+ // Test hyperbolic_random_graph
1048+ //
1049+ // Hyperboloid (H^2) "polar" coordinates (r, theta) are transformed to "cartesian"
1050+ // coordinates using
1051+ // z = cosh(r)
1052+ // x = sinh(r)cos(theta)
1053+ // y = sinh(r)sin(theta)
1054+
1055+ #[ test]
1056+ fn test_hyperbolic_dist ( ) {
1057+ assert_eq ! (
1058+ hyperbolic_distance(
1059+ & [ 3_f64 . cosh( ) , 3_f64 . sinh( ) , 0. ] ,
1060+ & [ 0.5_f64 . cosh( ) , -0.5_f64 . sinh( ) , 0. ]
1061+ ) ,
1062+ 3.5
1063+ ) ;
1064+ }
1065+ #[ test]
1066+ fn test_hyperbolic_dist_inf ( ) {
1067+ assert_eq ! (
1068+ hyperbolic_distance( & [ f64 :: INFINITY , f64 :: INFINITY , 0. ] , & [ 1. , 0. , 0. ] ) ,
1069+ f64 :: INFINITY
1070+ ) ;
1071+ }
1072+
1073+ #[ test]
1074+ fn test_hyperbolic_random_graph_seeded ( ) {
1075+ let g = hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1076+ & [
1077+ vec ! [ 3_f64 . cosh( ) , 3_f64 . sinh( ) , 0. ] ,
1078+ vec ! [ 0.5_f64 . cosh( ) , -0.5_f64 . sinh( ) , 0. ] ,
1079+ vec ! [ 0.5_f64 . cosh( ) , 0.5_f64 . sinh( ) , 0. ] ,
1080+ vec ! [ 1. , 0. , 0. ] ,
1081+ ] ,
1082+ Some ( 10000. ) ,
1083+ 0.75 ,
1084+ Some ( 10 ) ,
1085+ || ( ) ,
1086+ || ( ) ,
1087+ )
1088+ . unwrap ( ) ;
1089+ assert_eq ! ( g. node_count( ) , 4 ) ;
1090+ assert_eq ! ( g. edge_count( ) , 2 ) ;
1091+ }
1092+
1093+ #[ test]
1094+ fn test_hyperbolic_random_graph_threshold ( ) {
1095+ let g = hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1096+ & [
1097+ vec ! [ 1_f64 . cosh( ) , 3_f64 . sinh( ) , 0. ] ,
1098+ vec ! [ 0.5_f64 . cosh( ) , -0.5_f64 . sinh( ) , 0. ] ,
1099+ vec ! [ 1_f64 . cosh( ) , -1_f64 . sinh( ) , 0. ] ,
1100+ ] ,
1101+ None ,
1102+ 1. ,
1103+ None ,
1104+ || ( ) ,
1105+ || ( ) ,
1106+ )
1107+ . unwrap ( ) ;
1108+ assert_eq ! ( g. node_count( ) , 3 ) ;
1109+ assert_eq ! ( g. edge_count( ) , 1 ) ;
1110+ }
1111+
1112+ #[ test]
1113+ fn test_hyperbolic_random_graph_invalid_dim_error ( ) {
1114+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1115+ & [ vec ! [ 1. , 0. ] ] ,
1116+ None ,
1117+ 1. ,
1118+ None ,
1119+ || ( ) ,
1120+ || ( ) ,
1121+ ) {
1122+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1123+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1124+ }
1125+ }
1126+
1127+ #[ test]
1128+ fn test_hyperbolic_random_graph_invalid_first_coord_error ( ) {
1129+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1130+ & [ vec ! [ 0. , 0. , 0. ] ] ,
1131+ None ,
1132+ 1. ,
1133+ None ,
1134+ || ( ) ,
1135+ || ( ) ,
1136+ ) {
1137+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1138+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1139+ }
1140+ }
1141+
1142+ #[ test]
1143+ fn test_hyperbolic_random_graph_neg_r_error ( ) {
1144+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1145+ & [ vec ! [ 1. , 0. , 0. ] , vec ! [ 1. , 0. , 0. ] ] ,
1146+ None ,
1147+ -1. ,
1148+ None ,
1149+ || ( ) ,
1150+ || ( ) ,
1151+ ) {
1152+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1153+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1154+ }
1155+ }
1156+
1157+ #[ test]
1158+ fn test_hyperbolic_random_graph_neg_beta_error ( ) {
1159+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1160+ & [ vec ! [ 1. , 0. , 0. ] , vec ! [ 1. , 0. , 0. ] ] ,
1161+ Some ( -1. ) ,
1162+ 1. ,
1163+ None ,
1164+ || ( ) ,
1165+ || ( ) ,
1166+ ) {
1167+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1168+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1169+ }
1170+ }
1171+
1172+ #[ test]
1173+ fn test_hyperbolic_random_graph_diff_dims_error ( ) {
1174+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1175+ & [ vec ! [ 1. , 0. , 0. ] , vec ! [ 1. , 0. , 0. , 0. ] ] ,
1176+ None ,
1177+ 1. ,
1178+ None ,
1179+ || ( ) ,
1180+ || ( ) ,
1181+ ) {
1182+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1183+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1184+ }
1185+ }
1186+
1187+ #[ test]
1188+ fn test_hyperbolic_random_graph_empty_error ( ) {
1189+ match hyperbolic_random_graph :: < petgraph:: graph:: UnGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1190+ & [ ] ,
1191+ None ,
1192+ 1. ,
1193+ None ,
1194+ || ( ) ,
1195+ || ( ) ,
1196+ ) {
1197+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1198+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1199+ }
1200+ }
1201+
1202+ #[ test]
1203+ fn test_hyperbolic_random_graph_directed_error ( ) {
1204+ match hyperbolic_random_graph :: < petgraph:: graph:: DiGraph < ( ) , ( ) > , _ , _ , _ , _ > (
1205+ & [ vec ! [ 1. , 0. , 0. ] , vec ! [ 1. , 0. , 0. ] ] ,
1206+ None ,
1207+ 1. ,
1208+ None ,
1209+ || ( ) ,
1210+ || ( ) ,
1211+ ) {
1212+ Ok ( _) => panic ! ( "Returned a non-error" ) ,
1213+ Err ( e) => assert_eq ! ( e, InvalidInputError ) ,
1214+ }
1215+ }
9191216}
0 commit comments