From eb10adab283b4398f512e738ad6ed7e1b15d8086 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Sat, 19 Oct 2024 22:09:28 -0500 Subject: [PATCH 1/5] Set up `CliffScorer` This still needs to have the actual scoring mechanism implemented. --- src/cliff_scorer.rs | 22 ++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 23 insertions(+) create mode 100644 src/cliff_scorer.rs diff --git a/src/cliff_scorer.rs b/src/cliff_scorer.rs new file mode 100644 index 0000000..cb8a8fe --- /dev/null +++ b/src/cliff_scorer.rs @@ -0,0 +1,22 @@ +use ec_core::{individual::scorer::Scorer, test_results::Score}; +use ec_linear::genome::bitstring::Bitstring; + +use crate::knapsack::Knapsack; + +struct CliffScorer { + knapsack: Knapsack, +} + +impl CliffScorer { + fn new(knapsack: Knapsack) -> Self { + Self { knapsack } + } +} + +impl Scorer for CliffScorer { + type Score = anyhow::Result>; + + fn score(&self, genome: &Bitstring) -> Self::Score { + todo!() + } +} diff --git a/src/main.rs b/src/main.rs index 658c0e1..8df097a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod cliff_scorer; mod item; mod knapsack; mod run_error; From f28b5f485cd9c9ff186aca78398fd07ddf3855a0 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Sun, 20 Oct 2024 11:33:52 -0500 Subject: [PATCH 2/5] Add `test_case` dependency --- Cargo.lock | 34 ++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 35 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 058df88..3828ccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,7 @@ dependencies = [ "anyhow", "ec-core", "ec-linear", + "test-case", "thiserror", ] @@ -341,6 +342,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + [[package]] name = "thiserror" version = "1.0.64" diff --git a/Cargo.toml b/Cargo.toml index fa1bae5..062b567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["evolutionary computation", "genetic algorithms", "combinatorial o anyhow = "1.0.89" ec-core = { git = "https://github.com/unhindered-ec/unhindered-ec.git", rev = "dce7338aa6861ddc31e1f7c0601c1bf7fc3df664" } ec-linear = { git = "https://github.com/unhindered-ec/unhindered-ec.git", rev = "dce7338aa6861ddc31e1f7c0601c1bf7fc3df664" } +test-case = "3.3.1" thiserror = "1.0.64" [lints.clippy] From b0c38b36d72f9d84332e60968ca7f78751f51ea2 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Sun, 20 Oct 2024 11:34:55 -0500 Subject: [PATCH 3/5] Change `Item` and `Knapsack::capacity` fields to `u64` This also changed the return type of `Knapsack::capacity()` to `u64`. --- src/item.rs | 18 +++++++++--------- src/knapsack.rs | 12 ++++-------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/item.rs b/src/item.rs index edb411b..fe30b2a 100644 --- a/src/item.rs +++ b/src/item.rs @@ -3,25 +3,25 @@ use std::str::FromStr; #[derive(Debug, PartialEq, Eq)] pub struct Item { - id: usize, - value: usize, - weight: usize, + id: u64, + value: u64, + weight: u64, } impl Item { - pub const fn new(id: usize, value: usize, weight: usize) -> Self { + pub const fn new(id: u64, value: u64, weight: u64) -> Self { Self { id, value, weight } } - pub const fn id(&self) -> usize { + pub const fn id(&self) -> u64 { self.id } - pub const fn value(&self) -> usize { + pub const fn value(&self) -> u64 { self.value } - pub const fn weight(&self) -> usize { + pub const fn weight(&self) -> u64 { self.weight } } @@ -32,8 +32,8 @@ impl FromStr for Item { fn from_str(s: &str) -> Result { let values = s .split_ascii_whitespace() - .map(usize::from_str) - .collect::, _>>()?; + .map(FromStr::from_str) + .collect::, _>>()?; ensure!( values.len() == 3, "The item specification line should have had 3 whitespace separated fields" diff --git a/src/knapsack.rs b/src/knapsack.rs index 353afae..6444c9a 100644 --- a/src/knapsack.rs +++ b/src/knapsack.rs @@ -11,7 +11,7 @@ use crate::item::Item; #[derive(Debug)] pub struct Knapsack { items: Vec, - capacity: usize, + capacity: u64, } impl Knapsack { @@ -31,7 +31,7 @@ impl Knapsack { self.items.iter() } - pub const fn capacity(&self) -> usize { + pub const fn capacity(&self) -> u64 { self.capacity } @@ -62,13 +62,9 @@ impl Knapsack { "There was no capacity line in the input file {:?}\nThis might be because the number of items was set incorrectly.", file_path.as_ref() ))?? - .parse::()?; + .parse()?; - Ok(Self { - items, - capacity, - // Initialize fields - }) + Ok(Self { items, capacity }) } } From de62fe841a8b7ea9b8f77d0d0d8c87868da7d8e4 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Sun, 20 Oct 2024 11:42:46 -0500 Subject: [PATCH 4/5] Add `new()`, `value()` and `weight()` methods to `Knapsack` We also removed specific type references in the parser so it will be easier to refactor later. We added tests for the new `value()` and `weight()` methods, and indirect tests for `new()`. --- src/knapsack.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/knapsack.rs b/src/knapsack.rs index 6444c9a..d182c72 100644 --- a/src/knapsack.rs +++ b/src/knapsack.rs @@ -1,4 +1,6 @@ use anyhow::{anyhow, Context}; +use ec_core::population::Population; +use ec_linear::genome::{bitstring::Bitstring, Linear}; use std::{ fs::File, io::{self, BufRead}, @@ -15,6 +17,10 @@ pub struct Knapsack { } impl Knapsack { + pub fn new(items: Vec, capacity: u64) -> Self { + Knapsack { items, capacity } + } + pub fn items(&self) -> &[Item] { &self.items } @@ -35,6 +41,22 @@ impl Knapsack { self.capacity } + pub fn value(&self, choices: &Bitstring) -> u64 { + self.items + .iter() + .zip(choices.iter()) + .filter_map(|(item, included)| included.then_some(item.value())) + .sum() + } + + pub fn weight(&self, choices: &Bitstring) -> u64 { + self.items + .iter() + .zip(choices.iter()) + .filter_map(|(item, included)| included.then_some(item.weight())) + .sum() + } + pub fn from_file_path(file_path: impl AsRef) -> anyhow::Result { let file = File::open(file_path.as_ref())?; let reader = io::BufReader::new(file); @@ -70,9 +92,11 @@ impl Knapsack { #[expect(clippy::unwrap_used, reason = ".unwrap() is reasonable in tests")] #[cfg(test)] -mod test { +mod tests { use super::Knapsack; use crate::item::Item; + use ec_linear::genome::bitstring::Bitstring; + use test_case::test_case; #[test] fn parse_from_file_path() { @@ -83,4 +107,30 @@ mod test { assert_eq!(knapsack.get_item(2), Some(&Item::new(3, 9, 1))); assert_eq!(knapsack.capacity(), 10); } + + #[test_case([false, false, false], 0; "choose no items")] + #[test_case([false, true, false], 9; "choose one item")] + #[test_case([true, false, true], 7; "choose two items")] + fn test_values(choices: [bool; 3], expected_value: u64) { + let knapsack = Knapsack::new( + vec![Item::new(1, 5, 8), Item::new(2, 9, 6), Item::new(3, 2, 7)], + 100, + ); + + let choices = Bitstring::from_iter(choices); + assert_eq!(knapsack.value(&choices), expected_value); + } + + #[test_case([false, false, false], 0; "choose no items")] + #[test_case([false, true, false], 6; "choose one item")] + #[test_case([true, false, true], 15; "choose two items")] + fn test_weights(choices: [bool; 3], expected_weight: u64) { + let knapsack = Knapsack::new( + vec![Item::new(1, 5, 8), Item::new(2, 9, 6), Item::new(3, 2, 7)], + 100, + ); + + let choices = Bitstring::from_iter(choices); + assert_eq!(knapsack.weight(&choices), expected_weight); + } } From f43d738794f8e7b21e446f5dc40d587d8b461442 Mon Sep 17 00:00:00 2001 From: Nic McPhee Date: Sun, 20 Oct 2024 11:43:33 -0500 Subject: [PATCH 5/5] Implement and test `CliffScorer` --- src/cliff_scorer.rs | 58 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/cliff_scorer.rs b/src/cliff_scorer.rs index cb8a8fe..dcc8aad 100644 --- a/src/cliff_scorer.rs +++ b/src/cliff_scorer.rs @@ -1,8 +1,16 @@ -use ec_core::{individual::scorer::Scorer, test_results::Score}; +use ec_core::individual::scorer::Scorer; use ec_linear::genome::bitstring::Bitstring; use crate::knapsack::Knapsack; +#[derive(Debug, PartialEq, Eq)] +enum CliffScore { + Score(u64), + Overloaded, +} + +// We'll need to impl `PartialOrd` and `Ord` on `CliffScore`. + struct CliffScorer { knapsack: Knapsack, } @@ -14,9 +22,53 @@ impl CliffScorer { } impl Scorer for CliffScorer { - type Score = anyhow::Result>; + type Score = CliffScore; fn score(&self, genome: &Bitstring) -> Self::Score { - todo!() + let value = self.knapsack.value(genome); + let weight = self.knapsack.weight(genome); + if weight > self.knapsack.capacity() { + CliffScore::Overloaded + } else { + CliffScore::Score(value) + } + } +} + +#[cfg(test)] +mod tests { + use test_case::test_case; + + use ec_core::individual::scorer::Scorer; + use ec_linear::genome::bitstring::Bitstring; + + use crate::{cliff_scorer::CliffScore, item::Item, knapsack::Knapsack}; + + use super::CliffScorer; + + #[test_case([false, false, false], 0; "choose no items")] + #[test_case([false, true, false], 9; "choose one item")] + #[test_case([true, false, true], 7; "choose two items")] + fn test_choices(choices: [bool; 3], expected_value: u64) { + let knapsack = Knapsack::new( + vec![Item::new(1, 5, 8), Item::new(2, 9, 6), Item::new(3, 2, 7)], + 100, + ); + let scorer = CliffScorer::new(knapsack); + + let choices = Bitstring::from_iter(choices); + assert_eq!(scorer.score(&choices), CliffScore::Score(expected_value)); + } + + #[test] + fn test_overloading() { + let knapsack = Knapsack::new( + vec![Item::new(1, 5, 8), Item::new(2, 9, 6), Item::new(3, 2, 7)], + 10, + ); + let scorer = CliffScorer::new(knapsack); + + let choices = Bitstring::from_iter([true, true, false]); + assert_eq!(scorer.score(&choices), CliffScore::Overloaded); } }