1use crate::graph::save_commodity_graphs_for_model;
3use crate::input::{load_commodity_graphs, load_model};
4use crate::log;
5use crate::output::{copy_input_files, create_output_directory, get_graphs_dir, get_output_dir};
6use crate::settings::Settings;
7use crate::timeit::{DispatchContext, InvestmentContext, TimeContext};
8use ::log::{info, warn};
9use anyhow::{Context, Result, ensure};
10use clap::{Args, CommandFactory, Parser, Subcommand};
11use std::path::{self, Path, PathBuf};
12use std::time::Instant;
13
14pub mod example;
15use example::ExampleSubcommands;
16
17pub mod settings;
18use settings::SettingsSubcommands;
19
20#[derive(Parser)]
22#[command(
23 version,
24 about,
25 after_help = concat!(
26 "For more detailed documentation on this version of MUSE2, see: ", crate::docs_url!()
27 )
28)]
29struct Cli {
30 #[command(subcommand)]
32 command: Option<Commands>,
33 #[arg(long, hide = true)]
35 markdown_help: bool,
36}
37
38#[derive(Args)]
40pub struct RunOpts {
41 #[arg(short, long)]
43 pub output_dir: Option<PathBuf>,
44 #[arg(long)]
46 pub overwrite: bool,
47 #[arg(long)]
49 pub debug_model: bool,
50
51 #[arg(long)]
53 pub no_copy_input_files: bool,
54}
55
56#[derive(Args)]
58pub struct GraphOpts {
59 #[arg(short, long)]
61 pub output_dir: Option<PathBuf>,
62 #[arg(long)]
64 pub overwrite: bool,
65}
66
67#[derive(Subcommand)]
69enum Commands {
70 Run {
72 model_dir: PathBuf,
74 #[command(flatten)]
76 opts: RunOpts,
77 },
78 Example {
80 #[command(subcommand)]
82 subcommand: ExampleSubcommands,
83 },
84 Validate {
86 model_dir: PathBuf,
88 },
89 SaveGraphs {
91 model_dir: PathBuf,
93 #[command(flatten)]
95 opts: GraphOpts,
96 },
97 Settings {
99 #[command(subcommand)]
101 subcommand: SettingsSubcommands,
102 },
103}
104
105impl Commands {
106 fn execute(self) -> Result<()> {
108 match self {
109 Self::Run { model_dir, opts } => handle_run_command(&model_dir, &opts),
110 Self::Example { subcommand } => subcommand.execute(),
111 Self::Validate { model_dir } => handle_validate_command(&model_dir),
112 Self::SaveGraphs { model_dir, opts } => handle_save_graphs_command(&model_dir, &opts),
113 Self::Settings { subcommand } => subcommand.execute(),
114 }
115 }
116}
117
118pub fn run_cli() -> Result<()> {
120 let cli = Cli::parse();
121
122 if cli.markdown_help {
124 clap_markdown::print_help_markdown::<Cli>();
125 return Ok(());
126 }
127
128 if let Some(command) = cli.command {
129 command.execute()?;
130 } else {
131 Cli::command().print_long_help()?;
133 }
134
135 Ok(())
136}
137
138pub fn handle_run_command(model_path: &Path, opts: &RunOpts) -> Result<()> {
140 let start = Instant::now();
141 let mut settings = Settings::load_or_default().context("Failed to load settings.")?;
142
143 if opts.debug_model {
145 settings.debug_model = true;
146 }
147 if opts.overwrite {
148 settings.overwrite = true;
149 }
150 if opts.no_copy_input_files {
151 settings.copy_input_files = false;
152 }
153
154 let pathbuf: PathBuf;
156 let output_path = if let Some(p) = opts.output_dir.as_deref() {
157 p
158 } else {
159 pathbuf = get_output_dir(model_path, settings.results_root)?;
160 &pathbuf
161 };
162
163 ensure!(
164 !path::absolute(model_path)?.starts_with(path::absolute(output_path)?),
165 "Model input data cannot be inside output folder"
166 );
167
168 let overwrite =
169 create_output_directory(output_path, settings.overwrite).with_context(|| {
170 format!(
171 "Failed to create output directory: {}",
172 output_path.display()
173 )
174 })?;
175
176 let model_path = model_path
177 .canonicalize()
178 .context("Failed to resolve model path.")?;
179 let model_name = model_path
180 .file_name()
181 .context("Model cannot be the root directory.")?
182 .to_str()
183 .context("Invalid chars in model directory name")?;
184
185 if settings.copy_input_files {
186 copy_input_files(&model_path, output_path, model_name)
187 .context("Failed to copy input files to output directory.")?;
188 }
189 if !cfg!(feature = "bench") {
191 log::init(&settings.log_level, Some(output_path))
193 .context("Failed to initialise logging.")?;
194 }
195 info!("Starting MUSE2 v{}", env!("CARGO_PKG_VERSION"));
196
197 let model = load_model(&model_path).context("Failed to load model.")?;
199 info!("Loaded model from {}", model_path.display());
200 info!("Output folder: {}", output_path.display());
201
202 if overwrite {
204 warn!("Output folder will be overwritten");
205 }
206
207 crate::simulation::run(&model, output_path, settings.debug_model)?;
209 info!("Simulation complete!");
210
211 info!(
212 "--- Total execution time: {:.1}ms",
213 start.elapsed().as_secs_f64() * 1000.0
214 );
215
216 info!(
217 "--- Total time spent in investment steps: {:.1}ms",
218 InvestmentContext::total_time()
219 );
220 info!(
221 "--- Total time spent in dispatch steps: {:.1}ms",
222 DispatchContext::total_time()
223 );
224
225 Ok(())
226}
227
228pub fn handle_validate_command(model_path: &Path) -> Result<()> {
230 let settings = Settings::load_or_default().context("Failed to load settings.")?;
231
232 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
234
235 load_model(model_path).context("Failed to validate model.")?;
237 info!("Model validation successful!");
238
239 Ok(())
240}
241
242pub fn handle_save_graphs_command(model_path: &Path, opts: &GraphOpts) -> Result<()> {
244 let mut settings = Settings::load_or_default().context("Failed to load settings.")?;
245
246 if opts.overwrite {
247 settings.overwrite = true;
248 }
249
250 let pathbuf: PathBuf;
252 let output_path = if let Some(p) = opts.output_dir.as_deref() {
253 p
254 } else {
255 pathbuf = get_graphs_dir(model_path, settings.graph_results_root)?;
256 &pathbuf
257 };
258
259 let overwrite =
260 create_output_directory(output_path, settings.overwrite).with_context(|| {
261 format!(
262 "Failed to create graphs directory: {}",
263 output_path.display()
264 )
265 })?;
266
267 log::init(&settings.log_level, None).context("Failed to initialise logging.")?;
269
270 if overwrite {
272 warn!("Graphs directory will be overwritten");
273 }
274
275 let commodity_graphs = load_commodity_graphs(model_path).context("Failed to build graphs.")?;
277 save_commodity_graphs_for_model(&commodity_graphs, output_path)?;
278 info!("Graphs saved to: {}", output_path.display());
279
280 Ok(())
281}