mosaik_api/
types.rs

1//! Mosaik types as defined in the [Mosaik API](https://gitlab.com/mosaik/api/mosaik-api-python/-/blob/3.0.9/mosaik_api_v3/types.py?ref_type=tags)
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use std::collections::HashMap;
6
7///Time is represented as the number of simulation steps since the
8///simulation started. One step represents `time_resolution` seconds.
9/// All time-based or hybrid simulators start at time=0.
10pub type Time = u64;
11
12///An attribute name
13pub type Attr = String;
14
15///The name of a model.
16pub type ModelName = String;
17
18///A simulator ID
19pub type SimId = String;
20
21///An entity ID
22pub type EntityId = String;
23
24///A full ID of the form "`sim_id.entity_id`"
25pub type FullId = String;
26
27///The format of input data for simulator's step methods.
28pub type InputData = HashMap<EntityId, HashMap<Attr, Map<FullId, Value>>>;
29
30///The requested outputs for `get_data`. For each entity where data is
31///needed, the required attributes are listed.
32pub type OutputRequest = HashMap<EntityId, Vec<Attr>>;
33
34///The compatible Mosaik version with this edition of the API
35pub const API_VERSION: &str = "3.0";
36
37///The format of output data as return by ``get_data``
38#[derive(Debug, Serialize, Deserialize)]
39pub struct OutputData {
40    #[serde(flatten)]
41    pub requests: HashMap<EntityId, HashMap<Attr, Value>>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub time: Option<Time>,
44}
45
46/// Description of a single model in `Meta`
47///
48/// ## Example implementation
49/// ```rust
50/// use mosaik_api::types::ModelDescription;
51///
52/// const foo: ModelDescription = ModelDescription {
53///     public: true,
54///     params: &["init_val"],
55///     attrs: &["delta", "val"],
56///     trigger: Some(&["delta"]),
57///     any_inputs: None,
58///     persistent: None,
59/// };
60/// ```
61#[derive(Debug, Serialize, PartialEq, Clone, Default)]
62pub struct ModelDescription {
63    /// Whether the model can be created directly.
64    pub public: bool,
65    /// The parameters given during creating of this model.
66    pub params: &'static [&'static str],
67    /// The input and output attributes of this model.
68    pub attrs: &'static [&'static str],
69    /// Whether this model accepts inputs other than those specified in `attrs`.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub any_inputs: Option<bool>,
72    /// The input attributes that trigger a step of the associated simulator.
73    /// (Non-trigger attributes are collected and supplied to the simulator when it
74    /// steps next.)
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub trigger: Option<&'static [&'static str]>,
77    /// The output attributes that are persistent.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub persistent: Option<&'static [&'static str]>,
80}
81
82impl ModelDescription {
83    /// Creates a new `ModelDescription` with fields `any_inputs`, `trigger` and `persistent` set to `None`.
84    #[must_use]
85    pub fn new(
86        public: bool,
87        params: &'static [&'static str],
88        attrs: &'static [&'static str],
89    ) -> Self {
90        Self {
91            public,
92            params,
93            attrs,
94            any_inputs: None,
95            trigger: None,
96            persistent: None,
97        }
98    }
99}
100
101/// The meta-data for a simulator.
102#[derive(Debug, Serialize, PartialEq, Clone)]
103pub struct Meta {
104    /// The API version that this simulator supports in the format "major.minor".
105    api_version: &'static str,
106    /// The simulator's stepping type.
107    #[serde(rename = "type")]
108    pub simulator_type: SimulatorType,
109    /// The descriptions of this simulator's models.
110    pub models: HashMap<ModelName, ModelDescription>,
111    /// The names of the extra methods this simulator supports.
112    ///
113    /// # Note
114    /// > These methods can be called while the scenario is being created and can be used
115    /// > for operations that don’t really belong into init() or create().
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub extra_methods: Option<Vec<String>>,
118}
119
120impl Meta {
121    #[must_use]
122    pub fn new(
123        simulator_type: SimulatorType,
124        models: HashMap<ModelName, ModelDescription>,
125        extra_methods: Option<Vec<String>>,
126    ) -> Self {
127        Self {
128            api_version: API_VERSION,
129            simulator_type,
130            models,
131            extra_methods,
132        }
133    }
134
135    #[must_use]
136    pub fn version(&self) -> &str {
137        self.api_version
138    }
139}
140
141impl Default for Meta {
142    #[must_use]
143    fn default() -> Self {
144        Self {
145            api_version: API_VERSION,
146            simulator_type: SimulatorType::default(),
147            models: HashMap::new(),
148            extra_methods: None,
149        }
150    }
151}
152
153/// The three types of simulators. With `Hybrid` being the default.
154/// - `TimeBased`: start at time 0, return the next step time after each step, and produce data valid for \([t_{now}, t_{next})\).
155/// - `EventBased`: start whenever their first event is scheduled, step at event times, can schedule their own events, and produce output valid at specific times.
156/// - `Hybrid`: a mix of the two. Also starts at time 0.
157#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
158#[serde(rename_all = "kebab-case")]
159pub enum SimulatorType {
160    TimeBased,
161    EventBased,
162    #[default]
163    Hybrid,
164}
165
166/// The type for elements of the list returned by `create` calls in the mosaik API.
167#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
168pub struct CreateResult {
169    /// The entity ID of this entity.
170    pub eid: EntityId,
171    /// The model name (as given in the simulator's meta) of this entity.
172    #[serde(rename = "type")]
173    pub model_type: ModelName,
174    /// The entity IDs of the entities of this simulator that are related to this entity.
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub rel: Option<Vec<EntityId>>,
177    /// The child entities of this entity.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub children: Option<Vec<CreateResult>>,
180    /// Any additional information about the entity that the simulator wants to pass back to the scenario.
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub extra_info: Option<HashMap<String, String>>,
183}
184
185impl CreateResult {
186    #[must_use]
187    pub fn new(eid: EntityId, model_type: ModelName) -> Self {
188        Self {
189            eid,
190            model_type,
191            rel: None,
192            children: None,
193            extra_info: None,
194        }
195    }
196}
197
198/*
199// The below types are copied from the python implementation.
200// pub type CreateResultChild = CreateResult;
201
202class EntitySpec(TypedDict):
203    type: ModelName
204
205class EntityGraph(TypedDict):
206    nodes: Dict[FullId, EntitySpec]
207    edges: List[Tuple[FullId, FullId, Dict]]
208*/
209
210// tests for Meta
211#[cfg(test)]
212#[allow(clippy::unwrap_used)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_output_data() {
218        // Example JSON data
219        let json_data = r#"{
220        "eid_1": {"attr_1": "val_1"},
221        "time": 64
222    }
223    "#
224        .replace(['\n', ' '], "");
225
226        // Deserialize JSON to OutputData struct
227        let data: OutputData = serde_json::from_str(&json_data).unwrap();
228        assert_ne!(data.requests, HashMap::new());
229        assert_eq!(data.time, Some(64));
230
231        // Serialize EventData struct to JSON
232        let serialized_json = serde_json::to_string(&data).unwrap();
233        assert!(!serialized_json.contains("requests"));
234        assert!(serialized_json.contains("time"));
235
236        assert_eq!(serialized_json, json_data);
237    }
238
239    #[test]
240    fn test_model_description_without_optionals() {
241        let mut model = ModelDescription::default();
242
243        assert!(!model.public);
244        assert_eq!(model.params.len(), 0);
245        assert_eq!(model.attrs.len(), 0);
246        assert_eq!(model.any_inputs, None);
247        assert_eq!(model.trigger, None);
248        assert_eq!(model.persistent, None);
249
250        let model_json = serde_json::to_string(&model).unwrap();
251        assert_eq!(r#"{"public":false,"params":[],"attrs":[]}"#, model_json);
252
253        model.public = true;
254        model.params = &["init_reading"];
255        model.attrs = &["trades", "total"];
256
257        assert!(model.public);
258        assert_eq!(model.params.len(), 1);
259        assert_eq!(model.attrs.len(), 2);
260
261        let model_json = serde_json::to_string(&model).unwrap();
262        assert_eq!(
263            r#"{"public":true,"params":["init_reading"],"attrs":["trades","total"]}"#,
264            model_json
265        );
266    }
267
268    #[test]
269    fn test_model_description_with_optionals() {
270        let mut model = ModelDescription::new(true, &["init_reading"], &["p_mw_pv", "p_mw_load"]);
271        model.any_inputs = Some(true);
272        model.trigger = Some(&["trigger1"]);
273        model.persistent = Some(&["trades"]);
274
275        let model_json = serde_json::to_string(&model).unwrap();
276        assert_eq!(
277            r#"{"public":true,"params":["init_reading"],"attrs":["p_mw_pv","p_mw_load"],"any_inputs":true,"trigger":["trigger1"],"persistent":["trades"]}"#,
278            model_json
279        );
280
281        model.trigger = Some(&["trigger1"]);
282        model.any_inputs = None;
283        model.persistent = None;
284
285        let model_json = serde_json::to_string(&model).unwrap();
286        assert_eq!(
287            r#"{"public":true,"params":["init_reading"],"attrs":["p_mw_pv","p_mw_load"],"trigger":["trigger1"]}"#,
288            model_json
289        );
290    }
291
292    #[test]
293    fn test_meta_empty() {
294        let meta = Meta::new(SimulatorType::default(), HashMap::new(), None);
295        assert_eq!(
296            meta.api_version, API_VERSION,
297            "API version should match the global variable."
298        );
299        assert_eq!(
300            meta.version(),
301            API_VERSION,
302            "version should return the API version."
303        );
304        assert_eq!(
305            meta.simulator_type,
306            SimulatorType::Hybrid,
307            "Default type should be Hybrid"
308        );
309
310        let empty_meta_json = serde_json::to_string(&meta).unwrap();
311        assert_eq!(
312            r#"{"api_version":"3.0","type":"hybrid","models":{}}"#, empty_meta_json,
313            "Empty meta should not have extra_methods and empty models."
314        );
315        assert!(meta.models.is_empty());
316    }
317
318    #[test]
319    fn test_meta_with_models() {
320        let model1 = ModelDescription::new(true, &["init_reading"], &["trades", "total"]);
321        let meta = Meta::new(
322            SimulatorType::default(),
323            HashMap::from([("MarktplatzModel".to_string(), model1)]),
324            None,
325        );
326        assert_eq!(meta.models.len(), 1, "Should have one model");
327
328        assert!(meta.extra_methods.is_none());
329        let meta_json = serde_json::to_string(&meta).unwrap();
330        assert_eq!(
331            r#"{"api_version":"3.0","type":"hybrid","models":{"MarktplatzModel":{"public":true,"params":["init_reading"],"attrs":["trades","total"]}}}"#,
332            meta_json,
333            "Meta should have one model and no extra methods."
334        );
335    }
336
337    #[test]
338    fn test_meta_optionals() {
339        let meta = Meta::new(
340            SimulatorType::default(),
341            HashMap::new(),
342            Some(vec!["foo".to_string(), "bar".to_string()]),
343        );
344
345        assert_eq!(
346            meta.extra_methods.as_ref().unwrap().len(),
347            2,
348            "Should have 2 extra methods."
349        );
350
351        let meta_json = serde_json::to_string(&meta).unwrap();
352        assert_eq!(
353            r#"{"api_version":"3.0","type":"hybrid","models":{},"extra_methods":["foo","bar"]}"#,
354            meta_json,
355            "JSON String should contain 'foo' and 'bar' as extra methods."
356        );
357    }
358
359    #[test]
360    fn test_create_result_new() {
361        let create_result = CreateResult::new(String::from("eid_1"), String::from("model_name"));
362        assert_eq!(create_result.eid, "eid_1");
363        assert_eq!(create_result.model_type, "model_name");
364        assert!(create_result.rel.is_none());
365        assert!(create_result.children.is_none());
366        assert!(create_result.extra_info.is_none());
367
368        let create_result_json = serde_json::to_string(&create_result).unwrap();
369        assert_eq!(
370            r#"{"eid":"eid_1","type":"model_name"}"#, create_result_json,
371            "New CreateResult should not contain any optional fields"
372        );
373    }
374
375    #[test]
376    fn test_create_results_filled() {
377        let mut create_result = CreateResult::new("eid_1".to_string(), "model_name".to_string());
378
379        create_result.rel = Some(vec!["eid_2".to_string()]);
380        create_result.children = Some(vec![CreateResult::new(
381            "child_1".to_string(),
382            "child".to_string(),
383        )]);
384
385        assert_eq!(create_result.eid, "eid_1");
386        assert_eq!(create_result.model_type, "model_name");
387        assert_eq!(create_result.rel, Some(vec!["eid_2".to_string()]));
388        assert!(create_result.children.is_some());
389        if let Some(children) = &create_result.children {
390            assert_eq!(children.len(), 1);
391            assert_eq!(children[0].eid, "child_1");
392        }
393
394        let create_result_json = serde_json::to_string(&create_result).unwrap();
395        assert_eq!(
396            r#"{"eid":"eid_1","type":"model_name","rel":["eid_2"],"children":[{"eid":"child_1","type":"child"}]}"#,
397            create_result_json,
398            "Filled create result should contain optional fields without extra_info"
399        );
400    }
401}