Skip to content

Commit

Permalink
Merge pull request #12 from unhindered-ec/3-implement-cliff-scorer
Browse files Browse the repository at this point in the history
implement cliff scorer
  • Loading branch information
NicMcPhee authored Oct 20, 2024
2 parents 1a4499d + f43d738 commit b7dd8d2
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 18 deletions.
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
74 changes: 74 additions & 0 deletions src/cliff_scorer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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,
}

impl CliffScorer {
fn new(knapsack: Knapsack) -> Self {
Self { knapsack }
}
}

impl Scorer<Bitstring> for CliffScorer {
type Score = CliffScore;

fn score(&self, genome: &Bitstring) -> Self::Score {
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);
}
}
18 changes: 9 additions & 9 deletions src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -32,8 +32,8 @@ impl FromStr for Item {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let values = s
.split_ascii_whitespace()
.map(usize::from_str)
.collect::<Result<Vec<usize>, _>>()?;
.map(FromStr::from_str)
.collect::<Result<Vec<_>, _>>()?;
ensure!(
values.len() == 3,
"The item specification line should have had 3 whitespace separated fields"
Expand Down
64 changes: 55 additions & 9 deletions src/knapsack.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -11,10 +13,14 @@ use crate::item::Item;
#[derive(Debug)]
pub struct Knapsack {
items: Vec<Item>,
capacity: usize,
capacity: u64,
}

impl Knapsack {
pub fn new(items: Vec<Item>, capacity: u64) -> Self {
Knapsack { items, capacity }
}

pub fn items(&self) -> &[Item] {
&self.items
}
Expand All @@ -31,10 +37,26 @@ impl Knapsack {
self.items.iter()
}

pub const fn capacity(&self) -> usize {
pub const fn capacity(&self) -> u64 {
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<Path>) -> anyhow::Result<Self> {
let file = File::open(file_path.as_ref())?;
let reader = io::BufReader::new(file);
Expand Down Expand Up @@ -62,21 +84,19 @@ 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::<usize>()?;
.parse()?;

Ok(Self {
items,
capacity,
// Initialize fields
})
Ok(Self { items, capacity })
}
}

#[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() {
Expand All @@ -87,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);
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod cliff_scorer;
mod item;
mod knapsack;
mod run_error;
Expand Down

0 comments on commit b7dd8d2

Please sign in to comment.