mirror of
https://github.com/tig-foundation/tig-monorepo.git
synced 2026-02-21 02:17:48 +08:00
Implement job scheduling challenge
This commit is contained in:
parent
bd333f8f72
commit
91574007eb
2
.github/workflows/build_algorithm.yml
vendored
2
.github/workflows/build_algorithm.yml
vendored
@ -9,12 +9,14 @@ on:
|
||||
- 'vector_search/*'
|
||||
- 'hypergraph/*'
|
||||
- 'neuralnet_optimizer/*'
|
||||
- 'job_scheduling/*'
|
||||
- 'test/satisfiability/*'
|
||||
- 'test/vehicle_routing/*'
|
||||
- 'test/knapsack/*'
|
||||
- 'test/vector_search/*'
|
||||
- 'test/hypergraph/*'
|
||||
- 'test/neuralnet_optimizer/*'
|
||||
- 'test/job_scheduling/*'
|
||||
|
||||
jobs:
|
||||
init:
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2067,6 +2067,7 @@ dependencies = [
|
||||
"ndarray",
|
||||
"paste",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"statrs",
|
||||
|
||||
@ -100,6 +100,7 @@ f"""Library not found at {so_path}:
|
||||
"vector_search": "c004",
|
||||
"hypergraph": "c005",
|
||||
"neuralnet_optimizer": "c006",
|
||||
"job_scheduling": "c007",
|
||||
}
|
||||
challenge_id = challenge_ids[CHALLENGE]
|
||||
|
||||
|
||||
@ -39,3 +39,5 @@ c005 = ["cudarc", "tig-challenges/c005"]
|
||||
hypergraph = ["c005"]
|
||||
c006 = ["cudarc", "tig-challenges/c006"]
|
||||
neuralnet_optimizer = ["c006"]
|
||||
c007 = ["tig-challenges/c007"]
|
||||
job_scheduling = ["c007"]
|
||||
|
||||
1997
tig-algorithms/src/job_scheduling/mod.rs
Normal file
1997
tig-algorithms/src/job_scheduling/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
41
tig-algorithms/src/job_scheduling/template.md
Normal file
41
tig-algorithms/src/job_scheduling/template.md
Normal file
@ -0,0 +1,41 @@
|
||||
# TIG Code Submission
|
||||
|
||||
## Submission Details
|
||||
|
||||
* **Challenge Name:** job_scheduling
|
||||
* **Algorithm Name:** [name of submission]
|
||||
* **Copyright:** [year work created] [name of copyright owner]
|
||||
* **Identity of Submitter:** [name of person or entity submitting the work to TIG]
|
||||
* **Identity of Creator of Algorithmic Method:** [if applicable else null]
|
||||
* **Unique Algorithm Identifier (UAI):** [if applicable else null]
|
||||
|
||||
|
||||
## References and Acknowledgments
|
||||
*(If this implementation is based on or inspired by existing work, please include citations and acknowledgments below. Remove this section if unused.)*
|
||||
|
||||
### 1. Academic Papers
|
||||
- [Author(s)], *"[Paper Title]"*, DOI: [DOI or URL if available]
|
||||
|
||||
### 2. Code References
|
||||
- [Author(s)] – [URL]
|
||||
|
||||
### 3. Other
|
||||
- [Author(s)] – [Details or description]
|
||||
|
||||
|
||||
## Additional Notes
|
||||
*(Include any relevant context, usage notes, or implementation details here. Remove this section if unused.)*
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The files in this folder are under the following licenses:
|
||||
* TIG Benchmarker Outbound License
|
||||
* TIG Commercial License
|
||||
* TIG Inbound Game License
|
||||
* TIG Innovator Outbound Game License
|
||||
* TIG Open Data License
|
||||
* TIG THV Game License
|
||||
|
||||
Copies of the licenses can be obtained at:
|
||||
https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses
|
||||
50
tig-algorithms/src/job_scheduling/template.rs
Normal file
50
tig-algorithms/src/job_scheduling/template.rs
Normal file
@ -0,0 +1,50 @@
|
||||
// TIG's UI uses the pattern `tig_challenges::<challenge_name>` to automatically detect your algorithm's challenge
|
||||
use crate::{seeded_hasher, HashMap, HashSet};
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use tig_challenges::job_scheduling::*;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Hyperparameters {
|
||||
// Optionally define hyperparameters here. Example:
|
||||
// pub param1: usize,
|
||||
// pub param2: f64,
|
||||
}
|
||||
|
||||
pub fn help() {
|
||||
// Print help information about your algorithm here. It will be invoked with `help_algorithm` script
|
||||
println!("No help information provided.");
|
||||
}
|
||||
|
||||
pub fn solve_challenge(
|
||||
challenge: &Challenge,
|
||||
save_solution: &dyn Fn(&Solution) -> Result<()>,
|
||||
hyperparameters: &Option<Map<String, Value>>,
|
||||
) -> Result<()> {
|
||||
// If you need random numbers, recommend using SmallRng with challenge.seed:
|
||||
// use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
// let mut rng = SmallRng::from_seed(challenge.seed);
|
||||
|
||||
// If you need HashMap or HashSet, make sure to use a deterministic hasher for consistent runtime_signature:
|
||||
// use crate::{seeded_hasher, HashMap, HashSet};
|
||||
// let hasher = seeded_hasher(&challenge.seed);
|
||||
// let map = HashMap::with_hasher(hasher);
|
||||
|
||||
// Support hyperparameters if needed:
|
||||
// let hyperparameters = match hyperparameters {
|
||||
// Some(hyperparameters) => {
|
||||
// serde_json::from_value::<Hyperparameters>(Value::Object(hyperparameters.clone()))
|
||||
// .map_err(|e| anyhow!("Failed to parse hyperparameters: {}", e))?
|
||||
// }
|
||||
// None => Hyperparameters { /* set default values here */ },
|
||||
// };
|
||||
|
||||
// use save_solution(&Solution) to save your solution. Overwrites any previous solution
|
||||
|
||||
// return Err(<msg>) if your algorithm encounters an error
|
||||
// return Ok(()) if your algorithm is finished
|
||||
Err(anyhow!("Not implemented"))
|
||||
}
|
||||
|
||||
// Important! Do not include any tests in this file, it will result in your submission being rejected
|
||||
@ -36,3 +36,5 @@ c005 = ["cuda", "tig-algorithms/c005", "tig-challenges/c005"]
|
||||
hypergraph = ["c005"]
|
||||
c006 = ["cuda", "tig-algorithms/c006", "tig-challenges/c006"]
|
||||
neuralnet_optimizer = ["c006"]
|
||||
c007 = ["tig-algorithms/c007", "tig-challenges/c007"]
|
||||
job_scheduling = ["c007"]
|
||||
|
||||
@ -46,8 +46,12 @@ case "$CHALLENGE" in
|
||||
build_so $ALGORITHM
|
||||
build_ptx $ALGORITHM
|
||||
;;
|
||||
job_scheduling)
|
||||
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'job_scheduling'"
|
||||
build_so $ALGORITHM
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid CHALLENGE value. Must be one of: satisfiability, knapsack, vehicle_routing, vector_search, hypergraph, neuralnet_optimizer"
|
||||
echo "Error: Invalid CHALLENGE value. Must be one of: satisfiability, knapsack, vehicle_routing, vector_search, hypergraph, neuralnet_optimizer, job_scheduling"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -21,6 +21,7 @@ rand = { version = "0.8.5", default-features = false, features = [
|
||||
"std_rng",
|
||||
"small_rng",
|
||||
] }
|
||||
rand_distr = "0.4.3"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = { version = "1.0.113" }
|
||||
statrs = { version = "0.18.0" }
|
||||
@ -39,3 +40,5 @@ c005 = ["cudarc"]
|
||||
hypergraph = ["c005"]
|
||||
c006 = ["cudarc", "cudarc/cublas", "cudarc/cudnn"]
|
||||
neuralnet_optimizer = ["c006"]
|
||||
c007 = []
|
||||
job_scheduling = ["c007"]
|
||||
|
||||
177
tig-challenges/src/job_scheduling/baselines/dispatching_rules.rs
Normal file
177
tig-challenges/src/job_scheduling/baselines/dispatching_rules.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use crate::job_scheduling::{Challenge, Solution};
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn average_processing_time(operation: &HashMap<usize, u32>) -> f64 {
|
||||
if operation.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let sum: u32 = operation.values().sum();
|
||||
sum as f64 / operation.len() as f64
|
||||
}
|
||||
|
||||
fn earliest_end_time(
|
||||
time: u32,
|
||||
machine_available_time: &[u32],
|
||||
operation: &HashMap<usize, u32>,
|
||||
) -> u32 {
|
||||
let mut earliest_end = u32::MAX;
|
||||
for (&machine_id, &proc_time) in operation.iter() {
|
||||
let start = time.max(machine_available_time[machine_id]);
|
||||
let end = start + proc_time;
|
||||
if end < earliest_end {
|
||||
earliest_end = end;
|
||||
}
|
||||
}
|
||||
earliest_end
|
||||
}
|
||||
|
||||
pub fn solve_challenge(
|
||||
challenge: &Challenge,
|
||||
save_solution: &dyn Fn(&Solution) -> Result<()>,
|
||||
_hyperparameters: &Option<Map<String, Value>>,
|
||||
) -> Result<()> {
|
||||
let num_jobs = challenge.num_jobs;
|
||||
let num_machines = challenge.num_machines;
|
||||
|
||||
let mut job_products = Vec::with_capacity(num_jobs);
|
||||
for (product, count) in challenge.jobs_per_product.iter().enumerate() {
|
||||
for _ in 0..*count {
|
||||
job_products.push(product);
|
||||
}
|
||||
}
|
||||
if job_products.len() != num_jobs {
|
||||
return Err(anyhow!(
|
||||
"Job count mismatch. Expected {}, got {}",
|
||||
num_jobs,
|
||||
job_products.len()
|
||||
));
|
||||
}
|
||||
|
||||
let mut product_avg_times = Vec::with_capacity(challenge.product_processing_times.len());
|
||||
for product_ops in challenge.product_processing_times.iter() {
|
||||
let mut avg_ops = Vec::with_capacity(product_ops.len());
|
||||
for op in product_ops.iter() {
|
||||
avg_ops.push(average_processing_time(op));
|
||||
}
|
||||
product_avg_times.push(avg_ops);
|
||||
}
|
||||
|
||||
let mut job_ops_len = Vec::with_capacity(num_jobs);
|
||||
let mut job_remaining_work: Vec<f64> = Vec::with_capacity(num_jobs);
|
||||
for &product in job_products.iter() {
|
||||
let avg_ops = &product_avg_times[product];
|
||||
job_ops_len.push(avg_ops.len());
|
||||
job_remaining_work.push(avg_ops.iter().sum());
|
||||
}
|
||||
|
||||
let mut job_next_op_idx = vec![0usize; num_jobs];
|
||||
let mut job_ready_time = vec![0u32; num_jobs];
|
||||
let mut machine_available_time = vec![0u32; num_machines];
|
||||
let mut job_schedule = job_ops_len
|
||||
.iter()
|
||||
.map(|&ops_len| Vec::with_capacity(ops_len))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut remaining_ops = job_ops_len.iter().sum::<usize>();
|
||||
let mut time = 0u32;
|
||||
let eps = 1e-9_f64;
|
||||
|
||||
while remaining_ops > 0 {
|
||||
let mut available_machines = (0..num_machines)
|
||||
.filter(|&m| machine_available_time[m] <= time)
|
||||
.collect::<Vec<usize>>();
|
||||
available_machines.sort_unstable();
|
||||
|
||||
let mut scheduled_any = false;
|
||||
for &machine in available_machines.iter() {
|
||||
let mut best_job: Option<usize> = None;
|
||||
let mut best_priority = -1.0_f64;
|
||||
|
||||
for job in 0..num_jobs {
|
||||
if job_next_op_idx[job] >= job_ops_len[job] {
|
||||
continue;
|
||||
}
|
||||
if job_ready_time[job] > time {
|
||||
continue;
|
||||
}
|
||||
|
||||
let product = job_products[job];
|
||||
let op_idx = job_next_op_idx[job];
|
||||
let op_times = &challenge.product_processing_times[product][op_idx];
|
||||
let proc_time = match op_times.get(&machine) {
|
||||
Some(&value) => value,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let earliest_end = earliest_end_time(time, &machine_available_time, op_times);
|
||||
let machine_end = time.max(machine_available_time[machine]) + proc_time;
|
||||
if machine_end != earliest_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let priority = job_remaining_work[job];
|
||||
if priority > best_priority + eps
|
||||
|| ((priority - best_priority).abs() <= eps
|
||||
&& best_job.map_or(true, |best| job < best))
|
||||
{
|
||||
best_job = Some(job);
|
||||
best_priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(job) = best_job {
|
||||
let product = job_products[job];
|
||||
let op_idx = job_next_op_idx[job];
|
||||
let op_times = &challenge.product_processing_times[product][op_idx];
|
||||
let proc_time = op_times[&machine];
|
||||
|
||||
let start_time = time.max(machine_available_time[machine]);
|
||||
let end_time = start_time + proc_time;
|
||||
|
||||
job_schedule[job].push((machine, start_time));
|
||||
job_next_op_idx[job] += 1;
|
||||
job_ready_time[job] = end_time;
|
||||
machine_available_time[machine] = end_time;
|
||||
job_remaining_work[job] -= product_avg_times[product][op_idx];
|
||||
if job_remaining_work[job] < 0.0 {
|
||||
job_remaining_work[job] = 0.0;
|
||||
}
|
||||
|
||||
remaining_ops -= 1;
|
||||
scheduled_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if remaining_ops == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Compute next event time (either machine becoming available or job becoming ready)
|
||||
let mut next_time: Option<u32> = None;
|
||||
for &t in machine_available_time.iter() {
|
||||
if t > time {
|
||||
next_time = Some(next_time.map_or(t, |best| best.min(t)));
|
||||
}
|
||||
}
|
||||
for job in 0..num_jobs {
|
||||
if job_next_op_idx[job] < job_ops_len[job] && job_ready_time[job] > time {
|
||||
let t = job_ready_time[job];
|
||||
next_time = Some(next_time.map_or(t, |best| best.min(t)));
|
||||
}
|
||||
}
|
||||
|
||||
// Advance time to next event
|
||||
time = next_time.ok_or_else(|| {
|
||||
if scheduled_any {
|
||||
anyhow!("No next event time found while operations remain unscheduled")
|
||||
} else {
|
||||
anyhow!("No schedulable operations remain; dispatching rules stalled")
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
save_solution(&Solution { job_schedule })?;
|
||||
Ok(())
|
||||
}
|
||||
1
tig-challenges/src/job_scheduling/baselines/mod.rs
Normal file
1
tig-challenges/src/job_scheduling/baselines/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod dispatching_rules;
|
||||
383
tig-challenges/src/job_scheduling/mod.rs
Normal file
383
tig-challenges/src/job_scheduling/mod.rs
Normal file
@ -0,0 +1,383 @@
|
||||
use crate::QUALITY_PRECISION;
|
||||
mod baselines;
|
||||
use anyhow::{anyhow, Result};
|
||||
use rand::{
|
||||
distributions::Distribution,
|
||||
rngs::{SmallRng, StdRng},
|
||||
Rng, SeedableRng,
|
||||
};
|
||||
use rand_distr::Normal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub struct FlowConfig {
|
||||
pub avg_op_flexibility: f32,
|
||||
pub reentrance_level: f32,
|
||||
pub flow_structure: f32,
|
||||
pub product_mix_ratio: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Flow {
|
||||
STRICT,
|
||||
PARALLEL,
|
||||
RANDOM,
|
||||
COMPLEX,
|
||||
CHAOTIC,
|
||||
}
|
||||
|
||||
impl From<Flow> for FlowConfig {
|
||||
fn from(flow: Flow) -> Self {
|
||||
match flow {
|
||||
Flow::STRICT => FlowConfig {
|
||||
avg_op_flexibility: 1.0,
|
||||
reentrance_level: 0.2,
|
||||
flow_structure: 0.0,
|
||||
product_mix_ratio: 0.5,
|
||||
},
|
||||
Flow::PARALLEL => FlowConfig {
|
||||
avg_op_flexibility: 3.0,
|
||||
reentrance_level: 0.2,
|
||||
flow_structure: 0.0,
|
||||
product_mix_ratio: 0.5,
|
||||
},
|
||||
Flow::RANDOM => FlowConfig {
|
||||
avg_op_flexibility: 1.0,
|
||||
reentrance_level: 0.0,
|
||||
flow_structure: 0.4,
|
||||
product_mix_ratio: 1.0,
|
||||
},
|
||||
Flow::COMPLEX => FlowConfig {
|
||||
avg_op_flexibility: 3.0,
|
||||
reentrance_level: 0.2,
|
||||
flow_structure: 0.4,
|
||||
product_mix_ratio: 1.0,
|
||||
},
|
||||
Flow::CHAOTIC => FlowConfig {
|
||||
avg_op_flexibility: 10.0,
|
||||
reentrance_level: 0.0,
|
||||
flow_structure: 1.0,
|
||||
product_mix_ratio: 1.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Flow {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Flow::STRICT => write!(f, "strict"),
|
||||
Flow::PARALLEL => write!(f, "parallel"),
|
||||
Flow::RANDOM => write!(f, "random"),
|
||||
Flow::COMPLEX => write!(f, "complex"),
|
||||
Flow::CHAOTIC => write!(f, "chaotic"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Flow {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"strict" => Ok(Flow::STRICT),
|
||||
"parallel" => Ok(Flow::PARALLEL),
|
||||
"random" => Ok(Flow::RANDOM),
|
||||
"complex" => Ok(Flow::COMPLEX),
|
||||
"chaotic" => Ok(Flow::CHAOTIC),
|
||||
_ => Err(anyhow::anyhow!("Invalid flow type: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_kv_string_serde! {
|
||||
Track {
|
||||
n: usize,
|
||||
m: usize,
|
||||
o: usize,
|
||||
r#flow: Flow
|
||||
}
|
||||
}
|
||||
|
||||
impl_base64_serde! {
|
||||
Solution {
|
||||
job_schedule: Vec<Vec<(usize, u32)>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Solution {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
job_schedule: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Challenge {
|
||||
pub seed: [u8; 32],
|
||||
pub num_jobs: usize,
|
||||
pub num_machines: usize,
|
||||
pub num_operations: usize,
|
||||
pub jobs_per_product: Vec<usize>,
|
||||
// each product has a sequence of operations, and each operation has a map of eligible machines to processing times
|
||||
pub product_processing_times: Vec<Vec<HashMap<usize, u32>>>,
|
||||
}
|
||||
|
||||
impl Challenge {
|
||||
pub fn generate_instance(seed: &[u8; 32], track: &Track) -> Result<Self> {
|
||||
let mut rng = SmallRng::from_seed(StdRng::from_seed(seed.clone()).r#gen());
|
||||
let FlowConfig {
|
||||
avg_op_flexibility,
|
||||
reentrance_level,
|
||||
flow_structure,
|
||||
product_mix_ratio,
|
||||
} = track.flow.clone().into();
|
||||
let n_jobs = track.n;
|
||||
let n_machines = track.m;
|
||||
let n_op_types = track.o;
|
||||
let n_products = 1.max((product_mix_ratio * n_jobs as f32) as usize);
|
||||
let n_routes = 1.max((flow_structure * n_jobs as f32) as usize);
|
||||
let min_eligible_machines = 1;
|
||||
let flexibility_std_dev = 0.5;
|
||||
let base_proc_time_min = 1;
|
||||
let base_proc_time_max = 200;
|
||||
let min_speed_factor = 0.8;
|
||||
let max_speed_factor = 1.2;
|
||||
|
||||
// random product for each job, only keep products that have at least one job
|
||||
let mut map = HashMap::new();
|
||||
let jobs_per_product = (0..n_jobs).fold(Vec::new(), |mut acc, _| {
|
||||
let map_len = map.len();
|
||||
let product = *map
|
||||
.entry(rng.gen_range(0..n_products))
|
||||
.or_insert_with(|| map_len);
|
||||
if product >= acc.len() {
|
||||
acc.push(0);
|
||||
}
|
||||
acc[product] += 1;
|
||||
acc
|
||||
});
|
||||
// actual number of products (some products may have zero jobs)
|
||||
let n_products = jobs_per_product.len();
|
||||
|
||||
// random route for each product, only keep routes that are used
|
||||
let mut map = HashMap::new();
|
||||
let product_route = (0..n_products)
|
||||
.map(|_| {
|
||||
let map_len = map.len();
|
||||
*map.entry(rng.gen_range(0..n_routes))
|
||||
.or_insert_with(|| map_len)
|
||||
})
|
||||
.collect::<Vec<usize>>();
|
||||
// actual number of routes
|
||||
let n_routes = map.len();
|
||||
|
||||
// generate operation sequence for each route
|
||||
let routes = (0..n_routes)
|
||||
.map(|_| {
|
||||
let seq_len = n_op_types;
|
||||
let mut base_sequence: Vec<usize> = (0..n_op_types).collect();
|
||||
let mut steps = Vec::new();
|
||||
|
||||
// randomly build op sequence
|
||||
for _ in 0..seq_len {
|
||||
let next_op_idx = if rng.r#gen::<f32>() < flow_structure {
|
||||
// Job Shop Logic: Random permutation
|
||||
rng.gen_range(0..base_sequence.len())
|
||||
} else {
|
||||
// Flow Shop Logic: Pick next sequential op
|
||||
0
|
||||
};
|
||||
|
||||
let op_id = base_sequence.remove(next_op_idx);
|
||||
steps.push(op_id);
|
||||
}
|
||||
|
||||
for step_idx in (2..steps.len()).rev() {
|
||||
// Reentrance Logic
|
||||
if rng.r#gen::<f32>() < reentrance_level {
|
||||
// assuming reentrance_level of 0.1
|
||||
let op_id = steps[rng.gen_range(0..step_idx - 1)];
|
||||
steps.insert(step_idx, op_id);
|
||||
}
|
||||
}
|
||||
|
||||
steps
|
||||
})
|
||||
.collect::<Vec<Vec<usize>>>();
|
||||
|
||||
// generate machine eligibility and base processing time for each operation
|
||||
let normal = Normal::new(avg_op_flexibility, flexibility_std_dev).unwrap();
|
||||
let all_machines = (0..n_machines).collect::<HashSet<usize>>();
|
||||
let op_eligible_machines = (0..n_op_types)
|
||||
.map(|i| {
|
||||
if avg_op_flexibility as usize >= n_machines {
|
||||
(0..n_machines).collect::<HashSet<usize>>()
|
||||
} else {
|
||||
let mut eligible = HashSet::<usize>::from([if i < n_machines {
|
||||
i
|
||||
} else {
|
||||
rng.gen_range(0..n_machines)
|
||||
}]);
|
||||
if avg_op_flexibility > 1.0 {
|
||||
let target_flex = min_eligible_machines
|
||||
.max(normal.sample(&mut rng) as usize)
|
||||
.min(n_machines);
|
||||
let mut remaining = all_machines
|
||||
.difference(&eligible)
|
||||
.cloned()
|
||||
.collect::<Vec<usize>>();
|
||||
let num_to_add = (target_flex - 1).min(remaining.len());
|
||||
for j in 0..num_to_add {
|
||||
let idx = rng.gen_range(j..remaining.len());
|
||||
remaining.swap(j, idx);
|
||||
}
|
||||
eligible.extend(remaining[..num_to_add].iter().cloned());
|
||||
}
|
||||
eligible
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let base_proc_times = (0..n_op_types)
|
||||
.map(|_| rng.gen_range(base_proc_time_min..=base_proc_time_max))
|
||||
.collect::<Vec<u32>>();
|
||||
|
||||
// generate processing times for each product according to its route
|
||||
let product_processing_times = product_route
|
||||
.iter()
|
||||
.map(|&r_idx| {
|
||||
let route = &routes[r_idx];
|
||||
route
|
||||
.iter()
|
||||
.map(|&op_id| {
|
||||
let machines = &op_eligible_machines[op_id];
|
||||
let base_time = base_proc_times[op_id];
|
||||
machines
|
||||
.iter()
|
||||
.map(|&m_id| {
|
||||
(
|
||||
m_id,
|
||||
1.max(
|
||||
(base_time as f32
|
||||
* (min_speed_factor
|
||||
+ (max_speed_factor - min_speed_factor)
|
||||
* rng.r#gen::<f32>()))
|
||||
as u32,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<usize, u32>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Challenge {
|
||||
seed: seed.clone(),
|
||||
num_jobs: n_jobs,
|
||||
num_machines: n_machines,
|
||||
num_operations: n_op_types,
|
||||
jobs_per_product,
|
||||
product_processing_times,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn evaluate_makespan(&self, solution: &Solution) -> Result<u32> {
|
||||
if solution.job_schedule.len() != self.num_jobs {
|
||||
return Err(anyhow!(
|
||||
"Expecting solution to have {} jobs. Got {}",
|
||||
self.num_jobs,
|
||||
solution.job_schedule.len(),
|
||||
));
|
||||
}
|
||||
let mut job = 0;
|
||||
let mut machine_usage = HashMap::<usize, Vec<(u32, u32)>>::new();
|
||||
let mut makespan = 0u32;
|
||||
for (product, num_jobs) in self.jobs_per_product.iter().enumerate() {
|
||||
for _ in 0..*num_jobs {
|
||||
let schedule = &solution.job_schedule[job];
|
||||
let processing_times = &self.product_processing_times[product];
|
||||
if schedule.len() != processing_times.len() {
|
||||
return Err(anyhow!(
|
||||
"Job {} of product {} expecting {} operations. Got {}",
|
||||
job,
|
||||
product,
|
||||
processing_times.len(),
|
||||
schedule.len(),
|
||||
));
|
||||
}
|
||||
let mut min_start_time = 0;
|
||||
for (op_idx, &(machine, start_time)) in schedule.iter().enumerate() {
|
||||
let eligible_machines = &processing_times[op_idx];
|
||||
if !eligible_machines.contains_key(&machine) {
|
||||
return Err(anyhow!("Job {} schedule contains ineligible machine", job,));
|
||||
}
|
||||
if start_time < min_start_time {
|
||||
return Err(anyhow!(
|
||||
"Job {} schedule contains operation starting before previous is complete",
|
||||
job,
|
||||
));
|
||||
}
|
||||
let finish_time = start_time + eligible_machines[&machine];
|
||||
machine_usage
|
||||
.entry(machine)
|
||||
.or_default()
|
||||
.push((start_time, finish_time));
|
||||
min_start_time = finish_time;
|
||||
}
|
||||
// min_start_time is the finish time of the job
|
||||
if min_start_time > makespan {
|
||||
makespan = min_start_time;
|
||||
}
|
||||
job += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (machine, usage) in machine_usage.iter_mut() {
|
||||
usage.sort_by_key(|&(start, _)| start);
|
||||
for i in 1..usage.len() {
|
||||
if usage[i].0 < usage[i - 1].1 {
|
||||
return Err(anyhow!(
|
||||
"Machine {} is scheduled with overlapping jobs",
|
||||
machine,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(makespan)
|
||||
}
|
||||
|
||||
conditional_pub!(
|
||||
fn compute_greedy_baseline(&self) -> Result<Solution> {
|
||||
let solution = RefCell::new(Solution::new());
|
||||
let save_solution_fn = |s: &Solution| -> Result<()> {
|
||||
*solution.borrow_mut() = s.clone();
|
||||
Ok(())
|
||||
};
|
||||
baselines::dispatching_rules::solve_challenge(self, &save_solution_fn, &None)?;
|
||||
Ok(solution.into_inner())
|
||||
}
|
||||
);
|
||||
|
||||
conditional_pub!(
|
||||
fn compute_sota_baseline(&self) -> Result<Solution> {
|
||||
Err(anyhow!("Not implemented yet"))
|
||||
}
|
||||
);
|
||||
|
||||
conditional_pub!(
|
||||
fn evaluate_solution(&self, solution: &Solution) -> Result<i32> {
|
||||
let makespan = self.evaluate_makespan(solution)?;
|
||||
let greedy_solution = self.compute_greedy_baseline()?;
|
||||
let greedy_makespan = self.evaluate_makespan(&greedy_solution)?;
|
||||
// TODO: implement SOTA baseline
|
||||
let quality = (greedy_makespan as f64 - makespan as f64) / greedy_makespan as f64;
|
||||
let quality = quality.clamp(-10.0, 10.0) * QUALITY_PRECISION as f64;
|
||||
let quality = quality.round() as i32;
|
||||
Ok(quality)
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -202,3 +202,7 @@ pub use hypergraph as c005;
|
||||
pub mod neuralnet_optimizer;
|
||||
#[cfg(feature = "c006")]
|
||||
pub use neuralnet_optimizer as c006;
|
||||
#[cfg(feature = "c007")]
|
||||
pub mod job_scheduling;
|
||||
#[cfg(feature = "c007")]
|
||||
pub use job_scheduling as c007;
|
||||
|
||||
@ -34,3 +34,5 @@ c005 = ["cuda", "tig-challenges/c005"]
|
||||
hypergraph = ["c005"]
|
||||
c006 = ["cuda", "tig-challenges/c006"]
|
||||
neuralnet_optimizer = ["c006"]
|
||||
c007 = ["tig-challenges/c007"]
|
||||
job_scheduling = ["c007"]
|
||||
|
||||
@ -335,6 +335,12 @@ pub fn compute_solution(
|
||||
#[cfg(feature = "c006")]
|
||||
dispatch_challenge!(c006, gpu)
|
||||
}
|
||||
"c007" => {
|
||||
#[cfg(not(feature = "c007"))]
|
||||
panic!("tig-runtime was not compiled with '--features c007'");
|
||||
#[cfg(feature = "c007")]
|
||||
dispatch_challenge!(c007, cpu)
|
||||
}
|
||||
_ => panic!("Unsupported challenge"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,3 +32,5 @@ c005 = ["cuda", "tig-challenges/c005"]
|
||||
hypergraph = ["c005"]
|
||||
c006 = ["cuda", "tig-challenges/c006"]
|
||||
neuralnet_optimizer = ["c006"]
|
||||
c007 = ["tig-challenges/c007"]
|
||||
job_scheduling = ["c007"]
|
||||
|
||||
@ -209,6 +209,12 @@ pub fn verify_solution(
|
||||
#[cfg(feature = "c006")]
|
||||
dispatch_challenge!(c006, gpu)
|
||||
}
|
||||
"c007" => {
|
||||
#[cfg(not(feature = "c007"))]
|
||||
panic!("tig-verifier was not compiled with '--features c007'");
|
||||
#[cfg(feature = "c007")]
|
||||
dispatch_challenge!(c007, cpu)
|
||||
}
|
||||
_ => panic!("Unsupported challenge"),
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user