diff --git a/src/commands/arguments.rs b/src/commands/arguments.rs index ee59b25..c053c28 100644 --- a/src/commands/arguments.rs +++ b/src/commands/arguments.rs @@ -1,4 +1,4 @@ -use clap::Parser; +use clap::{Parser, ValueEnum}; use std::{path::PathBuf, time::Duration}; use tracing::error; use url::Url; @@ -11,6 +11,7 @@ pub const ENV_REQUIRE_OPTIMAL: &str = "STRIDE_OPTIMAL"; pub const ENV_KEEP_LOGS: &str = "STRIDE_KEEP"; pub const ENV_STRIDE_MAX_RUN_LOGS: &str = "STRIDE_MAX_RUN_LOGS"; pub const ENV_STRIDE_SERVER: &str = "STRIDE_SERVER"; +pub const ENV_INSTANCE_ORDER: &str = "STRIDE_INSTANCE_ORDER"; pub const STRIDE_SERVER_DEFAULT: &str = "https://pace2026.imada.sdu.dk/"; pub const ENV_STRIDE_DOWNLOADS_PATH: &str = "STRIDE_DOWNLOADS_PATH"; pub const STRIDE_DOWNLOADS_PATH_DEFAULT: &str = "stride-downloads"; @@ -70,35 +71,30 @@ pub struct CommandCheckArgs { pub upload: bool, } +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum InstanceOrder { + #[value(alias = "rand", alias = "r")] + Random, + #[value(alias = "nta")] + NodesTreesAsc, + #[value(alias = "ntd")] + NodesTreesDesc, + #[value(alias = "tna")] + TreesNodesAsc, + #[value(alias = "tnd")] + TreesNodesDesc, +} + #[derive(Parser, Debug, Clone)] pub struct CommandRunArgs { - #[arg(short, long, env = ENV_SOLVER, help = "Solver program to execute")] - pub solver: PathBuf, - - #[arg(short, long, help = "List of instance files", required = true, num_args(1..))] - pub instances: Vec, - - #[arg(short='t', long="timeout", env = ENV_SOFT_TIMEOUT, value_parser = parse_duration, help = "Solver time budget in seconds (then SIGTERM)", default_value="30")] - pub soft_timeout: Duration, + #[arg(long, env=ENV_STRIDE_DOWNLOADS_PATH, help="Path where downloads from STRIDE server are placed.", default_value = STRIDE_DOWNLOADS_PATH_DEFAULT)] + pub downloads_path: PathBuf, #[arg(short='g', long="grace", env = ENV_GRACE_PERIOD, value_parser = parse_duration, help = "Seconds between SIGTERM and SIGKILL", default_value="5")] pub grace_period: Duration, - #[arg( - short = 'p', - long = "parallel", - env = ENV_PARALLEL_JOBS, - help = "Number of solvers to run in parallel; default: number of physical cores" - )] - pub parallel_jobs: Option, - - #[arg( - short = 'o', - long = "optimal", - env = ENV_REQUIRE_OPTIMAL, - help = "Treat suboptimal solutions as error, e.g. keep logs of suboptimal runs" - )] - pub require_optimal: bool, + #[arg(short, long, help = "List of instance files", required = true, num_args(1..))] + pub instances: Vec, #[arg( short = 'k', @@ -122,20 +118,42 @@ pub struct CommandRunArgs { )] pub no_envs: bool, - #[arg(last = true, help = "Arguments passed to solver")] - pub solver_args: Vec, - - #[arg(short = 'S', long, env = ENV_STRIDE_SERVER, default_value = STRIDE_SERVER_DEFAULT, help = "Server to upload to")] - pub stride_server: Url, + #[arg(short = 'x', value_enum, default_value_t = InstanceOrder::Random, help = "Order in which instances are executed", env = ENV_INSTANCE_ORDER)] + pub order: InstanceOrder, #[arg(short = 'O', long, help = "Do not communicate with STRIDE servers")] pub offline: bool, + #[arg( + short = 'p', + long = "parallel", + env = ENV_PARALLEL_JOBS, + help = "Number of solvers to run in parallel; default: number of physical cores" + )] + pub parallel_jobs: Option, + #[arg(short = 'r', long="max_run_logs", env = ENV_STRIDE_MAX_RUN_LOGS, help="If more run logs are in the stride-log dir, remove oldest ones")] pub remove_old_logs: Option, - #[arg(long, env=ENV_STRIDE_DOWNLOADS_PATH, help="Path where downloads from STRIDE server are placed.", default_value = STRIDE_DOWNLOADS_PATH_DEFAULT)] - pub downloads_path: PathBuf, + #[arg( + short = 'o', + long = "optimal", + env = ENV_REQUIRE_OPTIMAL, + help = "Treat suboptimal solutions as error, e.g. keep logs of suboptimal runs" + )] + pub require_optimal: bool, + + #[arg(short = 'S', long, env = ENV_STRIDE_SERVER, default_value = STRIDE_SERVER_DEFAULT, help = "Server to upload to")] + pub stride_server: Url, + + #[arg(short='t', long="timeout", env = ENV_SOFT_TIMEOUT, value_parser = parse_duration, help = "Solver time budget in seconds (then SIGTERM)", default_value="30")] + pub soft_timeout: Duration, + + #[arg(short, long, env = ENV_SOLVER, help = "Solver program to execute")] + pub solver: PathBuf, + + #[arg(last = true, help = "Arguments passed to solver")] + pub solver_args: Vec, } #[derive(Parser, Debug, Clone)] diff --git a/src/commands/run/command.rs b/src/commands/run/command.rs index 1913bc1..c8dc2f5 100644 --- a/src/commands/run/command.rs +++ b/src/commands/run/command.rs @@ -15,7 +15,7 @@ use thiserror::Error; use tracing::{error, trace}; use crate::commands::run::upload::{JobResultUploadAggregation, UploadError, UploadToStride}; -use crate::instances::instance::{InstanceError, collect_instances}; +use crate::instances::instance::{InstanceError, collect_instances, sort_instances}; use crate::instances::parser::InstanceSourceParser; use crate::instances::{ directory::InstanceDirectory, instance::Instance, parser::collect_instances_from_args, @@ -29,7 +29,7 @@ use tokio::time::timeout; use tokio::time::{Duration, sleep}; use tracing_subscriber::EnvFilter; -const DISPLAY_TICK_MIN_WAIT: Duration = Duration::from_millis(25); +const DISPLAY_TICK_MIN_WAIT: Duration = Duration::from_millis(250); pub async fn command_run(args: &CommandRunArgs) -> Result<(), CommandRunError> { let mut task_context = TaskContext::new(args.clone()).await?; @@ -39,6 +39,8 @@ pub async fn command_run(args: &CommandRunArgs) -> Result<(), CommandRunError> { let mut instances = collect_instances(&instance_dir, collect_instances_from_args(&args.instances)?)?; + sort_instances(&mut instances, args.order); + let instances_with_digest = instances.iter().filter(|i| i.idigest().is_some()).count(); task_context.display.set_total_instance(instances.len()); @@ -113,11 +115,9 @@ pub async fn command_run(args: &CommandRunArgs) -> Result<(), CommandRunError> { } } - sleep(DISPLAY_TICK_MIN_WAIT).await; task_context.display.post_processing_tick(); sleep(DISPLAY_TICK_MIN_WAIT).await; task_context.display.final_message(); - sleep(DISPLAY_TICK_MIN_WAIT).await; Ok(()) } diff --git a/src/commands/run/display.rs b/src/commands/run/display.rs index 8f97111..cf27c3e 100644 --- a/src/commands/run/display.rs +++ b/src/commands/run/display.rs @@ -188,6 +188,8 @@ impl ProgressDisplay { } pub fn final_message(&self) { + self.stride_line.finish(); + self.status_line.finish(); println!("{}", self.status_line.message()); } diff --git a/src/instances/instance.rs b/src/instances/instance.rs index a974cca..45ab887 100644 --- a/src/instances/instance.rs +++ b/src/instances/instance.rs @@ -1,8 +1,11 @@ +use crate::commands::arguments::InstanceOrder; use crate::instances::directory::InstanceDirectory; use crate::instances::parser::{InstanceSource, InstanceSourceDescriptor}; use console::Style; use pace26checker::digest::digest_output::InstanceDigest; use pace26io::pace::reader::{Action, InstanceReader, InstanceVisitor, ReaderError}; +use rand::prelude::SliceRandom; +use std::cmp::Reverse; use std::collections::HashMap; use std::io::{BufReader, ErrorKind}; use std::path::{Path, PathBuf}; @@ -230,6 +233,47 @@ fn dedup_instances(instances: &mut Vec) { } } +pub fn sort_instances(instances: &mut [Instance], order: InstanceOrder) { + // we later pop the instances from the back; hence the order here is flipped! + match order { + InstanceOrder::Random => { + instances.shuffle(&mut rand::rng()); + } + InstanceOrder::NodesTreesAsc => { + instances.sort_unstable_by_key(|i| { + Reverse(( + i.num_leaves.unwrap_or(usize::MAX), + i.num_trees.unwrap_or(usize::MAX), + )) + }); + } + InstanceOrder::NodesTreesDesc => { + instances.sort_unstable_by_key(|i| { + ( + i.num_leaves.unwrap_or(usize::MAX), + i.num_trees.unwrap_or(usize::MAX), + ) + }); + } + InstanceOrder::TreesNodesAsc => { + instances.sort_unstable_by_key(|i| { + Reverse(( + i.num_trees.unwrap_or(usize::MAX), + i.num_leaves.unwrap_or(usize::MAX), + )) + }); + } + InstanceOrder::TreesNodesDesc => { + instances.sort_unstable_by_key(|i| { + ( + i.num_trees.unwrap_or(usize::MAX), + i.num_leaves.unwrap_or(usize::MAX), + ) + }); + } + } +} + #[derive(Default)] struct InstanceHeaderVisitor { idigest: Option, diff --git a/testcases/test_solver_valid/shortwait.in b/testcases/test_solver_valid/shortwait.in index 46b36f6..102b494 100644 --- a/testcases/test_solver_valid/shortwait.in +++ b/testcases/test_solver_valid/shortwait.in @@ -1,4 +1,4 @@ -#s test_params {"print": "((5,((3,1),2)),6);\n4;", "wait_seconds": 0.8} +#s test_params {"print": "((5,((3,1),2)),6);\n4;", "wait_seconds": 0.9} #p 2 6 ((5,(((3,1),4),2)),6); (6,(((1,3),2),(4,5))); diff --git a/tests/profile.rs b/tests/profile.rs index 14ccfb8..4d8cc8c 100644 --- a/tests/profile.rs +++ b/tests/profile.rs @@ -64,7 +64,7 @@ async fn profile_time_shortwait() { assert_eq!(result, JobResult::Valid { size: 2 }); assert!( - infos.get("s_wtime").unwrap().as_f64().unwrap() > 0.7, + infos.get("s_wtime").unwrap().as_f64().unwrap() > 0.65, "actual: {:?}", infos.get("s_wtime") );