1use crate::{build_info, num_threads_parser, pretty_print_elapsed, IntVectorFormat};
8use anyhow::Result;
9use clap::Parser;
10use dsi_bitstream::prelude::factory::CodesReaderFactoryHelper;
11use dsi_bitstream::prelude::*;
12use dsi_progress_logger::{progress_logger, ProgressLog};
13use std::path::PathBuf;
14use webgraph::graphs::bvgraph::get_endianness;
15use webgraph::prelude::*;
16use webgraph_algo::thread_pool;
17
18use super::GlobalArgs;
19
20#[derive(Parser, Debug)]
21#[command(name = "webgraph-sccs", version=build_info::version_string())]
22pub struct Cli {
38 #[clap(flatten)]
39 global_args: GlobalArgs,
40 #[clap(flatten)]
41 args: CliArgs,
42}
43
44#[derive(Parser, Debug)]
45pub struct CliArgs {
46 pub basename: PathBuf,
48
49 pub sccs: PathBuf,
51
52 #[arg(short = 's', long)]
53 pub sizes: Option<PathBuf>,
56
57 #[arg(short, long)]
58 pub renumber: bool,
60
61 #[arg(short = 'j', long, default_value_t = rayon::current_num_threads().max(1), value_parser = num_threads_parser)]
62 pub num_threads: usize,
64
65 #[arg(long, value_enum, default_value_t = IntVectorFormat::Ascii)]
66 pub fmt: IntVectorFormat,
68}
69
70pub fn cli_main<I, T>(args: I) -> Result<()>
71where
72 I: IntoIterator<Item = T>,
73 T: Into<std::ffi::OsString> + Clone,
74{
75 let start = std::time::Instant::now();
76 let cli = Cli::parse_from(args);
77 main(cli.global_args, cli.args)?;
78
79 log::info!(
80 "The command took {}",
81 pretty_print_elapsed(start.elapsed().as_secs_f64())
82 );
83
84 Ok(())
85}
86
87pub fn main(global_args: GlobalArgs, args: CliArgs) -> Result<()> {
88 match get_endianness(&args.basename)?.as_str() {
89 #[cfg(feature = "be_bins")]
90 BE::NAME => sccs::<BE>(global_args, args),
91 #[cfg(feature = "le_bins")]
92 LE::NAME => sccs::<LE>(global_args, args),
93 e => panic!("Unknown endianness: {}", e),
94 }
95}
96
97pub fn sccs<E: Endianness>(global_args: GlobalArgs, args: CliArgs) -> Result<()>
98where
99 MemoryFactory<E, MmapHelper<u32>>: CodesReaderFactoryHelper<E>,
100 for<'a> LoadModeCodesReader<'a, E, LoadMmap>: BitSeek,
101{
102 log::info!("Loading the graph from {}", args.basename.display());
103 let graph = BvGraph::with_basename(&args.basename)
104 .mode::<LoadMmap>()
105 .flags(MemoryFlags::TRANSPARENT_HUGE_PAGES | MemoryFlags::RANDOM_ACCESS)
106 .endianness::<E>()
107 .load()?;
108
109 let mut pl = progress_logger![];
110 if let Some(log_interval) = global_args.log_interval {
111 pl.log_interval(log_interval);
112 }
113
114 let mut sccs = webgraph_algo::sccs::tarjan(graph, &mut pl);
115 log::info!(
116 "Found {} strongly connected components",
117 sccs.num_components()
118 );
119
120 if args.renumber {
121 log::info!("Renumbering components by decreasing size");
122 let component_sizes = if args.num_threads == 1 {
123 log::debug!("Using sequential algorithm");
124 sccs.sort_by_size()
125 } else {
126 log::debug!("Using parallel algorithm with {} threads", args.num_threads);
127 let thread_pool = thread_pool![args.num_threads];
128 thread_pool.install(|| sccs.par_sort_by_size())
129 };
130 let max = component_sizes.first().copied();
131 args.fmt.store_usizes(&args.sccs, &component_sizes, max)?;
132 } else if let Some(sizes_path) = args.sizes {
133 log::info!("Computing the sizes of the components");
134 let sizes = sccs.compute_sizes();
135 args.fmt.store_usizes(sizes_path, &sizes, None)?;
136 };
137
138 args.fmt
139 .store_usizes(&args.sccs, sccs.components(), Some(sccs.num_components()))?;
140
141 Ok(())
142}