Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions crates/end_io/src/aic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::error::map_aic_build_error;
use crate::schema::{AicToml, PowerToml};
use crate::{Error, Result};
use end_model::{
AicInputs, Catalog, ItemNonZeroU32Map, ItemU32Map, OutpostInput, PowerConfig, Stage2Weights,
AicInputs, Catalog, ItemPosF64Map, ItemU32Map, OutpostInput, PosF64, PowerConfig, Stage2Weights,
};
use generativity::{Guard, make_guard};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -100,10 +100,18 @@ fn resolve_aic<'cid, 'sid>(

let supply_per_min_span = raw.supply_per_min.span();
let raw_supply_per_min = raw.supply_per_min.into_inner();
let mut supply_per_min = ItemNonZeroU32Map::with_capacity(raw_supply_per_min.len());
let mut supply_per_min = ItemPosF64Map::with_capacity(raw_supply_per_min.len());
for (item_key, value) in raw_supply_per_min {
let item_key = item_key.into_inner();
let value = value.into_inner();
let value = PosF64::new(value).ok_or_else(|| Error::Schema {
path: path.clone(),
field: "supply_per_min",
index: None,
span: Some(supply_per_min_span.clone()),
src: Some(Arc::clone(&src)),
message: format!("must be > 0, got {value}").into_boxed_str(),
})?;
let item = catalog
.item_id(item_key.as_str())
.ok_or_else(|| Error::UnknownItem {
Expand All @@ -118,10 +126,18 @@ fn resolve_aic<'cid, 'sid>(
let external_consumption_per_min_span = raw.external_consumption_per_min.span();
let raw_external_consumption_per_min = raw.external_consumption_per_min.into_inner();
let mut external_consumption_per_min =
ItemNonZeroU32Map::with_capacity(raw_external_consumption_per_min.len());
ItemPosF64Map::with_capacity(raw_external_consumption_per_min.len());
for (item_key, value) in raw_external_consumption_per_min {
let item_key = item_key.into_inner();
let value = value.into_inner();
let value = PosF64::new(value).ok_or_else(|| Error::Schema {
path: path.clone(),
field: "external_consumption_per_min",
index: None,
span: Some(external_consumption_per_min_span.clone()),
src: Some(Arc::clone(&src)),
message: format!("must be > 0, got {value}").into_boxed_str(),
})?;
let item = catalog
.item_id(item_key.as_str())
.ok_or_else(|| Error::UnknownItem {
Expand Down
89 changes: 83 additions & 6 deletions crates/end_io/src/schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use end_model::{DisplayName, Key, Region};
use serde::Deserialize;
use serde::de::Error as _;
use serde::de::{Visitor, Unexpected};
use std::collections::BTreeMap;
use std::num::NonZeroU32;
use toml::Spanned;
Expand Down Expand Up @@ -120,10 +121,10 @@
pub(crate) power: PowerToml,
#[serde(default)]
pub(crate) objective: ObjectiveToml,
#[serde(default = "default_empty_spanned_item_positive_u32_map")]
pub(crate) supply_per_min: Spanned<BTreeMap<KeyToml, PositiveU32Toml>>,
#[serde(default = "default_empty_spanned_item_positive_u32_map")]
pub(crate) external_consumption_per_min: Spanned<BTreeMap<KeyToml, PositiveU32Toml>>,
#[serde(default = "default_empty_spanned_item_positive_f64_map")]
pub(crate) supply_per_min: Spanned<BTreeMap<KeyToml, PositiveF64Toml>>,
#[serde(default = "default_empty_spanned_item_positive_f64_map")]
pub(crate) external_consumption_per_min: Spanned<BTreeMap<KeyToml, PositiveF64Toml>>,
#[serde(default)]
pub(crate) outposts: Box<[Spanned<OutpostToml>]>,
}
Expand Down Expand Up @@ -285,10 +286,10 @@
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct PositiveU32Toml(NonZeroU32);

Check warning on line 289 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / done

struct `PositiveU32Toml` is never constructed

Check warning on line 289 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / done

struct `PositiveU32Toml` is never constructed

Check warning on line 289 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop ubuntu-22.04

struct `PositiveU32Toml` is never constructed

Check warning on line 289 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop windows-latest

struct `PositiveU32Toml` is never constructed

Check warning on line 289 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop macos-latest

struct `PositiveU32Toml` is never constructed

impl PositiveU32Toml {
pub(crate) fn into_inner(self) -> NonZeroU32 {

Check warning on line 292 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / done

method `into_inner` is never used

Check warning on line 292 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / done

method `into_inner` is never used

Check warning on line 292 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop ubuntu-22.04

method `into_inner` is never used

Check warning on line 292 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop windows-latest

method `into_inner` is never used

Check warning on line 292 in crates/end_io/src/schema.rs

View workflow job for this annotation

GitHub Actions / Tauri Desktop macos-latest

method `into_inner` is never used
self.0
}
}
Expand All @@ -305,6 +306,72 @@
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct PositiveF64Toml(f64);

impl PositiveF64Toml {
pub(crate) fn into_inner(self) -> f64 {
self.0
}
}

impl<'de> Deserialize<'de> for PositiveF64Toml {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct PositiveF64Visitor;

impl<'de> Visitor<'de> for PositiveF64Visitor {
type Value = PositiveF64Toml;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a positive number")
}

fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
parse_positive_f64(value as f64).map(PositiveF64Toml).map_err(E::custom)
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
parse_positive_f64(value as f64).map(PositiveF64Toml).map_err(E::custom)
}

fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
parse_positive_f64(value).map(PositiveF64Toml).map_err(E::custom)
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let parsed = value.parse::<f64>().map_err(|_| {
E::invalid_value(Unexpected::Str(value), &"a positive number")
})?;
parse_positive_f64(parsed).map(PositiveF64Toml).map_err(E::custom)
}

fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&value)
}
}

deserializer.deserialize_any(PositiveF64Visitor)
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct NonNegativeU32Toml(u32);

Expand Down Expand Up @@ -456,6 +523,16 @@
Ok(value)
}

fn parse_positive_f64(value: f64) -> Result<f64, String> {
if !value.is_finite() {
return Err("must be a finite number".to_string());
}
if value <= 0.0 {
return Err(format!("must be > 0, got {value}"));
}
Ok(value)
}

fn parse_supported_aic_version(value: i64) -> Result<u32, String> {
let parsed = parse_non_negative_u32(value)?;
match parsed {
Expand All @@ -464,6 +541,6 @@
}
}

fn default_empty_spanned_item_positive_u32_map() -> Spanned<BTreeMap<KeyToml, PositiveU32Toml>> {
fn default_empty_spanned_item_positive_f64_map() -> Spanned<BTreeMap<KeyToml, PositiveF64Toml>> {
Spanned::new(0..0, BTreeMap::new())
}
}
4 changes: 2 additions & 2 deletions crates/end_io/tests/aic_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ version = 2
);

let err = load_aic_from_str(&src, &catalog, aic_guard).expect_err("zero supply should fail");
assert_toml_parse_with_span(&err, "aic.toml", "must be >= 1, got 0");
assert_toml_parse_with_span(&err, "aic.toml", "must be > 0, got 0");
}

