1use crate::{GlobalArgs, IntVectorFormat, NumThreadsArg};
8use anyhow::{Result, ensure};
9use clap::{Parser, ValueEnum};
10use dsi_bitstream::prelude::*;
11use dsi_progress_logger::{ProgressLog, concurrent_progress_logger};
12use std::path::PathBuf;
13use webgraph::{graphs::bvgraph::get_endianness, prelude::BvGraph};
14use webgraph_algo::distances::exact_sum_sweep::{
15 All, AllForward, Diameter, Level, Radius, RadiusDiameter,
16};
17
18#[derive(Parser, Debug)]
19#[command(name = "exact-sum-sweep", about = "Computes radius, diameter, and possibly eccentricities using the ExactSumSweep algorithm (scalar values are printed on stdout).", long_about = None)]
20pub struct CliArgs {
21 pub basename: PathBuf,
23
24 pub transposed: Option<PathBuf>,
26
27 #[arg(short, long = "symm")]
28 pub symmetric: bool,
30
31 #[arg(short, long)]
33 pub forward: Option<PathBuf>,
34
35 #[arg(short, long)]
37 pub backward: Option<PathBuf>,
38
39 #[arg(long, value_enum)]
41 pub level: LevelArg,
42
43 #[arg(long, value_enum, default_value_t = IntVectorFormat::Ascii)]
44 pub fmt: IntVectorFormat,
46
47 #[clap(flatten)]
48 pub num_threads: NumThreadsArg,
49}
50
51#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
53pub enum LevelArg {
54 Radius,
55 Diameter,
56 #[clap(name = "radius-diameter")]
57 RadiusDiameter,
58 #[clap(name = "all-forward")]
59 AllForward,
60 All,
61}
62
63pub fn main(global_args: GlobalArgs, args: CliArgs) -> Result<()> {
64 ensure!(
65 args.symmetric || args.transposed.is_some(),
66 "You have to either pass --transposed with the basename of the transposed graph or --symm if the graph is symmetric."
67 );
68 ensure!(
69 !(args.symmetric && args.transposed.is_some()),
70 "--transposed is needed only if the graph is not symmetric."
71 );
72 ensure!(
73 args.forward.is_none() || matches!(args.level, LevelArg::All | LevelArg::AllForward),
74 "You can only pass --forward with --level=all or --level=all-forward, as the forward eccentricities won't be computed otherwise."
75 );
76 ensure!(
77 !(args.forward.is_none() && matches!(args.level, LevelArg::All | LevelArg::AllForward)),
78 "If --level=all or --level=all-forward, you should pass --forward to store the computed eccentricities."
79 );
80 ensure!(
81 !(args.backward.is_some() && args.level != LevelArg::All),
82 "You cannot only pass --backward with --level=all as the backward eccentricities won't be computed otherwise."
83 );
84 ensure!(
85 !(args.level == LevelArg::All && args.symmetric && args.backward.is_some()),
86 "You cannot pass --backward with --symm and --level=all as the eccentricities of a symmetric graph are the same in both directions."
87 );
88
89 match get_endianness(&args.basename)?.as_str() {
90 #[cfg(feature = "be_bins")]
91 BE::NAME => exact_sum_sweep::<BE>(global_args, args),
92 #[cfg(feature = "le_bins")]
93 LE::NAME => exact_sum_sweep::<LE>(global_args, args),
94 e => panic!("Unknown endianness: {}", e),
95 }
96}
97
98fn store_eccentricities(
100 eccentricities: &[usize],
101 path: &PathBuf,
102 fmt: IntVectorFormat,
103) -> Result<()> {
104 fmt.store_usizes(path, eccentricities, None)
105}
106
107pub fn exact_sum_sweep<E: Endianness>(global_args: GlobalArgs, args: CliArgs) -> Result<()> {
108 let graph = BvGraph::with_basename(&args.basename).load()?;
109
110 let thread_pool = crate::get_thread_pool(args.num_threads.num_threads);
111 let mut pl = concurrent_progress_logger![];
112 if let Some(log_interval) = global_args.log_interval {
113 pl.log_interval(log_interval);
114 }
115
116 if args.symmetric {
117 match args.level {
118 LevelArg::Radius => {
119 let out = thread_pool.install(|| Radius::run_symm(graph, &mut pl));
120 println!("Radius: {}", out.radius);
121 println!("Radial vertex: {}", out.radial_vertex);
122 println!("Radius iterations: {}", out.radius_iterations);
123 }
124 LevelArg::Diameter => {
125 let out = thread_pool.install(|| Diameter::run_symm(graph, &mut pl));
126 println!("Diameter: {}", out.diameter);
127 println!("Diametral vertex: {}", out.diametral_vertex);
128 println!("Diameter iterations: {}", out.diameter_iterations);
129 }
130 LevelArg::RadiusDiameter => {
131 let out = thread_pool.install(|| RadiusDiameter::run_symm(graph, &mut pl));
132 println!("Radius: {}", out.radius);
133 println!("Diameter: {}", out.diameter);
134 println!("Radial vertex: {}", out.radial_vertex);
135 println!("Diametral vertex: {}", out.diametral_vertex);
136 println!("Radius iterations: {}", out.radius_iterations);
137 println!("Diameter iterations: {}", out.diameter_iterations);
138 }
139 LevelArg::AllForward | LevelArg::All => {
140 let out = thread_pool.install(|| All::run_symm(graph, &mut pl));
141 println!("Radius: {}", out.radius);
142 println!("Diameter: {}", out.diameter);
143 println!("Radial vertex: {}", out.radial_vertex);
144 println!("Diametral vertex: {}", out.diametral_vertex);
145 println!("Radius iterations: {}", out.radius_iterations);
146 println!("Diameter iterations: {}", out.diameter_iterations);
147 println!("Iterations: {}", out.iterations);
148 store_eccentricities(
149 &out.eccentricities,
150 args.forward.as_ref().unwrap(),
151 args.fmt,
152 )?;
153 }
154 }
155 } else {
156 let transpose_path = args
157 .transposed
158 .as_ref()
159 .expect("You have to pass the transposed graph if the graph is not symmetric.");
160 let transpose = BvGraph::with_basename(transpose_path).load()?;
161
162 match args.level {
163 LevelArg::Radius => {
164 let out = thread_pool.install(|| Radius::run(graph, transpose, None, &mut pl));
165 println!("Radius: {}", out.radius);
166 println!("Radial vertex: {}", out.radial_vertex);
167 println!("Radius iterations: {}", out.radius_iterations);
168 }
169 LevelArg::Diameter => {
170 let out = thread_pool.install(|| Diameter::run(graph, transpose, None, &mut pl));
171 println!("Diameter: {}", out.diameter);
172 println!("Diametral vertex: {}", out.diametral_vertex);
173 println!("Diameter iterations: {}", out.diameter_iterations);
174 }
175 LevelArg::RadiusDiameter => {
176 let out =
177 thread_pool.install(|| RadiusDiameter::run(graph, transpose, None, &mut pl));
178 println!("Radius: {}", out.radius);
179 println!("Diameter: {}", out.diameter);
180 println!("Radial vertex: {}", out.radial_vertex);
181 println!("Diametral vertex: {}", out.diametral_vertex);
182 println!("Radius iterations: {}", out.radius_iterations);
183 println!("Diameter iterations: {}", out.diameter_iterations);
184 }
185 LevelArg::AllForward => {
186 let out = thread_pool.install(|| AllForward::run(graph, transpose, None, &mut pl));
187 println!("Radius: {}", out.radius);
188 println!("Diameter: {}", out.diameter);
189 println!("Radial vertex: {}", out.radial_vertex);
190 println!("Diametral vertex: {}", out.diametral_vertex);
191 println!("Radius iterations: {}", out.radius_iterations);
192 println!("Diameter iterations: {}", out.diameter_iterations);
193 println!("Forward iterations: {}", out.forward_iterations);
194 store_eccentricities(
195 &out.forward_eccentricities,
196 args.forward.as_ref().unwrap(),
197 args.fmt,
198 )?;
199 }
200 LevelArg::All => {
201 let out = thread_pool.install(|| All::run(graph, transpose, None, &mut pl));
202 println!("Radius: {}", out.radius);
203 println!("Diameter: {}", out.diameter);
204 println!("Radial vertex: {}", out.radial_vertex);
205 println!("Diametral vertex: {}", out.diametral_vertex);
206 println!("Radius iterations: {}", out.radius_iterations);
207 println!("Diameter iterations: {}", out.diameter_iterations);
208 println!("Forward iterations: {}", out.forward_iterations);
209 println!("All iterations: {}", out.all_iterations);
210 store_eccentricities(
211 &out.forward_eccentricities,
212 args.forward.as_ref().unwrap(),
213 args.fmt,
214 )?;
215 if let Some(backward) = args.backward.as_ref() {
216 store_eccentricities(&out.backward_eccentricities, backward, args.fmt)?;
217 }
218 }
219 }
220 }
221
222 Ok(())
223}