1use crate::agent::AgentMap;
3use crate::commodity::CommodityMap;
4use crate::input::{input_err_msg, is_sorted_and_unique, read_toml};
5use crate::process::ProcessMap;
6use crate::region::{RegionID, RegionMap};
7use crate::time_slice::TimeSliceInfo;
8use anyhow::{ensure, Context, Result};
9use serde::Deserialize;
10use std::path::{Path, PathBuf};
11
12const MODEL_FILE_NAME: &str = "model.toml";
13
14pub struct Model {
16 pub model_path: PathBuf,
18 pub milestone_years: Vec<u32>,
20 pub agents: AgentMap,
22 pub commodities: CommodityMap,
24 pub processes: ProcessMap,
26 pub time_slice_info: TimeSliceInfo,
28 pub regions: RegionMap,
30}
31
32#[derive(Debug, Deserialize, PartialEq)]
34pub struct ModelFile {
35 pub milestone_years: MilestoneYears,
37}
38
39#[derive(Debug, Deserialize, PartialEq)]
41pub struct MilestoneYears {
42 pub years: Vec<u32>,
44}
45
46fn check_milestone_years(years: &[u32]) -> Result<()> {
56 ensure!(!years.is_empty(), "`milestone_years` is empty");
57
58 ensure!(
59 is_sorted_and_unique(years),
60 "`milestone_years` must be composed of unique values in order"
61 );
62
63 Ok(())
64}
65
66impl ModelFile {
67 pub fn from_path<P: AsRef<Path>>(model_dir: P) -> Result<ModelFile> {
77 let file_path = model_dir.as_ref().join(MODEL_FILE_NAME);
78 let model_file: ModelFile = read_toml(&file_path)?;
79 check_milestone_years(&model_file.milestone_years.years)
80 .with_context(|| input_err_msg(file_path))?;
81
82 Ok(model_file)
83 }
84}
85
86impl Model {
87 pub fn iter_years(&self) -> impl Iterator<Item = u32> + '_ {
89 self.milestone_years.iter().copied()
90 }
91
92 pub fn iter_regions(&self) -> impl Iterator<Item = &RegionID> + '_ {
94 self.regions.keys()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::fs::File;
102 use std::io::Write;
103 use tempfile::tempdir;
104
105 #[test]
106 fn test_check_milestone_years() {
107 assert!(check_milestone_years(&[1]).is_ok());
109 assert!(check_milestone_years(&[1, 2]).is_ok());
110
111 assert!(check_milestone_years(&[]).is_err());
113 assert!(check_milestone_years(&[1, 1]).is_err());
114 assert!(check_milestone_years(&[2, 1]).is_err());
115 }
116
117 #[test]
118 fn test_model_file_from_path() {
119 let dir = tempdir().unwrap();
120 {
121 let mut file = File::create(dir.path().join(MODEL_FILE_NAME)).unwrap();
122 writeln!(file, "[milestone_years]\nyears = [2020, 2100]").unwrap();
123 }
124
125 let model_file = ModelFile::from_path(dir.path()).unwrap();
126 assert_eq!(model_file.milestone_years.years, [2020, 2100]);
127 }
128}