#[test]
Expand All @@ -182,7 +182,7 @@ version = 2

let err =
load_aic_from_str(&src, &catalog, aic_guard).expect_err("zero consumption should fail");
assert_toml_parse_with_span(&err, "aic.toml", "must be >= 1, got 0");
assert_toml_parse_with_span(&err, "aic.toml", "must be > 0, got 0");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/end_model/src/aic_input/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod model;

pub use model::{
AicBuildError, AicInputs, AicInputsBuilder, ItemNonZeroU32Map, ItemU32Map, OutpostId,
AicBuildError, AicInputs, AicInputsBuilder, ItemNonZeroU32Map, ItemPosF64Map, ItemU32Map, OutpostId,
OutpostInput, PowerConfig, Region, Stage2Weights,
};

Expand Down
10 changes: 5 additions & 5 deletions crates/end_model/src/aic_input/model/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use std::collections::HashSet;
use generativity::Guard;

use super::{
AicBuildError, AicInputs, ItemNonZeroU32Map, OutpostId, OutpostInput, PowerConfig, Region,
AicBuildError, AicInputs, ItemPosF64Map, OutpostId, OutpostInput, PowerConfig, Region,
Stage2Weights,
};

#[derive(Debug)]
pub struct AicInputsBuilder<'cid, 'sid> {
region: Region,
supply_per_min: ItemNonZeroU32Map<'cid>,
external_consumption_per_min: ItemNonZeroU32Map<'cid>,
supply_per_min: ItemPosF64Map<'cid>,
external_consumption_per_min: ItemPosF64Map<'cid>,
outposts: Vec<OutpostInput<'cid>>,
outpost_keys: HashSet<crate::Key>,
power_config: PowerConfig,
Expand All @@ -23,8 +23,8 @@ impl<'cid, 'sid> AicInputs<'cid, 'sid> {
pub fn builder(
guard: Guard<'sid>,
power_config: PowerConfig,
supply_per_min: ItemNonZeroU32Map<'cid>,
external_consumption_per_min: ItemNonZeroU32Map<'cid>,
supply_per_min: ItemPosF64Map<'cid>,
external_consumption_per_min: ItemPosF64Map<'cid>,
) -> AicInputsBuilder<'cid, 'sid> {
AicInputsBuilder {
region: Region::FourthValley,
Expand Down
6 changes: 3 additions & 3 deletions crates/end_model/src/aic_input/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod types;

pub use builder::AicInputsBuilder;
pub use types::{
AicBuildError, AicInputs, ItemNonZeroU32Map, ItemU32Map, OutpostId, OutpostInput, PowerConfig,
AicBuildError, AicInputs, ItemNonZeroU32Map, ItemPosF64Map, ItemU32Map, OutpostId, OutpostInput, PowerConfig,
Region, Stage2Weights,
};

Expand All @@ -20,11 +20,11 @@ impl<'cid, 'sid> AicInputs<'cid, 'sid> {
self.region
}

pub fn supply_per_min(&self) -> &ItemNonZeroU32Map<'cid> {
pub fn supply_per_min(&self) -> &ItemPosF64Map<'cid> {
&self.supply_per_min
}

pub fn external_consumption_per_min(&self) -> &ItemNonZeroU32Map<'cid> {
pub fn external_consumption_per_min(&self) -> &ItemPosF64Map<'cid> {
&self.external_consumption_per_min
}

Expand Down
76 changes: 73 additions & 3 deletions crates/end_model/src/aic_input/model/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use generativity::Id;
use thiserror::Error;
use vector_map::VecMap;

use crate::{DisplayName, ItemId, Key};
use crate::{DisplayName, ItemId, Key, PosF64};

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Stage2Weights {
Expand Down Expand Up @@ -233,6 +233,76 @@ impl<'id> IntoIterator for ItemNonZeroU32Map<'id> {
}
}

#[derive(Debug, Clone, Default, PartialEq)]
pub struct ItemPosF64Map<'id>(VecMap<ItemId<'id>, PosF64>);

impl<'id> ItemPosF64Map<'id> {
pub fn new() -> Self {
Self(VecMap::new())
}

pub fn with_capacity(capacity: usize) -> Self {
Self(VecMap::with_capacity(capacity))
}

pub fn len(&self) -> usize {
self.0.len()
}

pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub fn insert(&mut self, item: ItemId<'id>, value: PosF64) -> Option<PosF64> {
self.0.insert(item, value)
}

pub fn get(&self, item: ItemId<'id>) -> Option<&PosF64> {
self.0.get(&item)
}

pub fn iter(&self) -> impl Iterator<Item = (ItemId<'id>, PosF64)> + '_ {
self.0.iter().map(|(item, value)| (*item, *value))
}
}

impl<'id> Extend<(ItemId<'id>, PosF64)> for ItemPosF64Map<'id> {
fn extend<T: IntoIterator<Item = (ItemId<'id>, PosF64)>>(&mut self, iter: T) {
for (item, value) in iter {
self.insert(item, value);
}
}
}

