From c7b2c4036d4229a80b444df76e8a099f892b2f95 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 27 Jul 2023 11:50:46 -0600 Subject: [PATCH 01/18] preliminary tidying up --- .../{add_pyo3_api.rs => add_pyo3_api/mod.rs} | 61 +++- .../src/add_pyo3_api/pyo3_api_utils.rs | 209 +++++++++++++ .../fastsim-proc-macros/src/doc_field.rs | 275 ++++++++++++++++++ .../fastsim-proc-macros/src/lib.rs | 15 +- .../fastsim-proc-macros/src/utilities.rs | 209 +------------ 5 files changed, 559 insertions(+), 210 deletions(-) rename rust/fastsim-core/fastsim-proc-macros/src/{add_pyo3_api.rs => add_pyo3_api/mod.rs} (85%) create mode 100644 rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs create mode 100644 rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs similarity index 85% rename from rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api.rs rename to rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index cb174d69..cf54d8f1 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -1,5 +1,7 @@ +#[macro_use] +mod pyo3_api_utils; + use crate::imports::*; -use crate::utilities::*; pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); @@ -284,3 +286,60 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { final_output.extend::(output); final_output.into() } + +#[derive(Debug, Default, Clone)] +pub struct FieldOptions { + /// if true, getters are not generated for a field + pub skip_get: bool, + /// if true, setters are not generated for a field + pub skip_set: bool, + /// if true, current field is itself a struct with `orphaned` field + pub field_has_orphaned: bool, +} + +pub fn impl_getters_and_setters( + type_path: syn::TypePath, + impl_block: &mut TokenStream2, + ident: &proc_macro2::Ident, + opts: FieldOptions, + has_orphaned: bool, + ftype: syn::Type, +) { + let type_str = type_path.into_token_stream().to_string(); + match type_str.as_str() { + "Array1 < f64 >" => { + impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3ArrayF64, has_orphaned); + } + "Array1 < u32 >" => { + impl_vec_get_set!(opts, ident, impl_block, u32, Pyo3ArrayU32, has_orphaned); + } + "Array1 < i32 >" => { + impl_vec_get_set!(opts, ident, impl_block, i32, Pyo3ArrayI32, has_orphaned); + } + "Array1 < bool >" => { + impl_vec_get_set!(opts, ident, impl_block, bool, Pyo3ArrayBool, has_orphaned); + } + "Vec < f64 >" => { + impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3VecF64, has_orphaned); + } + _ => match ident.to_string().as_str() { + "orphaned" => { + impl_block.extend::(quote! { + #[getter] + pub fn get_orphaned(&self) -> PyResult { + Ok(self.orphaned) + } + /// Reset the orphaned flag to false. + pub fn reset_orphaned(&mut self) -> PyResult<()> { + self.orphaned = false; + Ok(()) + } + }) + } + _ => { + impl_get_body!(ftype, ident, impl_block, opts); + impl_set_body!(ftype, ident, impl_block, has_orphaned, opts); + } + }, + } +} diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs new file mode 100644 index 00000000..465fcaa0 --- /dev/null +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs @@ -0,0 +1,209 @@ +use super::*; + +macro_rules! impl_vec_get_set { + ($opts: ident, $fident: ident, $impl_block: ident, $contained_type: ty, $wrapper_type: expr, $has_orphaned: expr) => { + if !$opts.skip_get { + let get_name: TokenStream2 = format!("get_{}", $fident).parse().unwrap(); + $impl_block.extend::(quote! { + #[getter] + pub fn #get_name(&self) -> PyResult<$wrapper_type> { + Ok($wrapper_type::new(self.#$fident.clone())) + } + }); + } + if !$opts.skip_set { + let set_name: TokenStream2 = format!("set_{}", $fident).parse().unwrap(); + match stringify!($wrapper_type) { + "Pyo3VecF64" => { + if $has_orphaned { + $impl_block.extend(quote! { + #[setter] + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + if !self.orphaned { + self.#$fident = new_value; + Ok(()) + } else { + Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + } + } + }) + } else { + $impl_block.extend(quote! { + #[setter] + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + self.#$fident = new_value; + Ok(()) + } + }) + } + } + _ => { + if $has_orphaned { + $impl_block.extend(quote! { + #[setter] + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + if !self.orphaned { + self.#$fident = Array1::from_vec(new_value); + Ok(()) + } else { + Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + } + } + }) + } else { + $impl_block.extend(quote! { + #[setter] + pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { + self.#$fident = Array1::from_vec(new_value); + Ok(()) + } + }) + } + } + } + + } + }; +} + +/// Generates pyo3 getter methods +/// +/// general match arguments: +/// - type: type of variable (e.g. `f64`) +/// - field: struct field +/// - impl_block: TokenStream2 +/// - opts: FieldOptions struct instance +macro_rules! impl_get_body { + ( + $type: ident, $field: ident, $impl_block: ident, $opts: ident + ) => { + if !$opts.skip_get { + let get_name: TokenStream2 = format!("get_{}", $field).parse().unwrap(); + let get_block = if $opts.field_has_orphaned { + quote! { + #[getter] + pub fn #get_name(&mut self) -> PyResult<#$type> { + self.#$field.orphaned = true; + Ok(self.#$field.clone()) + } + } + } else { + quote! { + #[getter] + pub fn #get_name(&self) -> PyResult<#$type> { + Ok(self.#$field.clone()) + } + } + }; + $impl_block.extend::(get_block) + } + }; +} + +/// Generates pyo3 setter methods +/// +/// general match arguments: +/// - type: type of variable (e.g. `f64`) +/// - field: struct field +/// - impl_block: TokenStream2 +/// - has_orphaned: bool, true if struct has `orphaned` field +/// - opts: FieldOptions struct instance + +macro_rules! impl_set_body { + ( // for generic + $type: ident, $field: ident, $impl_block: ident, $has_orphaned: expr, $opts: ident + ) => { + if !$opts.skip_set { + let set_name: TokenStream2 = format!("set_{}", $field).parse().unwrap(); + let orphaned_set_block = if $has_orphaned && $opts.field_has_orphaned { + quote! { + if !self.orphaned { + self.#$field = new_value; + self.#$field.orphaned = false; + Ok(()) + } else { + Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + } + } + } else if $has_orphaned { + quote! { + if !self.orphaned { + self.#$field = new_value; + Ok(()) + } else { + Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) + } + } + } else { + quote! { + self.#$field = new_value; + Ok(()) + } + }; + + $impl_block.extend::(quote! { + #[setter] + pub fn #set_name(&mut self, new_value: #$type) -> PyResult<()> { + #orphaned_set_block + } + }); + } + }; +} + +#[derive(Debug, Default, Clone)] +pub struct FieldOptions { + /// if true, getters are not generated for a field + pub skip_get: bool, + /// if true, setters are not generated for a field + pub skip_set: bool, + /// if true, current field is itself a struct with `orphaned` field + pub field_has_orphaned: bool, +} + +pub fn impl_getters_and_setters( + type_path: syn::TypePath, + impl_block: &mut TokenStream2, + ident: &proc_macro2::Ident, + opts: FieldOptions, + has_orphaned: bool, + ftype: syn::Type, +) { + let type_str = type_path.into_token_stream().to_string(); + match type_str.as_str() { + "Array1 < f64 >" => { + impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3ArrayF64, has_orphaned); + } + "Array1 < u32 >" => { + impl_vec_get_set!(opts, ident, impl_block, u32, Pyo3ArrayU32, has_orphaned); + } + "Array1 < i32 >" => { + impl_vec_get_set!(opts, ident, impl_block, i32, Pyo3ArrayI32, has_orphaned); + } + "Array1 < bool >" => { + impl_vec_get_set!(opts, ident, impl_block, bool, Pyo3ArrayBool, has_orphaned); + } + "Vec < f64 >" => { + impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3VecF64, has_orphaned); + } + _ => match ident.to_string().as_str() { + "orphaned" => { + impl_block.extend::(quote! { + #[getter] + pub fn get_orphaned(&self) -> PyResult { + Ok(self.orphaned) + } + /// Reset the orphaned flag to false. + pub fn reset_orphaned(&mut self) -> PyResult<()> { + self.orphaned = false; + Ok(()) + } + }) + } + _ => { + impl_get_body!(ftype, ident, impl_block, opts); + impl_set_body!(ftype, ident, impl_block, has_orphaned, opts); + } + }, + } +} diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs new file mode 100644 index 00000000..223be97b --- /dev/null +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -0,0 +1,275 @@ +// use crate::imports::*; +// use crate::utilities::*; + +// pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { +// let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); +// // println!("{}", ast.ident.to_string()); +// let ident = &ast.ident; + +// // add field `doc: Option` + +// let is_state_or_history: bool = +// ident.to_string().contains("State") || ident.to_string().contains("HistoryVec"); + +// if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { +// let field_names: Vec = named +// .iter() +// .map(|f| f.ident.as_ref().unwrap().to_string()) +// .collect(); +// let has_orphaned: bool = field_names.iter().any(|f| f == "orphaned"); + +// for field in named.iter_mut() { +// let ident = field.ident.as_ref().unwrap(); +// let ftype = field.ty.clone(); + +// // this is my quick and dirty attempt at emulating: +// // https://github.com/PyO3/pyo3/blob/48690525e19b87818b59f99be83f1e0eb203c7d4/pyo3-macros-backend/src/pyclass.rs#L220 + +// let mut opts = FieldOptions::default(); +// let keep: Vec = field +// .attrs +// .iter() +// .map(|x| match x.path.segments[0].ident.to_string().as_str() { +// "api" => { +// let meta = x.parse_meta().unwrap(); +// if let Meta::List(list) = meta { +// for nested in list.nested { +// if let syn::NestedMeta::Meta(opt) = nested { +// // println!("opt_path{:?}", opt.path().segments[0].ident.to_string().as_str());; +// let opt_name = opt.path().segments[0].ident.to_string(); +// match opt_name.as_str() { +// "skip_get" => opts.skip_get = true, +// "skip_set" => opts.skip_set = true, +// "has_orphaned" => opts.field_has_orphaned = true, +// // todo: figure out how to use span to have rust-analyzer highlight the exact code +// // where this gets messed up +// _ => { +// abort!( +// x.span(), +// format!( +// "Invalid api option: {}.\nValid options are: `skip_get`, `skip_set`, and `has_orphaned`.", +// opt_name +// ) +// ) +// } +// } +// } +// } +// } +// false +// } +// _ => true, +// }) +// .collect(); +// // println!("options {:?}", opts); +// let mut iter = keep.iter(); +// // this drops attrs with api, removing the field attribute from the struct def +// field.attrs.retain(|_| *iter.next().unwrap()); + +// if let syn::Type::Path(type_path) = ftype.clone() { +// // println!( +// // "{:?}", +// // ident.to_string().as_str(), +// // type_path.clone().into_token_stream().to_string().as_str(), +// // // attr_vec.clone().into_iter().collect::>() +// // ); +// impl_getters_and_setters( +// type_path, +// &mut py_impl_block, +// ident, +// opts, +// has_orphaned, +// ftype, +// ); +// } +// } + +// if !is_state_or_history { +// py_impl_block.extend::(quote! { +// #[pyo3(name = "to_file")] +// pub fn to_file_py(&self, filename: &str) -> PyResult<()> { +// Ok(self.to_file(filename)?) +// } + +// #[classmethod] +// #[pyo3(name = "from_file")] +// pub fn from_file_py(_cls: &PyType, json_str:String) -> PyResult { +// // unwrap is ok here because it makes sense to stop execution if a file is not loadable +// Ok(Self::from_file(&json_str)?) +// } +// }); +// } +// } else if let syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) = &mut ast.fields { +// // tuple struct +// if ast.ident.to_string().contains("Vec") || ast.ident.to_string().contains("Array") { +// assert!(unnamed.len() == 1); +// for field in unnamed.iter() { +// let ftype = field.ty.clone(); +// if let syn::Type::Path(type_path) = ftype.clone() { +// let type_str = type_path.clone().into_token_stream().to_string(); +// let (re, container, py_new_body, tolist_body) = if type_str.contains("Vec") { +// ( +// Regex::new(r"Vec < (.+) >").unwrap(), +// "Vec".parse::().unwrap(), +// "Self(v)".parse::().unwrap(), +// "self.0.clone()".parse::().unwrap(), +// ) +// } else if type_str.contains("Array1") { +// ( +// Regex::new(r"Array1 < (.+) >").unwrap(), +// "Array1".parse::().unwrap(), +// "Self(Array1::from_vec(v))".parse::().unwrap(), +// "self.0.to_vec()".parse::().unwrap(), +// ) +// } else { +// abort!( +// ftype.span(), +// "Invalid container type. Must be Array1 or Vec." +// ) +// // replace with proc_macro_error::abort macro +// }; + +// // println!("{}", type_str); +// // println!("{}", &re.captures(&type_str).unwrap()[1]); +// let contained_dtype: TokenStream2 = re.captures(&type_str).unwrap()[1] +// .to_string() +// .parse() +// .unwrap(); +// py_impl_block.extend::(quote! { +// #[new] +// pub fn __new__(v: Vec<#contained_dtype>) -> Self { +// #py_new_body +// } + +// pub fn __repr__(&self) -> String { +// format!("RustArray({:?})", self.0) +// } +// pub fn __str__(&self) -> String { +// format!("{:?}", self.0) +// } +// pub fn __getitem__(&self, idx: i32) -> PyResult<#contained_dtype> { +// if idx >= self.0.len() as i32 { +// Err(PyIndexError::new_err("Index is out of bounds")) +// } else if idx >= 0 { +// Ok(self.0[idx as usize]) +// } else { +// Ok(self.0[self.0.len() + idx as usize]) +// } +// } +// pub fn __setitem__(&mut self, _idx: usize, _new_value: #contained_dtype +// ) -> PyResult<()> { +// Err(PyNotImplementedError::new_err( +// "Setting value at index is not implemented. +// Run `tolist` method, modify value at index, and +// then set entire vector.", +// )) +// } +// pub fn tolist(&self) -> PyResult> { +// Ok(#tolist_body) +// } +// pub fn __list__(&self) -> PyResult> { +// Ok(#tolist_body) +// } +// pub fn __len__(&self) -> usize { +// self.0.len() +// } +// pub fn is_empty(&self) -> bool { +// self.0.is_empty() +// } +// }); +// impl_block.extend::(quote! { +// pub fn new(value: #container<#contained_dtype>) -> Self { +// Self(value) +// } +// }); +// } +// } +// } else { +// abort_call_site!( +// "`add_pyo3_api` works only on tuple structs with `Vec` or `Array` in the name" +// ); +// } +// } else { +// abort_call_site!("`add_pyo3_api` works only on named and tuple structs."); +// }; + +// // py_impl_block.extend::(quote! { +// // #[classmethod] +// // #[pyo3(name = "default")] +// // pub fn default_py(_cls: &PyType) -> PyResult { +// // Ok(Self::default()) +// // } +// // }); + +// py_impl_block.extend::(quote! { +// pub fn copy(&self) -> Self {self.clone()} +// pub fn __copy__(&self) -> Self {self.clone()} +// pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} + +// /// json serialization method. +// #[pyo3(name = "to_json")] +// pub fn to_json_py(&self) -> PyResult { +// Ok(self.to_json()) +// } + +// #[classmethod] +// /// json deserialization method. +// #[pyo3(name = "from_json")] +// pub fn from_json_py(_cls: &PyType, json_str: &str) -> PyResult { +// Ok(Self::from_json(json_str)?) +// } + +// /// yaml serialization method. +// #[pyo3(name = "to_yaml")] +// pub fn to_yaml_py(&self) -> PyResult { +// Ok(self.to_yaml()) +// } + +// #[classmethod] +// /// yaml deserialization method. +// #[pyo3(name = "from_yaml")] +// pub fn from_yaml_py(_cls: &PyType, yaml_str: &str) -> PyResult { +// Ok(Self::from_yaml(yaml_str)?) +// } + +// /// bincode serialization method. +// #[pyo3(name = "to_bincode")] +// pub fn to_bincode_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyBytes> { +// Ok(PyBytes::new(py, &self.to_bincode())) +// } + +// #[classmethod] +// /// bincode deserialization method. +// #[pyo3(name = "from_bincode")] +// pub fn from_bincode_py(_cls: &PyType, encoded: &PyBytes) -> PyResult { +// Ok(Self::from_bincode(encoded.as_bytes())?) +// } + +// }); + +// let impl_block = quote! { +// impl #ident { +// #impl_block +// } + +// #[pymethods] +// #[cfg(feature="pyo3")] +// impl #ident { +// #py_impl_block +// } +// }; + +// let mut final_output = TokenStream2::default(); +// // add pyclass attribute +// final_output.extend::(quote! { +// #[cfg_attr(feature="pyo3", pyclass(module = "fastsimrust", subclass))] +// }); +// let mut output: TokenStream2 = ast.to_token_stream(); +// output.extend(impl_block); +// // if ast.ident.to_string() == "RustSimDrive" { +// // println!("{}", output.to_string()); +// // } +// // println!("{}", output.to_string()); +// final_output.extend::(output); +// final_output.into() +// } diff --git a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs index 14a8cbc7..b9f65a8c 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs @@ -1,10 +1,17 @@ +// modules mod imports; -use crate::imports::*; +// modules - macros mod add_pyo3_api; mod approx_eq_derive; +mod doc_field; mod history_vec_derive; + +// modules - other mod utilities; +// imports +use crate::imports::*; + /// macro for creating appropriate setters and getters for pyo3 struct attributes #[proc_macro_error] #[proc_macro_attribute] @@ -12,6 +19,12 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { add_pyo3_api::add_pyo3_api(attr, item) } +// #[proc_macro_error] +// #[proc_macro_attribute] +// pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { +// doc_field::doc_field(attr, item) +// } + #[proc_macro_derive(HistoryVec)] pub fn history_vec_derive(input: TokenStream) -> TokenStream { history_vec_derive::history_vec_derive(input) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/utilities.rs b/rust/fastsim-core/fastsim-proc-macros/src/utilities.rs index 914d551a..8018455d 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/utilities.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/utilities.rs @@ -25,216 +25,9 @@ impl> TokenStreamIterator for T { } } -macro_rules! impl_vec_get_set { - ($opts: ident, $fident: ident, $impl_block: ident, $contained_type: ty, $wrapper_type: expr, $has_orphaned: expr) => { - if !$opts.skip_get { - let get_name: TokenStream2 = format!("get_{}", $fident).parse().unwrap(); - $impl_block.extend::(quote! { - #[getter] - pub fn #get_name(&self) -> PyResult<$wrapper_type> { - Ok($wrapper_type::new(self.#$fident.clone())) - } - }); - } - if !$opts.skip_set { - let set_name: TokenStream2 = format!("set_{}", $fident).parse().unwrap(); - match stringify!($wrapper_type) { - "Pyo3VecF64" => { - if $has_orphaned { - $impl_block.extend(quote! { - #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { - if !self.orphaned { - self.#$fident = new_value; - Ok(()) - } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) - } - } - }) - } else { - $impl_block.extend(quote! { - #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { - self.#$fident = new_value; - Ok(()) - } - }) - } - } - _ => { - if $has_orphaned { - $impl_block.extend(quote! { - #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { - if !self.orphaned { - self.#$fident = Array1::from_vec(new_value); - Ok(()) - } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) - } - } - }) - } else { - $impl_block.extend(quote! { - #[setter] - pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> { - self.#$fident = Array1::from_vec(new_value); - Ok(()) - } - }) - } - } - } - - } - }; -} - -/// Generates pyo3 getter methods -/// -/// general match arguments: -/// - type: type of variable (e.g. `f64`) -/// - field: struct field -/// - impl_block: TokenStream2 -/// - opts: FieldOptions struct instance -macro_rules! impl_get_body { - ( - $type: ident, $field: ident, $impl_block: ident, $opts: ident - ) => { - if !$opts.skip_get { - let get_name: TokenStream2 = format!("get_{}", $field).parse().unwrap(); - let get_block = if $opts.field_has_orphaned { - quote! { - #[getter] - pub fn #get_name(&mut self) -> PyResult<#$type> { - self.#$field.orphaned = true; - Ok(self.#$field.clone()) - } - } - } else { - quote! { - #[getter] - pub fn #get_name(&self) -> PyResult<#$type> { - Ok(self.#$field.clone()) - } - } - }; - $impl_block.extend::(get_block) - } - }; -} - -/// Generates pyo3 setter methods -/// -/// general match arguments: -/// - type: type of variable (e.g. `f64`) -/// - field: struct field -/// - impl_block: TokenStream2 -/// - has_orphaned: bool, true if struct has `orphaned` field -/// - opts: FieldOptions struct instance - -macro_rules! impl_set_body { - ( // for generic - $type: ident, $field: ident, $impl_block: ident, $has_orphaned: expr, $opts: ident - ) => { - if !$opts.skip_set { - let set_name: TokenStream2 = format!("set_{}", $field).parse().unwrap(); - let orphaned_set_block = if $has_orphaned && $opts.field_has_orphaned { - quote! { - if !self.orphaned { - self.#$field = new_value; - self.#$field.orphaned = false; - Ok(()) - } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) - } - } - } else if $has_orphaned { - quote! { - if !self.orphaned { - self.#$field = new_value; - Ok(()) - } else { - Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR)) - } - } - } else { - quote! { - self.#$field = new_value; - Ok(()) - } - }; - - $impl_block.extend::(quote! { - #[setter] - pub fn #set_name(&mut self, new_value: #$type) -> PyResult<()> { - #orphaned_set_block - } - }); - } - }; -} - -#[derive(Debug, Default, Clone)] -pub struct FieldOptions { - /// if true, getters are not generated for a field - pub skip_get: bool, - /// if true, setters are not generated for a field - pub skip_set: bool, - /// if true, current field is itself a struct with `orphaned` field - pub field_has_orphaned: bool, -} - -pub fn impl_getters_and_setters( - type_path: syn::TypePath, - impl_block: &mut TokenStream2, - ident: &proc_macro2::Ident, - opts: FieldOptions, - has_orphaned: bool, - ftype: syn::Type, -) { - let type_str = type_path.into_token_stream().to_string(); - match type_str.as_str() { - "Array1 < f64 >" => { - impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3ArrayF64, has_orphaned); - } - "Array1 < u32 >" => { - impl_vec_get_set!(opts, ident, impl_block, u32, Pyo3ArrayU32, has_orphaned); - } - "Array1 < i32 >" => { - impl_vec_get_set!(opts, ident, impl_block, i32, Pyo3ArrayI32, has_orphaned); - } - "Array1 < bool >" => { - impl_vec_get_set!(opts, ident, impl_block, bool, Pyo3ArrayBool, has_orphaned); - } - "Vec < f64 >" => { - impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3VecF64, has_orphaned); - } - _ => match ident.to_string().as_str() { - "orphaned" => { - impl_block.extend::(quote! { - #[getter] - pub fn get_orphaned(&self) -> PyResult { - Ok(self.orphaned) - } - /// Reset the orphaned flag to false. - pub fn reset_orphaned(&mut self) -> PyResult<()> { - self.orphaned = false; - Ok(()) - } - }) - } - _ => { - impl_get_body!(ftype, ident, impl_block, opts); - impl_set_body!(ftype, ident, impl_block, has_orphaned, opts); - } - }, - } -} - const ONLY_FN_MSG: &str = "Only function definitions allowed here."; +/// Read name as "parse tokenstream as function definitions". /// accepts `attr` TokenStream from attribute-like proc macro and returns /// TokenStream2 of fn defs that are in `expected_fn_names` and/or not in `forbidden_fn_names`. /// If `expected_exlusive` is true, only values in `expected_fn_names` are allowed. From 2e73e3236eb5c0992651d966ec12d3dc3895e321 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 27 Jul 2023 12:35:40 -0600 Subject: [PATCH 02/18] started on doc_field --- .../fastsim-proc-macros/src/doc_field.rs | 357 ++++-------------- .../fastsim-proc-macros/src/lib.rs | 10 +- 2 files changed, 87 insertions(+), 280 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index 223be97b..255a51e2 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -1,275 +1,82 @@ -// use crate::imports::*; -// use crate::utilities::*; - -// pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { -// let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); -// // println!("{}", ast.ident.to_string()); -// let ident = &ast.ident; - -// // add field `doc: Option` - -// let is_state_or_history: bool = -// ident.to_string().contains("State") || ident.to_string().contains("HistoryVec"); - -// if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { -// let field_names: Vec = named -// .iter() -// .map(|f| f.ident.as_ref().unwrap().to_string()) -// .collect(); -// let has_orphaned: bool = field_names.iter().any(|f| f == "orphaned"); - -// for field in named.iter_mut() { -// let ident = field.ident.as_ref().unwrap(); -// let ftype = field.ty.clone(); - -// // this is my quick and dirty attempt at emulating: -// // https://github.com/PyO3/pyo3/blob/48690525e19b87818b59f99be83f1e0eb203c7d4/pyo3-macros-backend/src/pyclass.rs#L220 - -// let mut opts = FieldOptions::default(); -// let keep: Vec = field -// .attrs -// .iter() -// .map(|x| match x.path.segments[0].ident.to_string().as_str() { -// "api" => { -// let meta = x.parse_meta().unwrap(); -// if let Meta::List(list) = meta { -// for nested in list.nested { -// if let syn::NestedMeta::Meta(opt) = nested { -// // println!("opt_path{:?}", opt.path().segments[0].ident.to_string().as_str());; -// let opt_name = opt.path().segments[0].ident.to_string(); -// match opt_name.as_str() { -// "skip_get" => opts.skip_get = true, -// "skip_set" => opts.skip_set = true, -// "has_orphaned" => opts.field_has_orphaned = true, -// // todo: figure out how to use span to have rust-analyzer highlight the exact code -// // where this gets messed up -// _ => { -// abort!( -// x.span(), -// format!( -// "Invalid api option: {}.\nValid options are: `skip_get`, `skip_set`, and `has_orphaned`.", -// opt_name -// ) -// ) -// } -// } -// } -// } -// } -// false -// } -// _ => true, -// }) -// .collect(); -// // println!("options {:?}", opts); -// let mut iter = keep.iter(); -// // this drops attrs with api, removing the field attribute from the struct def -// field.attrs.retain(|_| *iter.next().unwrap()); - -// if let syn::Type::Path(type_path) = ftype.clone() { -// // println!( -// // "{:?}", -// // ident.to_string().as_str(), -// // type_path.clone().into_token_stream().to_string().as_str(), -// // // attr_vec.clone().into_iter().collect::>() -// // ); -// impl_getters_and_setters( -// type_path, -// &mut py_impl_block, -// ident, -// opts, -// has_orphaned, -// ftype, -// ); -// } -// } - -// if !is_state_or_history { -// py_impl_block.extend::(quote! { -// #[pyo3(name = "to_file")] -// pub fn to_file_py(&self, filename: &str) -> PyResult<()> { -// Ok(self.to_file(filename)?) -// } - -// #[classmethod] -// #[pyo3(name = "from_file")] -// pub fn from_file_py(_cls: &PyType, json_str:String) -> PyResult { -// // unwrap is ok here because it makes sense to stop execution if a file is not loadable -// Ok(Self::from_file(&json_str)?) -// } -// }); -// } -// } else if let syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) = &mut ast.fields { -// // tuple struct -// if ast.ident.to_string().contains("Vec") || ast.ident.to_string().contains("Array") { -// assert!(unnamed.len() == 1); -// for field in unnamed.iter() { -// let ftype = field.ty.clone(); -// if let syn::Type::Path(type_path) = ftype.clone() { -// let type_str = type_path.clone().into_token_stream().to_string(); -// let (re, container, py_new_body, tolist_body) = if type_str.contains("Vec") { -// ( -// Regex::new(r"Vec < (.+) >").unwrap(), -// "Vec".parse::().unwrap(), -// "Self(v)".parse::().unwrap(), -// "self.0.clone()".parse::().unwrap(), -// ) -// } else if type_str.contains("Array1") { -// ( -// Regex::new(r"Array1 < (.+) >").unwrap(), -// "Array1".parse::().unwrap(), -// "Self(Array1::from_vec(v))".parse::().unwrap(), -// "self.0.to_vec()".parse::().unwrap(), -// ) -// } else { -// abort!( -// ftype.span(), -// "Invalid container type. Must be Array1 or Vec." -// ) -// // replace with proc_macro_error::abort macro -// }; - -// // println!("{}", type_str); -// // println!("{}", &re.captures(&type_str).unwrap()[1]); -// let contained_dtype: TokenStream2 = re.captures(&type_str).unwrap()[1] -// .to_string() -// .parse() -// .unwrap(); -// py_impl_block.extend::(quote! { -// #[new] -// pub fn __new__(v: Vec<#contained_dtype>) -> Self { -// #py_new_body -// } - -// pub fn __repr__(&self) -> String { -// format!("RustArray({:?})", self.0) -// } -// pub fn __str__(&self) -> String { -// format!("{:?}", self.0) -// } -// pub fn __getitem__(&self, idx: i32) -> PyResult<#contained_dtype> { -// if idx >= self.0.len() as i32 { -// Err(PyIndexError::new_err("Index is out of bounds")) -// } else if idx >= 0 { -// Ok(self.0[idx as usize]) -// } else { -// Ok(self.0[self.0.len() + idx as usize]) -// } -// } -// pub fn __setitem__(&mut self, _idx: usize, _new_value: #contained_dtype -// ) -> PyResult<()> { -// Err(PyNotImplementedError::new_err( -// "Setting value at index is not implemented. -// Run `tolist` method, modify value at index, and -// then set entire vector.", -// )) -// } -// pub fn tolist(&self) -> PyResult> { -// Ok(#tolist_body) -// } -// pub fn __list__(&self) -> PyResult> { -// Ok(#tolist_body) -// } -// pub fn __len__(&self) -> usize { -// self.0.len() -// } -// pub fn is_empty(&self) -> bool { -// self.0.is_empty() -// } -// }); -// impl_block.extend::(quote! { -// pub fn new(value: #container<#contained_dtype>) -> Self { -// Self(value) -// } -// }); -// } -// } -// } else { -// abort_call_site!( -// "`add_pyo3_api` works only on tuple structs with `Vec` or `Array` in the name" -// ); -// } -// } else { -// abort_call_site!("`add_pyo3_api` works only on named and tuple structs."); -// }; - -// // py_impl_block.extend::(quote! { -// // #[classmethod] -// // #[pyo3(name = "default")] -// // pub fn default_py(_cls: &PyType) -> PyResult { -// // Ok(Self::default()) -// // } -// // }); - -// py_impl_block.extend::(quote! { -// pub fn copy(&self) -> Self {self.clone()} -// pub fn __copy__(&self) -> Self {self.clone()} -// pub fn __deepcopy__(&self, _memo: &PyDict) -> Self {self.clone()} - -// /// json serialization method. -// #[pyo3(name = "to_json")] -// pub fn to_json_py(&self) -> PyResult { -// Ok(self.to_json()) -// } - -// #[classmethod] -// /// json deserialization method. -// #[pyo3(name = "from_json")] -// pub fn from_json_py(_cls: &PyType, json_str: &str) -> PyResult { -// Ok(Self::from_json(json_str)?) -// } - -// /// yaml serialization method. -// #[pyo3(name = "to_yaml")] -// pub fn to_yaml_py(&self) -> PyResult { -// Ok(self.to_yaml()) -// } - -// #[classmethod] -// /// yaml deserialization method. -// #[pyo3(name = "from_yaml")] -// pub fn from_yaml_py(_cls: &PyType, yaml_str: &str) -> PyResult { -// Ok(Self::from_yaml(yaml_str)?) -// } - -// /// bincode serialization method. -// #[pyo3(name = "to_bincode")] -// pub fn to_bincode_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyBytes> { -// Ok(PyBytes::new(py, &self.to_bincode())) -// } - -// #[classmethod] -// /// bincode deserialization method. -// #[pyo3(name = "from_bincode")] -// pub fn from_bincode_py(_cls: &PyType, encoded: &PyBytes) -> PyResult { -// Ok(Self::from_bincode(encoded.as_bytes())?) -// } - -// }); - -// let impl_block = quote! { -// impl #ident { -// #impl_block -// } - -// #[pymethods] -// #[cfg(feature="pyo3")] -// impl #ident { -// #py_impl_block -// } -// }; - -// let mut final_output = TokenStream2::default(); -// // add pyclass attribute -// final_output.extend::(quote! { -// #[cfg_attr(feature="pyo3", pyclass(module = "fastsimrust", subclass))] -// }); -// let mut output: TokenStream2 = ast.to_token_stream(); -// output.extend(impl_block); -// // if ast.ident.to_string() == "RustSimDrive" { -// // println!("{}", output.to_string()); -// // } -// // println!("{}", output.to_string()); -// final_output.extend::(output); -// final_output.into() -// } +use crate::imports::*; +use crate::utilities::*; + +pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); + // println!("{}", ast.ident.to_string()); + let ident = &ast.ident; + + // add field `doc: Option` + + let is_state_or_history: bool = + ident.to_string().contains("State") || ident.to_string().contains("HistoryVec"); + + if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { + let field_names: Vec = named + .iter() + .map(|f| f.ident.as_ref().unwrap().to_string()) + .collect(); + let has_orphaned: bool = field_names.iter().any(|f| f == "orphaned"); + + for field in named.iter_mut() { + let ident = field.ident.as_ref().unwrap(); + + let mut skip_doc = false; + let fields_to_doc: Vec = field + .attrs + .iter() + .map(|x| match x.path.segments[0].ident.to_string().as_str() { + "doc" => { + let meta = x.parse_meta().unwrap(); + if let Meta::List(list) = meta { + for nested in list.nested { + if let syn::NestedMeta::Meta(opt) = nested { + // println!("opt_path{:?}", opt.path().segments[0].ident.to_string().as_str());; + let opt_name = opt.path().segments[0].ident.to_string(); + match opt_name.as_str() { + "skip_doc" => skip_doc = true, + _ => { + abort!( + x.span(), + format!( + "Invalid doc option: {}.\nValid option is: `skip_doc`.", + opt_name + ) + ) + } + } + } + } + } + false + } + _ => true, + }) + .collect(); + // println!("options {:?}", skip_doc); + let mut iter = fields_to_doc.iter(); + // this drops attrs with api, removing the field attribute from the struct def + field.attrs.retain(|_| *iter.next().unwrap()); + } + + if !is_state_or_history { + todo!() + } + } else { + abort_call_site!("`doc_field` proc macro works only on named structs."); + }; + + let mut final_output = TokenStream2::default(); + // add pyclass attribute + final_output.extend::(quote! { + #[cfg_attr(feature="pyo3", pyclass(module = "fastsimrust", subclass))] + }); + let mut output: TokenStream2 = ast.to_token_stream(); + output.extend(impl_block); + // if ast.ident.to_string() == "RustSimDrive" { + // println!("{}", output.to_string()); + // } + // println!("{}", output.to_string()); + final_output.extend::(output); + final_output.into() +} diff --git a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs index b9f65a8c..68831d28 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs @@ -19,11 +19,11 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { add_pyo3_api::add_pyo3_api(attr, item) } -// #[proc_macro_error] -// #[proc_macro_attribute] -// pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { -// doc_field::doc_field(attr, item) -// } +#[proc_macro_error] +#[proc_macro_attribute] +pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { + doc_field::doc_field(attr, item) +} #[proc_macro_derive(HistoryVec)] pub fn history_vec_derive(input: TokenStream) -> TokenStream { From bcfa495a0913ebffb37f98eed873369fc9574836 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 27 Jul 2023 12:41:56 -0600 Subject: [PATCH 03/18] added fuzzy version requirement --- rust/fastsim-cli/Cargo.toml | 2 +- rust/fastsim-py/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/fastsim-cli/Cargo.toml b/rust/fastsim-cli/Cargo.toml index 0f01005b..daedc832 100644 --- a/rust/fastsim-cli/Cargo.toml +++ b/rust/fastsim-cli/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/NREL/fastsim" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fastsim-core = { path = "../fastsim-core" } +fastsim-core = { path = "../fastsim-core", version = "~0" } serde = { workspace = true } serde_json = { workspace = true } project-root = "0.2.2" diff --git a/rust/fastsim-py/Cargo.toml b/rust/fastsim-py/Cargo.toml index 3c96220e..b7605acb 100644 --- a/rust/fastsim-py/Cargo.toml +++ b/rust/fastsim-py/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/NREL/fastsim" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fastsim-core = { path = "../fastsim-core", features = ["pyo3"] } +fastsim-core = { path = "../fastsim-core", features = ["pyo3"], version="~0" } pyo3 = { workspace = true, features = ["extension-module", "anyhow"] } pyo3-log = { workspace = true } log = "0.4.17" From b78a575c6eb01d8c4099bfd69770284fcda4042b Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 1 Aug 2023 11:30:09 -0600 Subject: [PATCH 04/18] no errors but needs to be applied --- .../src/add_pyo3_api/mod.rs | 1 + .../fastsim-proc-macros/src/doc_field.rs | 122 ++++++++++-------- 2 files changed, 67 insertions(+), 56 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index cf54d8f1..e46347ef 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -39,6 +39,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { // https://github.com/PyO3/pyo3/blob/48690525e19b87818b59f99be83f1e0eb203c7d4/pyo3-macros-backend/src/pyclass.rs#L220 let mut opts = FieldOptions::default(); + // attributes to retain, i.e. attributes that are not handled by this macro let keep: Vec = field .attrs .iter() diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index 255a51e2..eba882c1 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -1,16 +1,11 @@ use crate::imports::*; -use crate::utilities::*; pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); // println!("{}", ast.ident.to_string()); - let ident = &ast.ident; // add field `doc: Option` - let is_state_or_history: bool = - ident.to_string().contains("State") || ident.to_string().contains("HistoryVec"); - if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { let field_names: Vec = named .iter() @@ -18,65 +13,80 @@ pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { .collect(); let has_orphaned: bool = field_names.iter().any(|f| f == "orphaned"); - for field in named.iter_mut() { - let ident = field.ident.as_ref().unwrap(); + let mut new_doc_fields: Vec = Vec::new(); + for field in named.iter_mut() { let mut skip_doc = false; - let fields_to_doc: Vec = field - .attrs - .iter() - .map(|x| match x.path.segments[0].ident.to_string().as_str() { - "doc" => { - let meta = x.parse_meta().unwrap(); - if let Meta::List(list) = meta { - for nested in list.nested { - if let syn::NestedMeta::Meta(opt) = nested { - // println!("opt_path{:?}", opt.path().segments[0].ident.to_string().as_str());; - let opt_name = opt.path().segments[0].ident.to_string(); - match opt_name.as_str() { - "skip_doc" => skip_doc = true, - _ => { - abort!( - x.span(), - format!( - "Invalid doc option: {}.\nValid option is: `skip_doc`.", - opt_name - ) - ) - } - } - } - } - } - false - } - _ => true, - }) - .collect(); - // println!("options {:?}", skip_doc); - let mut iter = fields_to_doc.iter(); + let keep = get_attrs_to_keep(field, &mut skip_doc); + // println!("options {:?}", opts); + let mut iter = keep.iter(); // this drops attrs with api, removing the field attribute from the struct def field.attrs.retain(|_| *iter.next().unwrap()); + if !skip_doc { + // create new doc field as string + let new_doc_field = format!("{}_doc", field.ident.as_ref().unwrap().to_string()); + // convert it to named field + let mut new_doc_field = syn::parse_str::(&new_doc_field) + .or_else(|e| abort_call_site!(e)) + .unwrap(); + // give it a type + for named in new_doc_field.named.iter_mut() { + named.ty = syn::parse_str::("Option") + .or_else(|_| abort_call_site!()) + .unwrap(); + // TODO: figure out how to add a doc string here + } + new_doc_fields.push( + new_doc_field + .named + .first() + .or_else(|| abort_call_site!()) + .unwrap() + .clone(), + ); + } } - if !is_state_or_history { - todo!() - } + named.extend(new_doc_fields); } else { abort_call_site!("`doc_field` proc macro works only on named structs."); }; - let mut final_output = TokenStream2::default(); - // add pyclass attribute - final_output.extend::(quote! { - #[cfg_attr(feature="pyo3", pyclass(module = "fastsimrust", subclass))] - }); - let mut output: TokenStream2 = ast.to_token_stream(); - output.extend(impl_block); - // if ast.ident.to_string() == "RustSimDrive" { - // println!("{}", output.to_string()); - // } - // println!("{}", output.to_string()); - final_output.extend::(output); - final_output.into() + ast.into_token_stream().into() +} + +/// Returns attributes to retain, i.e. attributes that are not handled by the [doc_field] macro +fn get_attrs_to_keep(field: &mut syn::Field, skip_doc: &mut bool) -> Vec { + let keep: Vec = field + .attrs + .iter() + .map(|x| match x.path.segments[0].ident.to_string().as_str() { + "doc" => { + let meta = x.parse_meta().unwrap(); + if let Meta::List(list) = meta { + for nested in list.nested { + if let syn::NestedMeta::Meta(opt) = nested { + // println!("opt_path{:?}", opt.path().segments[0].ident.to_string().as_str());; + let opt_name = opt.path().segments[0].ident.to_string(); + match opt_name.as_str() { + "skip_doc" => *skip_doc = true, + _ => { + abort!( + x.span(), + format!( + "Invalid doc option: {}.\nValid option is: `skip_doc`.", + opt_name + ) + ) + } + } + } + } + } + false + } + _ => true, + }) + .collect(); + keep } From fd0bbdd4c6079a093ed1ec29260014c6f4edb71d Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Tue, 1 Aug 2023 11:30:18 -0600 Subject: [PATCH 05/18] made clippy happy --- rust/fastsim-core/src/vehicle.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 21b6c8c7..d296c79c 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -667,18 +667,12 @@ impl RustVehicle { // self.charging_on = false; // # Checking if a vehicle has any hybrid components - if (self.ess_max_kwh == 0.0) || (self.ess_max_kw == 0.0) || (self.mc_max_kw == 0.0) { - self.no_elec_sys = true; - } else { - self.no_elec_sys = false; - } + self.no_elec_sys = + (self.ess_max_kwh == 0.0) || (self.ess_max_kw == 0.0) || (self.mc_max_kw == 0.0); // # Checking if aux loads go through an alternator - if self.no_elec_sys || (self.mc_max_kw <= self.aux_kw) || self.force_aux_on_fc { - self.no_elec_aux = true; - } else { - self.no_elec_aux = false; - } + self.no_elec_aux = + self.no_elec_sys || (self.mc_max_kw <= self.aux_kw) || self.force_aux_on_fc; self.fc_perc_out_array = FC_PERC_OUT_ARRAY.clone().to_vec(); From 26d75068c1e7044f57e89d87dfd2d55b8bdded5e Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 2 Aug 2023 15:35:56 -0600 Subject: [PATCH 06/18] got `doc_field` working needs to have `skip_deserializing_if` for None cases --- rust/fastsim-core/Cargo.toml | 2 +- .../src/add_pyo3_api/pyo3_api_utils.rs | 49 ----------------- .../fastsim-proc-macros/src/doc_field.rs | 53 +++++++------------ rust/fastsim-core/src/vehicle.rs | 11 +++- 4 files changed, 30 insertions(+), 85 deletions(-) diff --git a/rust/fastsim-core/Cargo.toml b/rust/fastsim-core/Cargo.toml index a55c3147..4386b2b4 100644 --- a/rust/fastsim-core/Cargo.toml +++ b/rust/fastsim-core/Cargo.toml @@ -10,7 +10,7 @@ readme = "../../README.md" repository = "https://github.com/NREL/fastsim" [dependencies] -proc-macros = { package = "fastsim-proc-macros", version = "~0" } +proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" } pyo3 = { workspace = true, features = [ "extension-module", "anyhow", diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs index 465fcaa0..e412b3cd 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/pyo3_api_utils.rs @@ -1,5 +1,3 @@ -use super::*; - macro_rules! impl_vec_get_set { ($opts: ident, $fident: ident, $impl_block: ident, $contained_type: ty, $wrapper_type: expr, $has_orphaned: expr) => { if !$opts.skip_get { @@ -160,50 +158,3 @@ pub struct FieldOptions { /// if true, current field is itself a struct with `orphaned` field pub field_has_orphaned: bool, } - -pub fn impl_getters_and_setters( - type_path: syn::TypePath, - impl_block: &mut TokenStream2, - ident: &proc_macro2::Ident, - opts: FieldOptions, - has_orphaned: bool, - ftype: syn::Type, -) { - let type_str = type_path.into_token_stream().to_string(); - match type_str.as_str() { - "Array1 < f64 >" => { - impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3ArrayF64, has_orphaned); - } - "Array1 < u32 >" => { - impl_vec_get_set!(opts, ident, impl_block, u32, Pyo3ArrayU32, has_orphaned); - } - "Array1 < i32 >" => { - impl_vec_get_set!(opts, ident, impl_block, i32, Pyo3ArrayI32, has_orphaned); - } - "Array1 < bool >" => { - impl_vec_get_set!(opts, ident, impl_block, bool, Pyo3ArrayBool, has_orphaned); - } - "Vec < f64 >" => { - impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3VecF64, has_orphaned); - } - _ => match ident.to_string().as_str() { - "orphaned" => { - impl_block.extend::(quote! { - #[getter] - pub fn get_orphaned(&self) -> PyResult { - Ok(self.orphaned) - } - /// Reset the orphaned flag to false. - pub fn reset_orphaned(&mut self) -> PyResult<()> { - self.orphaned = false; - Ok(()) - } - }) - } - _ => { - impl_get_body!(ftype, ident, impl_block, opts); - impl_set_body!(ftype, ident, impl_block, has_orphaned, opts); - } - }, - } -} diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index eba882c1..fe0f7452 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -1,19 +1,11 @@ use crate::imports::*; -pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { +/// +pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); - // println!("{}", ast.ident.to_string()); - - // add field `doc: Option` if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { - let field_names: Vec = named - .iter() - .map(|f| f.ident.as_ref().unwrap().to_string()) - .collect(); - let has_orphaned: bool = field_names.iter().any(|f| f == "orphaned"); - - let mut new_doc_fields: Vec = Vec::new(); + let mut new_doc_fields = String::from("{\n"); for field in named.iter_mut() { let mut skip_doc = false; @@ -24,30 +16,25 @@ pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { field.attrs.retain(|_| *iter.next().unwrap()); if !skip_doc { // create new doc field as string - let new_doc_field = format!("{}_doc", field.ident.as_ref().unwrap().to_string()); - // convert it to named field - let mut new_doc_field = syn::parse_str::(&new_doc_field) - .or_else(|e| abort_call_site!(e)) - .unwrap(); - // give it a type - for named in new_doc_field.named.iter_mut() { - named.ty = syn::parse_str::("Option") - .or_else(|_| abort_call_site!()) - .unwrap(); - // TODO: figure out how to add a doc string here - } - new_doc_fields.push( - new_doc_field - .named - .first() - .or_else(|| abort_call_site!()) - .unwrap() - .clone(), - ); + new_doc_fields.push_str(&format!( + "/// {} documentation -- e.g. info about calibration/validation of vehicle, + /// links to reports or other long-form documentation. + pub {}_doc: Option,\n", + field.ident.as_ref().unwrap(), + field.ident.as_ref().unwrap() + )); } } - named.extend(new_doc_fields); + new_doc_fields.push_str( + "/// Vehicle level documentation -- e.g. info about calibration/validation of this parameter, + /// links to reports or other long-form documentation. + doc: Option\n}", + ); + let new_doc_fields: syn::FieldsNamed = syn::parse_str::(&new_doc_fields) + .unwrap_or_else(|e| abort_call_site!("{}", e)); + + named.extend(new_doc_fields.named); } else { abort_call_site!("`doc_field` proc macro works only on named structs."); }; @@ -61,7 +48,7 @@ fn get_attrs_to_keep(field: &mut syn::Field, skip_doc: &mut bool) -> Vec { .attrs .iter() .map(|x| match x.path.segments[0].ident.to_string().as_str() { - "doc" => { + "doc_field" => { let meta = x.parse_meta().unwrap(); if let Meta::List(list) = meta { for nested in list.nested { diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index d296c79c..8fcb29c4 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -3,7 +3,7 @@ // local use crate::imports::*; use crate::params::*; -use crate::proc_macros::{add_pyo3_api, ApproxEq}; +use crate::proc_macros::{add_pyo3_api, doc_field, ApproxEq}; #[cfg(feature = "pyo3")] use crate::pyo3imports::*; @@ -33,7 +33,6 @@ lazy_static! { Regex::new("SI|Atkinson|Diesel|H2FC|HD_Diesel").unwrap(); } -#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq, Validate)] #[add_pyo3_api( #[pyo3(name = "set_veh_mass")] pub fn set_veh_mass_py(&mut self) { @@ -88,6 +87,8 @@ lazy_static! { Self::mock_vehicle() } )] +#[doc_field] +#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq, Validate)] /// Struct containing vehicle attributes /// # Python Examples /// ```python @@ -101,15 +102,19 @@ pub struct RustVehicle { #[serde(skip)] #[api(has_orphaned)] /// Physical properties, see [RustPhysicalProperties](RustPhysicalProperties) + #[doc_field(skip_doc)] pub props: RustPhysicalProperties, /// Vehicle name #[serde(alias = "name")] + #[doc_field(skip_doc)] pub scenario_name: String, /// Vehicle database ID #[serde(skip)] + #[doc_field(skip_doc)] pub selection: u32, /// Vehicle year #[serde(alias = "vehModelYear")] + #[doc_field(skip_doc)] pub veh_year: u32, /// Vehicle powertrain type, one of \[[CONV](CONV), [HEV](HEV), [PHEV](PHEV), [BEV](BEV)\] #[serde(alias = "vehPtType")] @@ -912,6 +917,7 @@ impl RustVehicle { fs_mass_kg: Default::default(), veh_kg: Default::default(), max_trac_mps2: Default::default(), + ..Default::default() }; v.set_derived().unwrap(); v @@ -1209,6 +1215,7 @@ mod tests { regen_b, fc_peak_eff_override, mc_peak_eff_override, // bad input + ..Default::default() }; let validation_result = veh.set_derived(); From d96c5dcda4c670d32581c5857ee796b12f6520b4 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 2 Aug 2023 15:36:19 -0600 Subject: [PATCH 07/18] formatting, clippy stuff, and removing warnings during `cargo doc --no-deps` --- .../fastsim-proc-macros/src/doc_field.rs | 2 +- rust/fastsim-core/src/air.rs | 8 +-- rust/fastsim-core/src/cycle.rs | 4 +- rust/fastsim-core/src/simdrive/cyc_mods.rs | 6 +- rust/fastsim-core/src/thermal.rs | 58 ++++++++----------- rust/fastsim-core/src/utils.rs | 10 ++-- rust/fastsim-core/src/vehicle.rs | 45 ++++++++++++++ rust/fastsim-core/src/vehicle_thermal.rs | 48 +++++++-------- 8 files changed, 108 insertions(+), 73 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index fe0f7452..d9c8ce6d 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -29,7 +29,7 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { new_doc_fields.push_str( "/// Vehicle level documentation -- e.g. info about calibration/validation of this parameter, /// links to reports or other long-form documentation. - doc: Option\n}", + pub doc: Option\n}", ); let new_doc_fields: syn::FieldsNamed = syn::parse_str::(&new_doc_fields) .unwrap_or_else(|e| abort_call_site!("{}", e)); diff --git a/rust/fastsim-core/src/air.rs b/rust/fastsim-core/src/air.rs index 327ccb11..239ea554 100644 --- a/rust/fastsim-core/src/air.rs +++ b/rust/fastsim-core/src/air.rs @@ -52,15 +52,13 @@ pub struct AirProperties { impl AirProperties { /// Returns density [kg/m^3] of air - /// Source: https://www.grc.nasa.gov/WWW/K-12/rocket/atmosmet.html + /// Source: /// T = 15.04 - .00649 * h /// p = 101.29 * [(T + 273.1)/288.08]^5.256 /// Arguments: /// ---------- - /// te_air: f64 - /// ambient temperature \[°C\] of air - /// h=180: Option - /// evelation \[m\] above sea level + /// * `te_air` - ambient temperature \[°C\] of air + /// * `h` - evelation \[m\] above sea level, defaults to 180 m pub fn get_rho(&self, te_air: f64, h: Option) -> f64 { let h = h.unwrap_or(180.0); let te_standard = 15.04 - 0.00649 * h; // \[degC\] diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index 00d19cc2..294a46ad 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -688,8 +688,8 @@ impl RustCycle { /// - delta_distance_m: non-negative-number, the distance traveled from distance_start_m (m) /// RETURN: number, the average grade (rise over run) over the given distance range /// Note: grade is assumed to be constant from just after the previous sample point - /// until the current sample point. That is, grade[i] applies over the range of - /// distances, d, from (d[i - 1], d[i]] + /// until the current sample point. That is, grade\[i\] applies over the range of + /// distances, d, from (d\[i - 1\], d\[i\]) pub fn average_grade_over_range( &self, distance_start_m: f64, diff --git a/rust/fastsim-core/src/simdrive/cyc_mods.rs b/rust/fastsim-core/src/simdrive/cyc_mods.rs index 3f55b538..de9d50fa 100644 --- a/rust/fastsim-core/src/simdrive/cyc_mods.rs +++ b/rust/fastsim-core/src/simdrive/cyc_mods.rs @@ -97,7 +97,7 @@ impl RustSimDrive { /// REFERENCE: /// Treiber, Martin and Kesting, Arne. 2013. "Chapter 11: Car-Following Models Based on Driving Strategies". /// Traffic Flow Dynamics: Data, Models and Simulation. Springer-Verlag. Springer, Berlin, Heidelberg. - /// DOI: https://doi.org/10.1007/978-3-642-32460-4. + /// DOI: . #[allow(clippy::too_many_arguments)] pub fn next_speed_by_idm( &mut self, @@ -190,7 +190,7 @@ impl RustSimDrive { /// NOTE: /// If not allowing coasting (i.e., sim_params.coast_allow == False) /// and not allowing IDM/following (i.e., self.sim_params.idm_allow == False) - /// then returns self.cyc.grade[i] + /// then returns self.cyc.grade\[i\] pub fn estimate_grade_for_step(&self, i: usize) -> f64 { if self.cyc0_cache.grade_all_zero { return 0.0; @@ -210,7 +210,7 @@ impl RustSimDrive { /// NOTE: /// If not allowing coasting (i.e., sim_params.coast_allow == False) /// and not allowing IDM/following (i.e., self.sim_params.idm_allow == False) - /// then returns self.cyc.grade[i] + /// then returns self.cyc.grade\[i\] pub fn lookup_grade_for_step(&self, i: usize, mps_ach: Option) -> f64 { if self.cyc0_cache.grade_all_zero { return 0.0; diff --git a/rust/fastsim-core/src/thermal.rs b/rust/fastsim-core/src/thermal.rs index a58dddaf..2d6fb0ef 100644 --- a/rust/fastsim-core/src/thermal.rs +++ b/rust/fastsim-core/src/thermal.rs @@ -834,20 +834,12 @@ impl SimDriveHot { } self.sd.aux_in_kw[i] += self.state.cab_hvac_pwr_aux_kw; // Is SOC below min threshold? - if self.sd.soc[i - 1] < (self.sd.veh.min_soc + self.sd.veh.perc_high_acc_buf) { - self.sd.reached_buff[i] = false; - } else { - self.sd.reached_buff[i] = true; - } + self.sd.reached_buff[i] = + self.sd.soc[i - 1] >= (self.sd.veh.min_soc + self.sd.veh.perc_high_acc_buf); // Does the engine need to be on for low SOC or high acceleration - if self.sd.soc[i - 1] < self.sd.veh.min_soc - || (self.sd.high_acc_fc_on_tag[i - 1] && !(self.sd.reached_buff[i])) - { - self.sd.high_acc_fc_on_tag[i] = true - } else { - self.sd.high_acc_fc_on_tag[i] = false - } + self.sd.high_acc_fc_on_tag[i] = self.sd.soc[i - 1] < self.sd.veh.min_soc + || (self.sd.high_acc_fc_on_tag[i - 1] && !(self.sd.reached_buff[i])); self.sd.max_trac_mps[i] = self.sd.mps_ach[i - 1] + (self.sd.veh.max_trac_mps2 * self.sd.cyc.dt_s_at_i(i)); } @@ -1024,19 +1016,19 @@ impl SimDriveHot { /// Struct containing thermal state variables for all thermal components pub struct ThermalState { // fuel converter (engine) variables - /// fuel converter (engine) temperature [°C] + /// fuel converter (engine) temperature \[°C\] pub fc_te_deg_c: f64, /// fuel converter temperature efficiency correction pub fc_eta_temp_coeff: f64, /// fuel converter heat generation per total heat release minus shaft power pub fc_qdot_per_net_heat: f64, - /// fuel converter heat generation [kW] + /// fuel converter heat generation \[kW\] pub fc_qdot_kw: f64, - /// fuel converter convection to ambient [kW] + /// fuel converter convection to ambient \[kW\] pub fc_qdot_to_amb_kw: f64, - /// fuel converter heat loss to heater core [kW] + /// fuel converter heat loss to heater core \[kW\] pub fc_qdot_to_htr_kw: f64, - /// heat transfer coeff [W / (m ** 2 * K)] to amb after arbitration + /// heat transfer coeff \[W / (m ** 2 * K)\] to amb after arbitration pub fc_htc_to_amb: f64, /// lambda (air/fuel ratio normalized w.r.t. stoich air/fuel ratio) -- 1 is reasonable default pub fc_lambda: f64, @@ -1044,13 +1036,13 @@ pub struct ThermalState { pub fc_te_adiabatic_deg_c: f64, // cabin (cab) variables - /// cabin temperature [°C] + /// cabin temperature \[°C\] pub cab_te_deg_c: f64, - /// previous cabin temperature [°C] + /// previous cabin temperature \[°C\] pub cab_prev_te_deg_c: f64, - /// cabin solar load [kw] + /// cabin solar load \[kw\] pub cab_qdot_solar_kw: f64, - /// cabin convection to ambient [kw] + /// cabin convection to ambient \[kw\] pub cab_qdot_to_amb_kw: f64, /// heat transfer to cabin from hvac system pub cab_qdot_from_hvac_kw: f64, @@ -1058,41 +1050,41 @@ pub struct ThermalState { pub cab_hvac_pwr_aux_kw: f64, // exhaust variables - /// exhaust mass flow rate [kg/s] + /// exhaust mass flow rate \[kg/s\] pub exh_mdot: f64, - /// exhaust enthalpy flow rate [kw] + /// exhaust enthalpy flow rate \[kw\] pub exh_hdot_kw: f64, /// exhaust port (exhport) variables /// exhaust temperature at exhaust port inlet pub exhport_exh_te_in_deg_c: f64, - /// heat transfer from exhport to amb [kw] + /// heat transfer from exhport to amb \[kw\] pub exhport_qdot_to_amb: f64, - /// catalyst temperature [°C] + /// catalyst temperature \[°C\] pub exhport_te_deg_c: f64, - /// convection from exhaust to exhport [W] + /// convection from exhaust to exhport \[W\] /// positive means exhport is receiving heat pub exhport_qdot_from_exh: f64, - /// net heat generation in cat [W] + /// net heat generation in cat \[W\] pub exhport_qdot_net: f64, // catalyst (cat) variables - /// catalyst heat generation [W] + /// catalyst heat generation \[W\] pub cat_qdot: f64, - /// catalytic converter convection coefficient to ambient [W / (m ** 2 * K)] + /// catalytic converter convection coefficient to ambient \[W / (m ** 2 * K)\] pub cat_htc_to_amb: f64, - /// heat transfer from catalyst to ambient [W] + /// heat transfer from catalyst to ambient \[W\] pub cat_qdot_to_amb: f64, - /// catalyst temperature [°C] + /// catalyst temperature \[°C\] pub cat_te_deg_c: f64, /// exhaust temperature at cat inlet pub cat_exh_te_in_deg_c: f64, /// catalyst external reynolds number pub cat_re_ext: f64, - /// convection from exhaust to cat [W] + /// convection from exhaust to cat \[W\] /// positive means cat is receiving heat pub cat_qdot_from_exh: f64, - /// net heat generation in cat [W] + /// net heat generation in cat \[W\] pub cat_qdot_net: f64, /// ambient temperature diff --git a/rust/fastsim-core/src/utils.rs b/rust/fastsim-core/src/utils.rs index b12bc254..db3556b6 100644 --- a/rust/fastsim-core/src/utils.rs +++ b/rust/fastsim-core/src/utils.rs @@ -70,22 +70,22 @@ pub fn min(a: f64, b: f64) -> f64 { a.min(b) } -/// return max of arr +/// return max of arr pub fn arrmax(arr: &[f64]) -> f64 { arr.iter().copied().fold(f64::NAN, f64::max) } -/// return min of arr +/// return min of arr pub fn arrmin(arr: &[f64]) -> f64 { arr.iter().copied().fold(f64::NAN, f64::min) } -/// return min of arr +/// return min of arr pub fn ndarrmin(arr: &Array1) -> f64 { arr.to_vec().into_iter().reduce(f64::min).unwrap() } -/// return max of arr +/// return max of arr pub fn ndarrmax(arr: &Array1) -> f64 { arr.to_vec().into_iter().reduce(f64::max).unwrap() } @@ -95,7 +95,7 @@ pub fn ndarrallzeros(arr: &Array1) -> bool { arr.iter().all(|x| *x == 0.0) } -/// return cumsum of arr +/// return cumsum of arr pub fn ndarrcumsum(arr: &Array1) -> Array1 { arr.iter() .scan(0.0, |acc, &x| { diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 8fcb29c4..56e98deb 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -122,6 +122,7 @@ pub struct RustVehicle { path = "VEH_PT_TYPE_OPTIONS_REGEX", message = "must be one of [\"Conv\", \"HEV\", \"PHEV\", \"BEV\"]" ))] + #[doc_field(skip_doc)] pub veh_pt_type: String, /// Aerodynamic drag coefficient #[serde(alias = "dragCoef")] @@ -351,138 +352,181 @@ pub struct RustVehicle { #[validate(range(min = 0))] pub ess_to_fuel_ok_error: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub small_motor_power_kw: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub large_motor_power_kw: f64, // this and other fixed-size arrays can probably be vectors // without any performance penalty with the current implementation // of the functions in utils.rs #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub fc_perc_out_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub regen_a: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub regen_b: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub charging_on: bool, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub no_elec_sys: bool, #[doc(hidden)] + #[doc_field(skip_doc)] // all of the parameters that are set in `set_derived` should be skipped by serde #[serde(skip)] pub no_elec_aux: bool, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub max_roadway_chg_kw: Array1, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub input_kw_out_array: Array1, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub fc_kw_out_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(default)] #[serde(alias = "fcEffArray")] pub fc_eff_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub modern_max: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_eff_array: Array1, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_kw_in_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_kw_out_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_max_elec_in_kw: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_full_eff_array: Vec, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(alias = "vehKg")] pub veh_kg: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub max_trac_mps2: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub ess_mass_kg: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_mass_kg: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub fc_mass_kg: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub fs_mass_kg: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub mc_perc_out_array: Vec, // these probably don't need to be in rust #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_udds_mpgge: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_hwy_mpgge: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_comb_mpgge: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_udds_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_hwy_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_comb_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_cd_range_mi: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_const65_mph_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_const60_mph_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_const55_mph_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_const45_mph_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_unadj_udds_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_unadj_hwy_kwh_per_mile: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val0_to60_mph: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_ess_life_miles: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_range_miles: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_veh_base_cost: f64, #[doc(hidden)] + #[doc_field(skip_doc)] #[serde(skip)] pub val_msrp: f64, /// Fuel converter efficiency peak override, scales entire curve @@ -495,6 +539,7 @@ pub struct RustVehicle { pub mc_peak_eff_override: Option, #[serde(skip)] #[doc(hidden)] + #[doc_field(skip_doc)] pub orphaned: bool, } diff --git a/rust/fastsim-core/src/vehicle_thermal.rs b/rust/fastsim-core/src/vehicle_thermal.rs index 6679a198..2f8763ad 100644 --- a/rust/fastsim-core/src/vehicle_thermal.rs +++ b/rust/fastsim-core/src/vehicle_thermal.rs @@ -99,13 +99,13 @@ impl Default for FcTempEffModelExponential { pub struct HVACModel { /// set temperature for component (e.g. cabin, ESS) pub te_set_deg_c: f64, - /// proportional control effort [kW / °C] + /// proportional control effort \[kW / °C\] pub p_cntrl_kw_per_deg_c: f64, - /// integral control effort [kW / (°C-seconds)] + /// integral control effort \[kW / (°C-seconds)\] pub i_cntrl_kw_per_deg_c_scnds: f64, - /// derivative control effort [kW / (°C/second) = kJ / °C] + /// derivative control effort \\[kW / (°C/second) = kJ / °C\\] pub d_cntrl_kj_per_deg_c: f64, - /// Saturation value for integral control [kW]. + /// Saturation value for integral control \[kW\]. /// Whenever `i_cntrl_kw` hit this value, it stops accumulating pub cntrl_max_kw: f64, /// deadband range. any cabin temperature within this range of @@ -358,18 +358,18 @@ pub fn get_sphere_conv_params(re: f64) -> (f64, f64) { )] pub struct VehicleThermal { // fuel converter / engine - /// parameter fuel converter thermal mass [kJ/K] + /// parameter fuel converter thermal mass \[kJ/K\] pub fc_c_kj__k: f64, - /// parameter for engine characteristic length [m] for heat transfer calcs + /// parameter for engine characteristic length \[m\] for heat transfer calcs pub fc_l: f64, - /// parameter for heat transfer coeff [W / (m ** 2 * K)] from eng to ambient during vehicle stop + /// parameter for heat transfer coeff \[W / (m ** 2 * K)\] from eng to ambient during vehicle stop pub fc_htc_to_amb_stop: f64, /// coeff. for fraction of combustion heat that goes to fuel converter (engine) /// thermal mass. Remainder goes to environment (e.g. via tailpipe) pub fc_coeff_from_comb: f64, - /// parameter for temperature [°C] at which thermostat starts to open + /// parameter for temperature \[°C\] at which thermostat starts to open pub tstat_te_sto_deg_c: f64, - /// temperature delta [°C] over which thermostat is partially open + /// temperature delta \[°C\] over which thermostat is partially open pub tstat_te_delta_deg_c: f64, /// radiator effectiveness -- ratio of active heat rejection from /// radiator to passive heat rejection @@ -381,7 +381,7 @@ pub struct VehicleThermal { pub fc_model: FcModelTypes, // battery - /// battery thermal mass [kJ/K] + /// battery thermal mass \[kJ/K\] pub ess_c_kj_k: f64, /// effective (incl. any thermal management system) heat transfer coefficient from battery to ambient pub ess_htc_to_amb: f64, @@ -393,15 +393,15 @@ pub struct VehicleThermal { /// cabin model internal or external w.r.t. fastsim #[api(skip_get, skip_set)] pub cabin_hvac_model: CabinHvacModelTypes, - /// parameter for cabin thermal mass [kJ/K] + /// parameter for cabin thermal mass \[kJ/K\] pub cab_c_kj__k: f64, - /// cabin length [m], modeled as a flat plate + /// cabin length \[m\], modeled as a flat plate pub cab_l_length: f64, - /// cabin width [m], modeled as a flat plate + /// cabin width \[m\], modeled as a flat plate pub cab_l_width: f64, - /// cabin shell thermal resistance [m **2 * K / W] + /// cabin shell thermal resistance \[m **2 * K / W\] pub cab_r_to_amb: f64, - /// parameter for heat transfer coeff [W / (m ** 2 * K)] from cabin to ambient during + /// parameter for heat transfer coeff \[W / (m ** 2 * K)\] from cabin to ambient during /// vehicle stop pub cab_htc_to_amb_stop: f64, @@ -410,21 +410,21 @@ pub struct VehicleThermal { /// exhaust port model type #[api(skip_get, skip_set)] pub exhport_model: ComponentModelTypes, - /// thermal conductance [W/K] for heat transfer to ambient + /// thermal conductance \[W/K\] for heat transfer to ambient pub exhport_ha_to_amb: f64, - /// thermal conductance [W/K] for heat transfer from exhaust + /// thermal conductance \[W/K\] for heat transfer from exhaust pub exhport_ha_int: f64, - /// exhaust port thermal capacitance [kJ/K] + /// exhaust port thermal capacitance \[kJ/K\] pub exhport_c_kj__k: f64, // catalytic converter (catalyst) #[api(skip_get, skip_set)] pub cat_model: ComponentModelTypes, - /// diameter [m] of catalyst as sphere for thermal model + /// diameter \[m\] of catalyst as sphere for thermal model pub cat_l: f64, - /// catalyst thermal capacitance [kJ/K] + /// catalyst thermal capacitance \[kJ/K\] pub cat_c_kj__K: f64, - /// parameter for heat transfer coeff [W / (m ** 2 * K)] from catalyst to ambient + /// parameter for heat transfer coeff \[W / (m ** 2 * K)\] from catalyst to ambient /// during vehicle stop pub cat_htc_to_amb_stop: f64, /// lightoff temperature to be used when fc_temp_eff_component == 'hybrid' @@ -474,17 +474,17 @@ impl Default for VehicleThermal { } impl VehicleThermal { - /// derived temperature [ºC] at which thermostat is fully open + /// derived temperature \[ºC\] at which thermostat is fully open pub fn tstat_te_fo_deg_c(&self) -> f64 { self.tstat_te_sto_deg_c + self.tstat_te_delta_deg_c } - /// parameter for engine surface area [m**2] for heat transfer calcs + /// parameter for engine surface area \[m**2\] for heat transfer calcs pub fn fc_area_ext(&self) -> f64 { PI * self.fc_l.powf(2.0 / 4.0) } - /// parameter for catalyst surface area [m**2] for heat transfer calcs + /// parameter for catalyst surface area \[m**2\] for heat transfer calcs pub fn cat_area_ext(&self) -> f64 { PI * self.cat_l.powf(2.0 / 4.0) } From ce1fed962abbc48f4dbc2b78bad46da6767cb6c6 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 2 Aug 2023 15:59:06 -0600 Subject: [PATCH 08/18] improved documentation and added `..Default::default` --- rust/fastsim-core/src/air.rs | 8 ++++++-- rust/fastsim-core/src/simdrivelabel.rs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/fastsim-core/src/air.rs b/rust/fastsim-core/src/air.rs index 239ea554..1d93aaaa 100644 --- a/rust/fastsim-core/src/air.rs +++ b/rust/fastsim-core/src/air.rs @@ -51,12 +51,16 @@ pub struct AirProperties { } impl AirProperties { + // This is an example of a canonical doc string per: + // https://doc.rust-lang.org/beta/rust-by-example/meta/doc.html /// Returns density [kg/m^3] of air /// Source: + /// + /// # Equations used /// T = 15.04 - .00649 * h /// p = 101.29 * [(T + 273.1)/288.08]^5.256 - /// Arguments: - /// ---------- + /// + /// # Arguments /// * `te_air` - ambient temperature \[°C\] of air /// * `h` - evelation \[m\] above sea level, defaults to 180 m pub fn get_rho(&self, te_air: f64, h: Option) -> f64 { diff --git a/rust/fastsim-core/src/simdrivelabel.rs b/rust/fastsim-core/src/simdrivelabel.rs index 25186b84..4515a598 100644 --- a/rust/fastsim-core/src/simdrivelabel.rs +++ b/rust/fastsim-core/src/simdrivelabel.rs @@ -947,6 +947,7 @@ mod simdrivelabel_tests { fc_peak_eff_override: None, mc_peak_eff_override: None, orphaned: false, + ..Default::default() }; veh.set_derived().unwrap(); From eef22f3230b37ce2300b3a16e523d6fd7dc77c16 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 2 Aug 2023 16:30:16 -0600 Subject: [PATCH 09/18] placement of `#[doc_field]` above `#[add_pyo3_api(...)]` is critical! --- rust/fastsim-core/src/vehicle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 56e98deb..a951a5da 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -33,6 +33,7 @@ lazy_static! { Regex::new("SI|Atkinson|Diesel|H2FC|HD_Diesel").unwrap(); } +#[doc_field] #[add_pyo3_api( #[pyo3(name = "set_veh_mass")] pub fn set_veh_mass_py(&mut self) { @@ -87,7 +88,6 @@ lazy_static! { Self::mock_vehicle() } )] -#[doc_field] #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, ApproxEq, Validate)] /// Struct containing vehicle attributes /// # Python Examples From f690a3412c6061ca60ce0bffc725911e204403fe Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Wed, 2 Aug 2023 16:33:46 -0600 Subject: [PATCH 10/18] better word wrapping --- python/fastsim/demos/demo.py | 205 ++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 89 deletions(-) diff --git a/python/fastsim/demos/demo.py b/python/fastsim/demos/demo.py index b72c7e39..744f76f1 100644 --- a/python/fastsim/demos/demo.py +++ b/python/fastsim/demos/demo.py @@ -1,21 +1,31 @@ -# To add a new cell, type '# %%' -# To add a new markdown cell, type '# %% [markdown]' +# To add a new cell, type '# %%' To add a new markdown cell, type '# %% +# [markdown]' # %% [markdown] # # FASTSim Demonstration -# | -# ![fastsim icon](fastsim-icon-web-131x172.jpg) +# | ![fastsim icon](fastsim-icon-web-131x172.jpg) # -# Developed by NREL, the Future Automotive Systems Technology Simulator (FASTSim) evaluates the impact of technology improvements on efficiency, performance, cost, and battery life in conventional vehicles, hybrid electric vehicles (HEVs), plug-in hybrid electric vehicles (PHEVs), and all-electric vehicles (EVs). +# Developed by NREL, the Future Automotive Systems Technology Simulator +# (FASTSim) evaluates the impact of technology improvements on +# efficiency, performance, cost, and battery life in conventional +# vehicles, hybrid electric vehicles (HEVs), plug-in hybrid electric +# vehicles (PHEVs), and all-electric vehicles (EVs). # # FASTSim answers questions such as: # - Which battery sizes are most cost effective for a PHEV or EV? # - At what battery prices do PHEVs and EVs become cost effective? -# - On average, how much fuel does a PHEV with a 30-mile electric range save? +# - On average, how much fuel does a PHEV with a 30-mile electric range +# save? # - How much fuel savings does an HEV provide for a given drive cycle? -# - How do lifetime costs and petroleum use compare for conventional vehicles, HEVs, PHEVs, and EVs? +# - How do lifetime costs and petroleum use compare for conventional +# vehicles, HEVs, PHEVs, and EVs? # -# FASTSim was originally implemented in Microsoft Excel. The pythonic implementation of FASTSim, demonstrated here, captures the drive cycle energy consumption simulation component of the software. The python version of FASTSim is more convenient than the Excel version when very high computational speed is desired, such as for simulating a large batch of drive cycles. +# FASTSim was originally implemented in Microsoft Excel. The pythonic +# implementation of FASTSim, demonstrated here, captures the drive cycle +# energy consumption simulation component of the software. The python +# version of FASTSim is more convenient than the Excel version when very +# high computational speed is desired, such as for simulating a large +# batch of drive cycles. # %% import sys @@ -27,13 +37,11 @@ import pandas as pd import matplotlib.pyplot as plt import importlib -# import seaborn as sns -# sns.set(font_scale=2, style='whitegrid') +# import seaborn as sns sns.set(font_scale=2, style='whitegrid') # local modules import fastsim as fsim -# importlib.reload(simdrive) -# importlib.reload(cycle) +# importlib.reload(simdrive) importlib.reload(cycle) #%% @@ -46,15 +54,20 @@ # ## Individual Drive Cycle # ### Load Drive Cycle # -# Default (UDDS, US06, HWFET) cycles can be loaded from the ```../cycles``` directory, or custom cycles can be specified in the same format. The expected format is a dictionary with the following keys: +# Default (UDDS, US06, HWFET) cycles can be loaded from the +# ```../cycles``` directory, or custom cycles can be specified in the +# same format. The expected format is a dictionary with the following +# keys: # # ```['cycGrade', 'mps', 'time_s', 'road_type']``` # - cycGrade = Road grade [%/100] # - mps = Vehicle speed [meters per second] # - time_s = Relative time in the cycles [seconds] -# - road_type = Indicator as to whether or not there is a wireless charging capability from the road to vehicle +# - road_type = Indicator as to whether or not there is a wireless +# charging capability from the road to vehicle # -# There is no limit to the length of a drive cycle that can be provided as an input to FASTSim. +# There is no limit to the length of a drive cycle that can be provided +# as an input to FASTSim. # %% t0 = time.time() @@ -65,7 +78,13 @@ # %% [markdown] # ### Load Powertrain Model # -# A vehicle database in CSV format is required to be in the working directory where FASTSim is running (i.e. the same directory as this notebook). The "get_veh" function selects the appropriate vehicle attributes from the database and contructs the powertrain model (engine efficiency map, etc.). An integer value corresponds to each vehicle in the database. To add a new vehicle, simply populate a new row to the vehicle database CSV. +# A vehicle database in CSV format is required to be in the working +# directory where FASTSim is running (i.e. the same directory as this +# notebook). The "get_veh" function selects the appropriate vehicle +# attributes from the database and contructs the powertrain model +# (engine efficiency map, etc.). An integer value corresponds to each +# vehicle in the database. To add a new vehicle, simply populate a new +# row to the vehicle database CSV. # %% t0 = time.time() @@ -129,12 +148,14 @@ # %% [markdown] # ## Running sim_drive_step() with modified auxInKw -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 # ### Overriding at each time step # %% ## Running sim_drive_step() with modified auxInKw -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 t0 = time.time() @@ -159,7 +180,8 @@ # %% ## Running sim_drive_step() with modified auxInKw using Rust # Note that the aux load array **must** be set as a whole. We currently -# cannot set just an index of an array via the Python bindings to Rust at this time +# cannot set just an index of an array via the Python bindings to Rust +# at this time t0 = time.time() @@ -169,8 +191,9 @@ sim_drive.init_for_step(init_soc=0.7935) while sim_drive.i < len(cyc.time_s): - # NOTE: we need to copy out and in the entire array to work with the Rust version - # that is, we can't set just a specific element of an array in rust via python bindings at this time + # NOTE: we need to copy out and in the entire array to work with the + # Rust version that is, we can't set just a specific element of an + # array in rust via python bindings at this time aux_in_kw = sim_drive.aux_in_kw.tolist() aux_in_kw[sim_drive.i] = sim_drive.i / cyc.time_s[-1] * 10 sim_drive.aux_in_kw = aux_in_kw @@ -190,7 +213,8 @@ # %% ## Running sim_drive_step() with modified auxInKw -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 t0 = time.time() @@ -213,7 +237,8 @@ # %% ## Running sim_drive_step() with modified auxInKw using Rust -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 t0 = time.time() veh = fsim.vehicle.Vehicle.from_vehdb(9).to_rust() @@ -237,7 +262,8 @@ # %% ## Running sim_drive_step() with modified auxInKw -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 t0 = time.time() @@ -245,7 +271,8 @@ cyc = fsim.cycle.Cycle.from_file('udds') sim_drive = fsim.simdrive.SimDrive(cyc, veh) -# by assigning the value directly (this is faster than using positional args) +# by assigning the value directly (this is faster than using positional +# args) sim_drive.init_for_step( 0.5, aux_in_kw_override=cyc.time_s / cyc.time_s[-1] * 10 @@ -265,7 +292,8 @@ # %% ## Running sim_drive_step() with modified auxInKw using Rust -# Note that auxInKw is the only variable setup to be externally modified as of 1 July 2020 +# Note that auxInKw is the only variable setup to be externally modified +# as of 1 July 2020 t0 = time.time() @@ -273,7 +301,8 @@ cyc = fsim.cycle.Cycle.from_file('udds').to_rust() sim_drive = fsim.simdrive.RustSimDrive(cyc, veh) -# by assigning the value directly (this is faster than using positional args) +# by assigning the value directly (this is faster than using positional +# args) sim_drive.init_for_step( 0.5, aux_in_kw_override=np.array(cyc.time_s) / cyc.time_s[-1] * 10 @@ -292,10 +321,9 @@ print(f'Time to simulate: {time.time() - t0:.2e} s') -# %% -# by assigning positional arguments -# may require recompile if these arguments have not been passed, -# but this is the fastest approach after compilation +# %% by assigning positional arguments may require recompile if these +# arguments have not been passed, but this is the fastest approach after +# compilation veh = fsim.vehicle.Vehicle.from_vehdb(9) cyc = fsim.cycle.Cycle.from_file('udds') @@ -315,10 +343,9 @@ print(f'Time to simulate: {time.time() - t0:.2e} s') -# %% -# by assigning positional arguments (using Rust) -# may require recompile if these arguments have not been passed, -# but this is the fastest approach after compilation +# %% by assigning positional arguments (using Rust) may require +# recompile if these arguments have not been passed, but this is the +# fastest approach after compilation veh = fsim.vehicle.Vehicle.from_vehdb(9).to_rust() cyc = fsim.cycle.Cycle.from_file('udds').to_rust() @@ -341,23 +368,33 @@ # %% [markdown] # ## Batch Drive Cycles - TSDC Drive Cycles # -# FASTSim's most significant advantage over other powertrain simulation tools comes from the ability -# to simulate many drive cycles quickly. The same three steps described above (load cycle, load model, run FASTSim) -# will be used here, however, the demonstration highlights how quickly FASTSim runs over __2,225 miles of driving__ -# data for 22 vehicles. Running on a single core, the 241 drive cycles take roughly 25 seconds to run. Each drive -# cycle requires a fraction of a second of computational time. +# FASTSim's most significant advantage over other powertrain simulation +# tools comes from the ability to simulate many drive cycles quickly. +# The same three steps described above (load cycle, load model, run +# FASTSim) will be used here, however, the demonstration highlights how +# quickly FASTSim runs over __2,225 miles of driving__ data for 22 +# vehicles. Running on a single core, the 241 drive cycles take roughly +# 25 seconds to run. Each drive cycle requires a fraction of a second of +# computational time. # -# The drive cycles simulated are from a subset of Chicago Regional Household Travel Inventory housed in the the -# Transportation Secure Data Center ([TSDC](https://www.nrel.gov/transportation/secure-transportation-data/tsdc-cleansed-data.html)). -# Cycles within the TSDC are publicly available for download and easily integrate with FASTSim. You may contact the -# [TSDC](tsdc@nrel.gov) for general questions on the data center, or [Venu Garikapati](venu.garikapati@nrel.gov) for -# partnership-related inquiries. +# The drive cycles simulated are from a subset of Chicago Regional +# Household Travel Inventory housed in the the Transportation Secure +# Data Center +# ([TSDC](https://www.nrel.gov/transportation/secure-transportation-data/tsdc-cleansed-data.html)). +# Cycles within the TSDC are publicly available for download and easily +# integrate with FASTSim. You may contact the [TSDC](tsdc@nrel.gov) for +# general questions on the data center, or [Venu +# Garikapati](venu.garikapati@nrel.gov) for partnership-related +# inquiries. # # ### Load Cycles -# Iterate through the drive cycles directory structure and load the cycles into one pandas dataframe. If memory is an issue, -# this processing can be broken into smaller chunks. The points table must have trip identifiers appended to run FASTSim on -# individual trips. The trips are identified and labeled using the start and end timestamps in the "trips.csv" summary tables -# in each of the vehicle directories downloadable from the TSDC. +# Iterate through the drive cycles directory structure and load the +# cycles into one pandas dataframe. If memory is an issue, this +# processing can be broken into smaller chunks. The points table must +# have trip identifiers appended to run FASTSim on individual trips. The +# trips are identified and labeled using the start and end timestamps in +# the "trips.csv" summary tables in each of the vehicle directories +# downloadable from the TSDC. # %% t0 = time.time() @@ -398,7 +435,8 @@ tripK_df['sampno'] = [sampno] * len(tripK_df) tripK_df['vehno'] = [vehno] * len(tripK_df) drive_cycs_df = pd.concat([drive_cycs_df, tripK_df],ignore_index=True) - #drive_cycs_df = drive_cycs_df.append(tripK_df, ignore_index=True) + #drive_cycs_df = drive_cycs_df.append(tripK_df, + #ignore_index=True) t1 = time.time() print(f'Time to load cycles: {time.time() - t0:.2e} s') @@ -450,8 +488,7 @@ print(' Average time per cycle = {:.2f} s'.format(( t_end - t_start) / len(drive_cycs_df.nrel_trip_id.unique()))) -# %% -# ... and the Rust version +# %% ... and the Rust version veh = fsim.vehicle.Vehicle.from_vehdb(1).to_rust() # load vehicle model output = {} @@ -498,13 +535,14 @@ # %% [markdown] # ### Results # -# In this demo, the batch results from all 494 drive cycles were output to a -# Pandas Dataframe to simplify post-processing. Any python data structure or -# output file format can be used to save batch results. For simplicity, time -# series data was not stored, but it could certainly be included in batch processing. -# In order to plot the data, a handful of results are filtered out either because -# they are much longer than we are interested in, or there was some GPS issue in -# data acquisition that led to an unrealistically high cycle average speed. +# In this demo, the batch results from all 494 drive cycles were output +# to a Pandas Dataframe to simplify post-processing. Any python data +# structure or output file format can be used to save batch results. For +# simplicity, time series data was not stored, but it could certainly be +# included in batch processing. In order to plot the data, a handful of +# results are filtered out either because they are much longer than we +# are interested in, or there was some GPS issue in data acquisition +# that led to an unrealistically high cycle average speed. # %% df_fltr = results_df[(results_df['distance_mi'] < 1000) @@ -551,16 +589,14 @@ # %% [markdown] # ## Micro-trip -# %% -# load vehicle +# %% load vehicle t0 = time.time() veh = fsim.vehicle.Vehicle.from_vehdb(9) # veh = veh print(f'Time to load vehicle: {time.time() - t0:.2e} s') -# %% -# generate micro-trip +# %% generate micro-trip t0 = time.time() cyc = fsim.cycle.Cycle.from_file("udds") microtrips = fsim.cycle.to_microtrips(cyc.get_cyc_dict()) @@ -568,8 +604,7 @@ print(f'Time to load cycle: {time.time() - t0:.2e} s') -# %% -# simulate +# %% simulate t0 = time.time() sim_drive = fsim.simdrive.SimDrive(cyc, veh) sim_drive.sim_drive() @@ -605,19 +640,17 @@ # %% [markdown] # ## Concat cycles/trips -# Includes examples of loading vehicle from standalone file and loading non-standard -# cycle from file +# Includes examples of loading vehicle from standalone file and loading +# non-standard cycle from file -# %% -# load vehicle +# %% load vehicle t0 = time.time() # load from standalone vehicle file veh = fsim.vehicle.Vehicle.from_file('2012_Ford_Fusion.csv') # load vehicle using name print(f'Time to load veicle: {time.time() - t0:.2e} s') -# %% -# generate concatenated trip +# %% generate concatenated trip t0 = time.time() # load from cycle file path cyc1 = fsim.cycle.Cycle.from_file( @@ -628,8 +661,7 @@ print(f'Time to load cycles: {time.time() - t0:.2e} s') -# %% -# simulate +# %% simulate t0 = time.time() sim_drive = fsim.simdrive.SimDrive(cyc_combo, veh) sim_drive.sim_drive() @@ -665,8 +697,7 @@ # %% [markdown] # ## Cycle comparison -# %% -# generate concatenated trip +# %% generate concatenated trip t0 = time.time() cyc1 = fsim.cycle.Cycle.from_file("udds") cyc2 = fsim.cycle.Cycle.from_file("us06") @@ -700,10 +731,10 @@ # %% [markdown] # ## Concat cycles of different time steps and resample -# This is useful if you have test data with either a variable or overly high sample rate. +# This is useful if you have test data with either a variable or overly +# high sample rate. -# %% -# load vehicle +# %% load vehicle t0 = time.time() # load vehicle using explicit path veh = fsim.vehicle.Vehicle.from_file(Path(fsim.simdrive.__file__).parent / @@ -711,8 +742,7 @@ print(f'Time to load vehicle: {time.time() - t0:.2e} s') -# %% -# generate concatenated trip +# %% generate concatenated trip t0 = time.time() cyc_udds = fsim.cycle.Cycle.from_file("udds") # Generate cycle with 0.1 s time steps @@ -725,8 +755,7 @@ print(f'Time to load and concatenate cycles: {time.time() - t0:.2e} s') -# %% -# simulate +# %% simulate t0 = time.time() sim_drive = fsim.simdrive.SimDrive(cyc_combo, veh) sim_drive.sim_drive() @@ -762,16 +791,14 @@ # %% [markdown] # ## Clip by times -# %% -# load vehicle +# %% load vehicle t0 = time.time() veh = fsim.vehicle.Vehicle.from_vehdb(1) # veh = veh print(f'Time to load vehicle: {time.time() - t0:.2e} s') -# %% -# generate micro-trip +# %% generate micro-trip t0 = time.time() cyc = fsim.cycle.Cycle.from_file("udds") cyc = fsim.cycle.clip_by_times(cyc.get_cyc_dict(), t_end=300) @@ -779,8 +806,7 @@ print(f'Time to load and clip cycle: {time.time() - t0:.2e} s') -# %% -# simulate +# %% simulate t0 = time.time() sim_drive = fsim.simdrive.SimDrive(cyc, veh) sim_drive.sim_drive() @@ -815,7 +841,8 @@ # %% [markdown] # ### Test Coefficients Calculation # -# Test drag and wheel rolling resistance calculation from coastdown test values. +# Test drag and wheel rolling resistance calculation from coastdown test +# values. # %% test_veh = fsim.vehicle.Vehicle.from_vehdb(5, to_rust=True).to_rust() From ac03629eb8a0d0ee65441579f5813d0276e8ac51 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 3 Aug 2023 12:59:54 -0600 Subject: [PATCH 11/18] cleaned up proc macro documentation --- .../fastsim-proc-macros/src/add_pyo3_api/mod.rs | 2 ++ .../fastsim-proc-macros/src/approx_eq_derive.rs | 2 ++ rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs | 3 ++- .../fastsim-proc-macros/src/history_vec_derive.rs | 2 ++ rust/fastsim-core/fastsim-proc-macros/src/lib.rs | 8 +++++++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index e46347ef..e7ed0d17 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -1,3 +1,5 @@ +//! Module that implements [super::add_pyo3_api] + #[macro_use] mod pyo3_api_utils; diff --git a/rust/fastsim-core/fastsim-proc-macros/src/approx_eq_derive.rs b/rust/fastsim-core/fastsim-proc-macros/src/approx_eq_derive.rs index eb6068e2..5babdf0a 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/approx_eq_derive.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/approx_eq_derive.rs @@ -1,3 +1,5 @@ +//! Module that implements [super::approx_eq_derive] + use crate::imports::*; pub fn approx_eq_derive(input: TokenStream) -> TokenStream { diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index d9c8ce6d..e46fb030 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -1,6 +1,7 @@ +//! Module that implements [super::doc_field] + use crate::imports::*; -/// pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); diff --git a/rust/fastsim-core/fastsim-proc-macros/src/history_vec_derive.rs b/rust/fastsim-core/fastsim-proc-macros/src/history_vec_derive.rs index 61115cab..58bb71a5 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/history_vec_derive.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/history_vec_derive.rs @@ -1,3 +1,5 @@ +//! Module that implements [super::history_vec_derive] + use crate::imports::*; use crate::utilities::*; diff --git a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs index 68831d28..2e636623 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/lib.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/lib.rs @@ -1,3 +1,5 @@ +//! Crate that provides procedural macros for [fastsim-core](https://crates.io/crates/fastsim-core) + // modules mod imports; // modules - macros @@ -12,24 +14,28 @@ mod utilities; // imports use crate::imports::*; -/// macro for creating appropriate setters and getters for pyo3 struct attributes +/// Adds pyo3 getters and setters for all fields, unless skip attribute is present. #[proc_macro_error] #[proc_macro_attribute] pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { add_pyo3_api::add_pyo3_api(attr, item) } +/// Adds an equivelent `*_doc: Option` field for each field #[proc_macro_error] #[proc_macro_attribute] pub fn doc_field(attr: TokenStream, item: TokenStream) -> TokenStream { doc_field::doc_field(attr, item) } +/// Derive macro that creates `*HistoryVec` struct from `*State` struct, +/// with a corresponding Vec for each field in `*State`. #[proc_macro_derive(HistoryVec)] pub fn history_vec_derive(input: TokenStream) -> TokenStream { history_vec_derive::history_vec_derive(input) } +/// Derive implementation of ApproxEq trait #[proc_macro_derive(ApproxEq)] pub fn approx_eq_derive(input: TokenStream) -> TokenStream { approx_eq_derive::approx_eq_derive(input) From 23d8e1dd0a3ba1d082a21812b9ed964660502a36 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Thu, 3 Aug 2023 13:07:42 -0600 Subject: [PATCH 12/18] incremented version --- rust/fastsim-core/Cargo.toml | 7 +++++-- rust/fastsim-core/fastsim-proc-macros/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rust/fastsim-core/Cargo.toml b/rust/fastsim-core/Cargo.toml index 4386b2b4..ce738e2b 100644 --- a/rust/fastsim-core/Cargo.toml +++ b/rust/fastsim-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastsim-core" -version = "0.1.3" +version = "0.1.4" edition = "2021" license = "Apache-2.0" authors = ["NREL/MTES/CIMS/MBAP Group "] @@ -10,7 +10,10 @@ readme = "../../README.md" repository = "https://github.com/NREL/fastsim" [dependencies] -proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" } +# uncomment next line for any development work in fastsim-proc-macros +# proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" } +# comment next line for any development work in fastsim-proc-macros +proc-macros = { package = "fastsim-proc-macros", version = "0.1.4" } pyo3 = { workspace = true, features = [ "extension-module", "anyhow", diff --git a/rust/fastsim-core/fastsim-proc-macros/Cargo.toml b/rust/fastsim-core/fastsim-proc-macros/Cargo.toml index 18b308d4..89605827 100644 --- a/rust/fastsim-core/fastsim-proc-macros/Cargo.toml +++ b/rust/fastsim-core/fastsim-proc-macros/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["NREL/MTES/CIMS/MBAP Group "] name = "fastsim-proc-macros" -version = "0.1.3" +version = "0.1.4" edition = "2021" license = "Apache-2.0" readme = "../../../README.md" From 054cc41a154d7287128f089909a87e514b24b8b0 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Fri, 11 Aug 2023 15:13:58 -0600 Subject: [PATCH 13/18] fixed code for removed stale struct field attributes --- .../src/add_pyo3_api/mod.rs | 11 +++++++--- .../fastsim-proc-macros/src/doc_field.rs | 20 +++++++++++-------- .../fastsim-proc-macros/src/imports.rs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs index e7ed0d17..689fef7d 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/add_pyo3_api/mod.rs @@ -78,9 +78,14 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect(); // println!("options {:?}", opts); - let mut iter = keep.iter(); - // this drops attrs with api, removing the field attribute from the struct def - field.attrs.retain(|_| *iter.next().unwrap()); + // this drops attrs matching `#[pyo3_api(...)]`, removing the field attribute from the struct def + let new_attrs: (Vec<&syn::Attribute>, Vec) = field + .attrs + .iter() + .zip(keep.iter()) + .filter(|(_a, k)| **k) + .unzip(); + field.attrs = new_attrs.0.iter().cloned().cloned().collect(); if let syn::Type::Path(type_path) = ftype.clone() { // println!( diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index e46fb030..797cc66e 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -10,11 +10,7 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { for field in named.iter_mut() { let mut skip_doc = false; - let keep = get_attrs_to_keep(field, &mut skip_doc); - // println!("options {:?}", opts); - let mut iter = keep.iter(); - // this drops attrs with api, removing the field attribute from the struct def - field.attrs.retain(|_| *iter.next().unwrap()); + remove_handled_attrs(field, &mut skip_doc); if !skip_doc { // create new doc field as string new_doc_fields.push_str(&format!( @@ -43,8 +39,8 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { ast.into_token_stream().into() } -/// Returns attributes to retain, i.e. attributes that are not handled by the [doc_field] macro -fn get_attrs_to_keep(field: &mut syn::Field, skip_doc: &mut bool) -> Vec { +/// Remove field attributes, i.e. attributes that are not handled by the [doc_field] macro +fn remove_handled_attrs(field: &mut syn::Field, skip_doc: &mut bool) { let keep: Vec = field .attrs .iter() @@ -76,5 +72,13 @@ fn get_attrs_to_keep(field: &mut syn::Field, skip_doc: &mut bool) -> Vec { _ => true, }) .collect(); - keep + // println!("options {:?}", opts); + // this drops attrs matching `#[doc_field]`, removing the field attribute from the struct def + let new_attrs: (Vec<&syn::Attribute>, Vec) = field + .attrs + .iter() + .zip(keep.iter()) + .filter(|(_a, k)| **k) + .unzip(); + field.attrs = new_attrs.0.iter().cloned().cloned().collect(); } diff --git a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs index bfcdd14b..cb74e5bb 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs @@ -3,4 +3,4 @@ pub(crate) use proc_macro2::TokenStream as TokenStream2; pub(crate) use proc_macro_error::{abort, abort_call_site, emit_error, proc_macro_error}; pub(crate) use quote::{quote, ToTokens, TokenStreamExt}; // ToTokens is implicitly used as a trait pub(crate) use regex::Regex; -pub(crate) use syn::{spanned::Spanned, DeriveInput, Ident, Meta}; +pub(crate) use syn::{spanned::Spanned, DeriveInput, FieldsNamed, Ident, Meta}; From d17c21813ff5bfc0a09b2c5d1d0d9f83126f7d89 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Fri, 11 Aug 2023 16:23:26 -0600 Subject: [PATCH 14/18] got regex to work --- rust/fastsim-core/Cargo.toml | 4 +- .../fastsim-proc-macros/src/doc_field.rs | 48 +++++++++++++++---- rust/fastsim-core/src/vehicle.rs | 4 +- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/rust/fastsim-core/Cargo.toml b/rust/fastsim-core/Cargo.toml index ce738e2b..229a0070 100644 --- a/rust/fastsim-core/Cargo.toml +++ b/rust/fastsim-core/Cargo.toml @@ -11,9 +11,9 @@ repository = "https://github.com/NREL/fastsim" [dependencies] # uncomment next line for any development work in fastsim-proc-macros -# proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" } +proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" } # comment next line for any development work in fastsim-proc-macros -proc-macros = { package = "fastsim-proc-macros", version = "0.1.4" } +# proc-macros = { package = "fastsim-proc-macros", version = "0.1.4" } pyo3 = { workspace = true, features = [ "extension-module", "anyhow", diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index 797cc66e..d277c5ef 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -5,17 +5,20 @@ use crate::imports::*; pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); - if let syn::Fields::Named(syn::FieldsNamed { named, .. }) = &mut ast.fields { + let new_doc_fields: FieldsNamed = if let syn::Fields::Named(FieldsNamed { named, .. }) = + &mut ast.fields + { let mut new_doc_fields = String::from("{\n"); for field in named.iter_mut() { let mut skip_doc = false; remove_handled_attrs(field, &mut skip_doc); + if !skip_doc { // create new doc field as string new_doc_fields.push_str(&format!( - "/// {} documentation -- e.g. info about calibration/validation of vehicle, - /// links to reports or other long-form documentation. + " + /// {} documentation -- e.g. info about calibration/validation. pub {}_doc: Option,\n", field.ident.as_ref().unwrap(), field.ident.as_ref().unwrap() @@ -24,14 +27,41 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { } new_doc_fields.push_str( - "/// Vehicle level documentation -- e.g. info about calibration/validation of this parameter, - /// links to reports or other long-form documentation. - pub doc: Option\n}", + " + /// Vehicle level documentation -- e.g. info about calibration/validation of vehicle + /// and/or links to reports or other long-form documentation. + pub doc: Option\n}", ); - let new_doc_fields: syn::FieldsNamed = syn::parse_str::(&new_doc_fields) - .unwrap_or_else(|e| abort_call_site!("{}", e)); + syn::parse_str::(&new_doc_fields).unwrap_or_else(|e| abort_call_site!("{}", e)) + } else { + abort_call_site!("Expected use on struct with named fields.") + }; - named.extend(new_doc_fields.named); + if let syn::Fields::Named(FieldsNamed { named, .. }) = &mut ast.fields { + // named.extend(new_doc_fields.named); + + let old_named = named.clone(); + named.clear(); + for old in old_named.into_iter() { + named.push(old.clone()); + if let Some(i) = new_doc_fields.named.iter().position(|x| { + let re = Regex::new(&old.ident.to_token_stream().to_string()) + .unwrap_or_else(|e| abort!(old.span(), e)); + re.is_match(&x.ident.to_token_stream().to_string()) + }) { + dbg!(i); + // named.extend::>( + // syn::parse_str::(&format!( + // "{}_doc", + // new_doc_fields.named[i].clone().to_token_stream() + // )) + // .unwrap() + // .named, + // ); + // might be good to also remove field `i` from `new_doc_fields` + // dbg!(named.iter().last().to_token_stream().to_string()); + } + } } else { abort_call_site!("`doc_field` proc macro works only on named structs."); }; diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index a951a5da..8e1b22c8 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -180,7 +180,7 @@ pub struct RustVehicle { #[serde(alias = "maxFuelConvKw")] #[validate(range(min = 0))] pub fc_max_kw: f64, - /// Fuel converter output power percentage map, x-values of [fc_eff_map](RustVehicle::fc_eff_map) + /// Fuel converter output power percentage map, x values of [fc_eff_map](RustVehicle::fc_eff_map) #[serde(alias = "fcPwrOutPerc")] pub fc_pwr_out_perc: Array1, /// Fuel converter efficiency map @@ -218,7 +218,7 @@ pub struct RustVehicle { #[serde(alias = "mcMaxElecInKw")] #[validate(range(min = 0))] pub mc_max_kw: f64, - /// Electric motor output power percentage map, x-values of [mc_eff_map](RustVehicle::mc_eff_map) + /// Electric motor output power percentage map, x values of [mc_eff_map](RustVehicle::mc_eff_map) #[serde(alias = "mcPwrOutPerc")] pub mc_pwr_out_perc: Array1, /// Electric motor efficiency map From f57f0056ccc933836b0410592f8e2781d8739995 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Sat, 12 Aug 2023 13:23:01 -0600 Subject: [PATCH 15/18] struggling with getting Field from proc_macro2::TokenStream --- .../fastsim-proc-macros/src/doc_field.rs | 56 ++++++++----------- .../fastsim-proc-macros/src/imports.rs | 2 +- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index d277c5ef..a9065716 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -5,40 +5,38 @@ use crate::imports::*; pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); - let new_doc_fields: FieldsNamed = if let syn::Fields::Named(FieldsNamed { named, .. }) = - &mut ast.fields - { - let mut new_doc_fields = String::from("{\n"); - + if let syn::Fields::Named(FieldsNamed { named, .. }) = &mut ast.fields { + let mut new_doc_fields = TokenStream2::new(); for field in named.iter_mut() { let mut skip_doc = false; remove_handled_attrs(field, &mut skip_doc); if !skip_doc { - // create new doc field as string - new_doc_fields.push_str(&format!( - " - /// {} documentation -- e.g. info about calibration/validation. - pub {}_doc: Option,\n", - field.ident.as_ref().unwrap(), - field.ident.as_ref().unwrap() - )); + let field_name = format!("{}_doc", field.ident.to_token_stream()); + new_doc_fields.extend::(quote! { + /// documentation -- e.g. info about calibration/validation. + pub #field_name: Option, + }); } } - new_doc_fields.push_str( - " + new_doc_fields.extend::(quote! { /// Vehicle level documentation -- e.g. info about calibration/validation of vehicle /// and/or links to reports or other long-form documentation. - pub doc: Option\n}", - ); - syn::parse_str::(&new_doc_fields).unwrap_or_else(|e| abort_call_site!("{}", e)) - } else { - abort_call_site!("Expected use on struct with named fields.") - }; + pub doc: Option, + }); + dbg!(&new_doc_fields); + let new_doc_fields: TokenStream2 = quote! { + /// Dummy struct that will not be used anywhere + pub struct Dummy { + #new_doc_fields + } + }; + dbg!(&new_doc_fields); - if let syn::Fields::Named(FieldsNamed { named, .. }) = &mut ast.fields { - // named.extend(new_doc_fields.named); + let new_doc_fields: FieldsNamed = syn::parse2(new_doc_fields) + .map_err(|e| format!("[{}:{}] `parse2` failed. {}", file!(), line!(), e)) + .unwrap(); let old_named = named.clone(); named.clear(); @@ -50,20 +48,12 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { re.is_match(&x.ident.to_token_stream().to_string()) }) { dbg!(i); - // named.extend::>( - // syn::parse_str::(&format!( - // "{}_doc", - // new_doc_fields.named[i].clone().to_token_stream() - // )) - // .unwrap() - // .named, - // ); + named.push(new_doc_fields.named[i].clone()); // might be good to also remove field `i` from `new_doc_fields` - // dbg!(named.iter().last().to_token_stream().to_string()); } } } else { - abort_call_site!("`doc_field` proc macro works only on named structs."); + abort_call_site!("Expected use on struct with named fields.") }; ast.into_token_stream().into() diff --git a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs index cb74e5bb..04cf8c47 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs @@ -3,4 +3,4 @@ pub(crate) use proc_macro2::TokenStream as TokenStream2; pub(crate) use proc_macro_error::{abort, abort_call_site, emit_error, proc_macro_error}; pub(crate) use quote::{quote, ToTokens, TokenStreamExt}; // ToTokens is implicitly used as a trait pub(crate) use regex::Regex; -pub(crate) use syn::{spanned::Spanned, DeriveInput, FieldsNamed, Ident, Meta}; +pub(crate) use syn::{spanned::Spanned, DeriveInput, Field, FieldsNamed, Ident, Meta}; From 9c3b09ba55eefdeb6c8d42e0578fc09133979a02 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Mon, 14 Aug 2023 16:32:51 -0600 Subject: [PATCH 16/18] builds with * and *_doc adjacent! --- .../fastsim-proc-macros/src/doc_field.rs | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index a9065716..a9a8a77d 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -3,60 +3,68 @@ use crate::imports::*; pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mut ast = syn::parse_macro_input!(item as syn::ItemStruct); + let mut item_struct = syn::parse_macro_input!(item as syn::ItemStruct); - if let syn::Fields::Named(FieldsNamed { named, .. }) = &mut ast.fields { - let mut new_doc_fields = TokenStream2::new(); + let new_fields = if let syn::Fields::Named(FieldsNamed { named, .. }) = &mut item_struct.fields + { + let mut new_doc_fields: Vec = Vec::new(); for field in named.iter_mut() { let mut skip_doc = false; remove_handled_attrs(field, &mut skip_doc); if !skip_doc { - let field_name = format!("{}_doc", field.ident.to_token_stream()); - new_doc_fields.extend::(quote! { - /// documentation -- e.g. info about calibration/validation. + let field_name: TokenStream2 = format!("{}_doc", field.ident.to_token_stream()) + .parse() + .unwrap(); + new_doc_fields.push(quote! { + /// # String for documentation -- e.g. info about calibration/validation. pub #field_name: Option, }); } } - new_doc_fields.extend::(quote! { + new_doc_fields.push(quote! { /// Vehicle level documentation -- e.g. info about calibration/validation of vehicle /// and/or links to reports or other long-form documentation. pub doc: Option, }); - dbg!(&new_doc_fields); - let new_doc_fields: TokenStream2 = quote! { - /// Dummy struct that will not be used anywhere - pub struct Dummy { - #new_doc_fields - } - }; - dbg!(&new_doc_fields); - let new_doc_fields: FieldsNamed = syn::parse2(new_doc_fields) - .map_err(|e| format!("[{}:{}] `parse2` failed. {}", file!(), line!(), e)) - .unwrap(); + let mut new_fields = TokenStream2::new(); - let old_named = named.clone(); - named.clear(); - for old in old_named.into_iter() { - named.push(old.clone()); - if let Some(i) = new_doc_fields.named.iter().position(|x| { - let re = Regex::new(&old.ident.to_token_stream().to_string()) - .unwrap_or_else(|e| abort!(old.span(), e)); - re.is_match(&x.ident.to_token_stream().to_string()) + for orig_field in named.iter() { + // fields need to have comma added + new_fields.extend( + format!("{},", orig_field.to_token_stream()) + .parse::() + .unwrap(), + ); + if let Some(i) = new_doc_fields.iter().position(|x| { + let re = Regex::new(&orig_field.ident.to_token_stream().to_string()) + .unwrap_or_else(|err| abort!(orig_field.span(), err)); + re.is_match(&x.to_token_stream().to_string()) }) { - dbg!(i); - named.push(new_doc_fields.named[i].clone()); + new_fields.extend(new_doc_fields[i].clone()); + new_doc_fields.remove(i); // might be good to also remove field `i` from `new_doc_fields` } } + new_fields } else { abort_call_site!("Expected use on struct with named fields.") }; - ast.into_token_stream().into() + let struct_ident = item_struct.ident.to_token_stream(); + let struct_vis = item_struct.vis; + let struct_attrs = item_struct.attrs; + + let output: TokenStream2 = quote! { + #(#struct_attrs)* + #struct_vis struct #struct_ident { + #new_fields + } + }; + + output.into_token_stream().into() } /// Remove field attributes, i.e. attributes that are not handled by the [doc_field] macro From 38517f1fec8eb91ca1b6e98f7b4ac402491954d9 Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Mon, 14 Aug 2023 17:26:37 -0600 Subject: [PATCH 17/18] added comments and other cleanup --- rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs | 10 ++++------ rust/fastsim-core/fastsim-proc-macros/src/imports.rs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs index a9a8a77d..dbc48085 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/doc_field.rs @@ -17,22 +17,21 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { .parse() .unwrap(); new_doc_fields.push(quote! { - /// # String for documentation -- e.g. info about calibration/validation. + /// String for documentation -- e.g. info about calibration/validation. pub #field_name: Option, }); } } - new_doc_fields.push(quote! { + let mut new_fields = TokenStream2::new(); + new_fields.extend(quote! { /// Vehicle level documentation -- e.g. info about calibration/validation of vehicle /// and/or links to reports or other long-form documentation. pub doc: Option, }); - let mut new_fields = TokenStream2::new(); - for orig_field in named.iter() { - // fields need to have comma added + // fields from `orig_field` need to have comma added new_fields.extend( format!("{},", orig_field.to_token_stream()) .parse::() @@ -45,7 +44,6 @@ pub fn doc_field(_attr: TokenStream, item: TokenStream) -> TokenStream { }) { new_fields.extend(new_doc_fields[i].clone()); new_doc_fields.remove(i); - // might be good to also remove field `i` from `new_doc_fields` } } new_fields diff --git a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs index 04cf8c47..cb74e5bb 100644 --- a/rust/fastsim-core/fastsim-proc-macros/src/imports.rs +++ b/rust/fastsim-core/fastsim-proc-macros/src/imports.rs @@ -3,4 +3,4 @@ pub(crate) use proc_macro2::TokenStream as TokenStream2; pub(crate) use proc_macro_error::{abort, abort_call_site, emit_error, proc_macro_error}; pub(crate) use quote::{quote, ToTokens, TokenStreamExt}; // ToTokens is implicitly used as a trait pub(crate) use regex::Regex; -pub(crate) use syn::{spanned::Spanned, DeriveInput, Field, FieldsNamed, Ident, Meta}; +pub(crate) use syn::{spanned::Spanned, DeriveInput, FieldsNamed, Ident, Meta}; From b892eac27cf4b7c288d61db8eebb1881787f777a Mon Sep 17 00:00:00 2001 From: Chad Baker Date: Mon, 14 Aug 2023 17:45:28 -0600 Subject: [PATCH 18/18] made it so that tests pass on Fedora --- build_and_test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build_and_test.sh b/build_and_test.sh index 744dbfcd..810eae1c 100755 --- a/build_and_test.sh +++ b/build_and_test.sh @@ -1,3 +1,4 @@ -(cd rust/ && cargo test) && \ +(cd rust/fastsim-core/ && cargo test) && \ +(cd rust/fastsim-cli/ && cargo test) && \ pip install -qe ".[dev]" && \ pytest -v python/fastsim/tests/ \ No newline at end of file