Skip to content

Commit b7f7aeb

Browse files
authored
Merge pull request #125 from JeroenGar/feat/warm-start
(#124) Warm start
2 parents 0bcff8f + 758a19c commit b7f7aeb

File tree

7 files changed

+63
-21
lines changed

7 files changed

+63
-21
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ name = "bench"
1414
path = "src/bench.rs"
1515

1616
[dependencies]
17-
jagua-rs = { features = ["spp"], git = "https://github.com/JeroenGar/jagua-rs.git", rev = "b85d9268e46f4273b673325b172caee7b6116583"}
17+
jagua-rs = { features = ["spp"], git = "https://github.com/JeroenGar/jagua-rs.git", rev = "76aab0fa70688ee50d1809f5376f71c758ea0b7c"}
1818
#jagua-rs = { features = ["spp"], path = "../jagua-rs/jagua-rs" }
1919
rand = "0.9"
2020
rand_distr = "0.5"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ This repository accompanies the paper: ["_An open-source heuristic to reboot 2D
4646
**General usage:**
4747
```bash
4848
cargo run --release -- \
49-
-i [path to input JSON] \
49+
-i [path to input JSON, or a solution JSON] \
5050
-t [timelimit in seconds (default is 600s)]
5151
```
5252
The optimization process contains two distinct phases: exploration & compression.
@@ -55,7 +55,7 @@ Pressing 'Ctrl + C' immediately moves the algorithm to the next phase, or termin
5555

5656
**All CLI options:**
5757
```bash
58-
-i, --input <INPUT> Path to the input JSON file
58+
-i, --input <INPUT> Path to the input JSON file, or a solution JSON file for warm starting
5959
-t, --global-time <GLOBAL_TIME> Set a global time limit (in seconds)
6060
-e, --exploration <EXPLORATION> Set the exploration phase time limit (in seconds)
6161
-c, --compression <COMPRESSION> Set the compression phase time limit (in seconds)

src/bench.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ fn main() -> Result<()> {
5959
let n_runs_per_iter = (num_cpus::get_physical() / config.expl_cfg.separator_config.n_workers).min(n_runs_total);
6060
let n_batches = (n_runs_total as f32 / n_runs_per_iter as f32).ceil() as usize;
6161

62-
let ext_instance = io::read_spp_instance_json(Path::new(&input_file_path))?;
62+
let (ext_instance, _) = io::read_spp_input(Path::new(&input_file_path))?;
6363

6464
println!(
6565
"[BENCH] starting bench for {} ({}x{} runs across {} cores, {:?} timelimit)",
6666
ext_instance.name, n_batches, n_runs_per_iter, num_cpus::get_physical(), time_limit
6767
);
6868

6969
let importer = Importer::new(config.cde_config, config.poly_simpl_tolerance, config.min_item_separation, config.narrow_concavity_cutoff_ratio);
70-
let instance = jagua_rs::probs::spp::io::import(&importer, &ext_instance)?;
70+
let instance = jagua_rs::probs::spp::io::import_instance(&importer, &ext_instance)?;
7171

7272
let mut final_solutions = vec![];
7373

src/main.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rand::SeedableRng;
66
use sparrow::config::*;
77
use sparrow::optimizer::optimize;
88
use sparrow::util::io;
9-
use sparrow::util::io::{MainCli, SPOutput};
9+
use sparrow::util::io::{MainCli, ExtSPOutput};
1010
use std::fs;
1111
use std::path::Path;
1212
use std::time::Duration;
@@ -75,10 +75,14 @@ fn main() -> Result<()>{
7575

7676
info!("[MAIN] system time: {}", jiff::Timestamp::now());
7777

78-
let ext_instance = io::read_spp_instance_json(Path::new(&input_file_path))?;
78+
let (ext_instance, ext_solution) = io::read_spp_input(Path::new(&input_file_path))?;
7979

8080
let importer = Importer::new(config.cde_config, config.poly_simpl_tolerance, config.min_item_separation, config.narrow_concavity_cutoff_ratio);
81-
let instance = jagua_rs::probs::spp::io::import(&importer, &ext_instance)?;
81+
let instance = jagua_rs::probs::spp::io::import_instance(&importer, &ext_instance)?;
82+
83+
let initial_solution = ext_solution.map(|e|
84+
jagua_rs::probs::spp::io::import_solution(&instance, &e)
85+
);
8286

8387
info!("[MAIN] loaded instance {} with #{} items", ext_instance.name, instance.total_item_qty());
8488

@@ -104,10 +108,18 @@ fn main() -> Result<()>{
104108

105109
let mut ctrlc_terminator = CtrlCTerminator::new();
106110

107-
let solution = optimize(instance.clone(), rng, &mut svg_exporter, &mut ctrlc_terminator, &config.expl_cfg, &config.cmpr_cfg);
111+
let solution = optimize(
112+
instance.clone(),
113+
rng,
114+
&mut svg_exporter,
115+
&mut ctrlc_terminator,
116+
&config.expl_cfg,
117+
&config.cmpr_cfg,
118+
initial_solution.as_ref()
119+
);
108120

109121
let json_path = format!("{OUTPUT_DIR}/final_{}.json", ext_instance.name);
110-
let json_output = SPOutput {
122+
let json_output = ExtSPOutput {
111123
instance: ext_instance,
112124
solution: jagua_rs::probs::spp::io::export(&instance, &solution, *EPOCH)
113125
};

src/optimizer/mod.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::optimizer::separator::Separator;
44
use jagua_rs::probs::spp::entities::{SPInstance, SPSolution};
55
use rand::{RngCore, SeedableRng};
66
use std::time::Duration;
7+
use log::info;
78
use rand_xoshiro::Xoshiro256PlusPlus;
89
use crate::consts::LBF_SAMPLE_CONFIG;
910
use crate::optimizer::compress::compression_phase;
@@ -18,12 +19,31 @@ pub mod explore;
1819
pub mod compress;
1920

2021
///Algorithm 11 from https://doi.org/10.48550/arXiv.2509.13329
21-
pub fn optimize(instance: SPInstance, mut rng: Xoshiro256PlusPlus, sol_listener: &mut impl SolutionListener, terminator: &mut impl Terminator, expl_config: &ExplorationConfig, cmpr_config: &CompressionConfig) -> SPSolution {
22+
pub fn optimize(
23+
instance: SPInstance,
24+
mut rng: Xoshiro256PlusPlus,
25+
sol_listener: &mut impl SolutionListener,
26+
terminator: &mut impl Terminator,
27+
expl_config: &ExplorationConfig,
28+
cmpr_config: &CompressionConfig,
29+
initial_solution: Option<&SPSolution>
30+
) -> SPSolution {
2231
let mut next_rng = || Xoshiro256PlusPlus::seed_from_u64(rng.next_u64());
23-
let builder = LBFBuilder::new(instance.clone(), next_rng(), LBF_SAMPLE_CONFIG).construct();
32+
let start_prob = match initial_solution {
33+
None => {
34+
let builder = LBFBuilder::new(instance.clone(), next_rng(), LBF_SAMPLE_CONFIG).construct();
35+
builder.prob
36+
}
37+
Some(init_sol) => {
38+
info!("[OPT] warm starting from provided initial solution");
39+
let mut prob = jagua_rs::probs::spp::entities::SPProblem::new(instance.clone());
40+
prob.restore(init_sol);
41+
prob
42+
}
43+
};
2444

2545
terminator.new_timeout(expl_config.time_limit);
26-
let mut expl_separator = Separator::new(builder.instance, builder.prob, next_rng(), expl_config.separator_config);
46+
let mut expl_separator = Separator::new(instance.clone(), start_prob, next_rng(), expl_config.separator_config);
2747
let solutions = exploration_phase(
2848
&instance,
2949
&mut expl_separator,

src/util/io.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::EPOCH;
1313
#[derive(Parser)]
1414
pub struct MainCli {
1515
/// Path to input file (mandatory)
16-
#[arg(short = 'i', long, help = "Path to the input JSON file")]
16+
#[arg(short = 'i', long, help = "Path to the input JSON file, or a solution JSON file for warm starting")]
1717
pub input: String,
1818

1919
/// Global time limit in seconds (mutually exclusive with -e and -c)
@@ -37,7 +37,7 @@ pub struct MainCli {
3737
}
3838

3939
#[derive(Serialize, Deserialize, Clone)]
40-
pub struct SPOutput {
40+
pub struct ExtSPOutput {
4141
#[serde(flatten)]
4242
pub instance: ExtSPInstance,
4343
pub solution: ExtSPSolution,
@@ -111,8 +111,18 @@ pub fn write_json(json: &impl Serialize, path: &Path, log_lvl: Level) -> Result<
111111
Ok(())
112112
}
113113

114-
pub fn read_spp_instance_json(path: &Path) -> Result<ExtSPInstance> {
115-
let file = File::open(path).context("could not open instance file")?;
116-
serde_json::from_reader(BufReader::new(file))
117-
.context("not a valid strip packing instance (ExtSPInstance)")
114+
pub fn read_spp_input(path: &Path) -> Result<(ExtSPInstance, Option<ExtSPSolution>)> {
115+
let input_str = fs::read_to_string(path).context("could not read input file")?;
116+
//try parsing a full output (instance + solution)
117+
match serde_json::from_str::<ExtSPOutput>(&input_str) {
118+
Ok(ext_output) => {
119+
Ok((ext_output.instance, Some(ext_output.solution)))
120+
}
121+
Err(_) => {
122+
//try parsing just the instance
123+
let ext_instance = serde_json::from_str::<ExtSPInstance>(&input_str)
124+
.context("could not parse instance from input file")?;
125+
Ok((ext_instance, None))
126+
}
127+
}
118128
}

tests/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ mod integration_tests {
2929
fn simulate_optimization(path: &str) -> Result<()> {
3030
let config = DEFAULT_SPARROW_CONFIG;
3131
let input_file_path = format!("{INSTANCE_BASE_PATH}/{path}");
32-
let json_instance = io::read_spp_instance_json(Path::new(&input_file_path))?;
32+
let (json_instance, _) = io::read_spp_input(Path::new(&input_file_path))?;
3333

3434
let importer = Importer::new(config.cde_config, config.poly_simpl_tolerance, config.min_item_separation, config.narrow_concavity_cutoff_ratio);
35-
let instance = jagua_rs::probs::spp::io::import(&importer, &json_instance)?;
35+
let instance = jagua_rs::probs::spp::io::import_instance(&importer, &json_instance)?;
3636

3737
println!("[TEST] loaded instance: {}", json_instance.name);
3838

0 commit comments

Comments
 (0)