impl<'id> FromIterator<(ItemId<'id>, PosF64)> for ItemPosF64Map<'id> {
fn from_iter<T: IntoIterator<Item = (ItemId<'id>, PosF64)>>(iter: T) -> Self {
let mut map = Self::new();
map.extend(iter);
map
}
}

impl<'id, const N: usize> From<[(ItemId<'id>, PosF64); N]> for ItemPosF64Map<'id> {
fn from(value: [(ItemId<'id>, PosF64); N]) -> Self {
value.into_iter().collect()
}
}

impl<'id> From<Vec<(ItemId<'id>, PosF64)>> for ItemPosF64Map<'id> {
fn from(value: Vec<(ItemId<'id>, PosF64)>) -> Self {
value.into_iter().collect()
}
}

impl<'id> IntoIterator for ItemPosF64Map<'id> {
type Item = (ItemId<'id>, PosF64);
type IntoIter = vector_map::IntoIter<ItemId<'id>, PosF64>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

#[derive(Debug, Clone)]
pub struct OutpostInput<'id> {
pub key: Key,
Expand All @@ -245,8 +315,8 @@ pub struct OutpostInput<'id> {
#[derive(Debug, Clone)]
pub struct AicInputs<'cid, 'sid> {
pub(super) region: Region,
pub(super) supply_per_min: ItemNonZeroU32Map<'cid>,
pub(super) external_consumption_per_min: ItemNonZeroU32Map<'cid>,
pub(super) supply_per_min: ItemPosF64Map<'cid>,
pub(super) external_consumption_per_min: ItemPosF64Map<'cid>,
pub(super) outposts: Box<[OutpostInput<'cid>]>,
pub(super) power_config: PowerConfig,
pub(super) stage2_weights: Stage2Weights,
Expand Down
21 changes: 10 additions & 11 deletions crates/end_model/src/aic_input/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
use generativity::make_guard;

use crate::{
AicBuildError, Catalog, DisplayName, ItemDef, ItemNonZeroU32Map, ItemU32Map, Key, OutpostInput,
PowerConfig, ThermalBankDef,
AicBuildError, Catalog, DisplayName, ItemDef, ItemPosF64Map, ItemU32Map, Key, OutpostInput,
PosF64, PowerConfig, ThermalBankDef,
};
use std::num::NonZeroU32;

fn key(value: &str) -> Key {
value.try_into().expect("valid key")
Expand Down Expand Up @@ -71,19 +70,19 @@ fn item_u32_map_from_vec_uses_last_value_for_duplicates() {
}

#[test]
fn item_non_zero_u32_map_from_vec_uses_last_value_for_duplicates() {
fn item_pos_f64_map_from_vec_uses_last_value_for_duplicates() {
make_guard!(guard);
let (_, a, b) = sample_catalog(guard);
let map: ItemNonZeroU32Map = vec![
(a, NonZeroU32::new(1).expect("non-zero")),
(b, NonZeroU32::new(3).expect("non-zero")),
(a, NonZeroU32::new(2).expect("non-zero")),
let map: ItemPosF64Map = vec![
(a, PosF64::new(1.0).expect("positive")),
(b, PosF64::new(3.0).expect("positive")),
(a, PosF64::new(2.0).expect("positive")),
]
.into();

assert_eq!(map.len(), 2);
assert_eq!(map.get(a), Some(&NonZeroU32::new(2).expect("non-zero")));
assert_eq!(map.get(b), Some(&NonZeroU32::new(3).expect("non-zero")));
assert_eq!(map.get(a), Some(&PosF64::new(2.0).expect("positive")));
assert_eq!(map.get(b), Some(&PosF64::new(3.0).expect("positive")));
}

#[test]
Expand All @@ -95,7 +94,7 @@ fn aic_parse_rejects_duplicate_outpost_keys() {
let mut builder = crate::AicInputs::builder(
aic_guard,
PowerConfig::default(),
vec![(b, NonZeroU32::new(1).expect("non-zero"))].into(),
vec![(b, PosF64::new(1.0).expect("positive"))].into(),
Default::default(),
);

Expand Down
Loading
Loading