1use sim_lib_femm_core::{FemmError, FemmResult};
7use sim_lib_femm_field::{Field, Projection};
8use sim_lib_femm_mesh::FemmModel;
9use sim_lib_femm_post::FemmSolution;
10
11use crate::support::{
12 formulation_name, parse_atom_field, parse_json_params, parse_json_string_field,
13 parse_json_u64_field, parse_lisp_params, parse_u64_field, physics_name,
14};
15
16#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct ModelSummary {
22 pub id: u64,
24 pub name: String,
26 pub physics: String,
28 pub formulation: String,
30 pub params: Vec<String>,
32}
33
34#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct SolutionSummary {
39 pub id: u64,
41 pub model_id: u64,
43 pub physics: String,
45 pub formulation: String,
47 pub params: Vec<String>,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct FieldSummary {
56 pub solution_id: u64,
58 pub projection: String,
60}
61
62pub fn model_to_lisp(model: &FemmModel) -> String {
64 format!(
65 "(femm/model :id {} :name {} :physics {} :formulation {} :params ({}))",
66 model.id.0,
67 model.name,
68 physics_name(&model.physics),
69 formulation_name(&model.formulation),
70 model
71 .inputs
72 .iter()
73 .map(|param| param.name.to_string())
74 .collect::<Vec<_>>()
75 .join(" ")
76 )
77}
78
79pub fn model_from_lisp(text: &str) -> FemmResult<ModelSummary> {
94 Ok(ModelSummary {
95 id: parse_u64_field(text, ":id ")?,
96 name: parse_atom_field(text, ":name ")?,
97 physics: parse_atom_field(text, ":physics ")?,
98 formulation: parse_atom_field(text, ":formulation ")?,
99 params: parse_lisp_params(text)?,
100 })
101}
102
103pub fn model_to_json(model: &FemmModel) -> String {
105 let params = model
106 .inputs
107 .iter()
108 .map(|param| format!("\"{}\"", param.name))
109 .collect::<Vec<_>>()
110 .join(",");
111 format!(
112 "{{\"id\":{},\"name\":\"{}\",\"physics\":\"{}\",\"formulation\":\"{}\",\"params\":[{}]}}",
113 model.id.0,
114 model.name,
115 physics_name(&model.physics),
116 formulation_name(&model.formulation),
117 params
118 )
119}
120
121pub fn model_from_json(text: &str) -> FemmResult<ModelSummary> {
136 Ok(ModelSummary {
137 id: parse_json_u64_field(text, "\"id\":")?,
138 name: parse_json_string_field(text, "\"name\":\"")?,
139 physics: parse_json_string_field(text, "\"physics\":\"")?,
140 formulation: parse_json_string_field(text, "\"formulation\":\"")?,
141 params: parse_json_params(text)?,
142 })
143}
144
145pub fn reject_unknown_binary_tag(tag: u8) -> FemmResult<()> {
159 match tag {
160 0xF0..=0xF7 => Ok(()),
161 _ => Err(FemmError::InvalidGeometry(format!(
162 "unknown frame tag {tag:#x}"
163 ))),
164 }
165}
166
167pub fn solution_to_lisp(solution: &FemmSolution) -> String {
169 format!(
170 "(femm/solution :id {} :model {} :physics {} :formulation {} :params ({}))",
171 solution.id.0,
172 solution.model_id.0,
173 physics_name(&solution.physics),
174 formulation_name(&solution.formulation),
175 solution
176 .params
177 .entries
178 .iter()
179 .map(|(param, _)| param.to_string())
180 .collect::<Vec<_>>()
181 .join(" ")
182 )
183}
184
185pub fn solution_from_lisp(text: &str) -> FemmResult<SolutionSummary> {
187 Ok(SolutionSummary {
188 id: parse_u64_field(text, ":id ")?,
189 model_id: parse_u64_field(text, ":model ")?,
190 physics: parse_atom_field(text, ":physics ")?,
191 formulation: parse_atom_field(text, ":formulation ")?,
192 params: parse_lisp_params(text)?,
193 })
194}
195
196pub fn solution_to_json(solution: &FemmSolution) -> String {
198 let params = solution
199 .params
200 .entries
201 .iter()
202 .map(|(param, _)| format!("\"{param}\""))
203 .collect::<Vec<_>>()
204 .join(",");
205 format!(
206 "{{\"id\":{},\"model\":{},\"physics\":\"{}\",\"formulation\":\"{}\",\"params\":[{}]}}",
207 solution.id.0,
208 solution.model_id.0,
209 physics_name(&solution.physics),
210 formulation_name(&solution.formulation),
211 params
212 )
213}
214
215pub fn solution_from_json(text: &str) -> FemmResult<SolutionSummary> {
217 Ok(SolutionSummary {
218 id: parse_json_u64_field(text, "\"id\":")?,
219 model_id: parse_json_u64_field(text, "\"model\":")?,
220 physics: parse_json_string_field(text, "\"physics\":\"")?,
221 formulation: parse_json_string_field(text, "\"formulation\":\"")?,
222 params: parse_json_params(text)?,
223 })
224}
225
226pub fn field_read_construct(field: &Field) -> String {
228 format!(
229 "#(femm/Field v1 {} \"{}\")",
230 field.solution_id().0,
231 projection_name(&field.projection())
232 )
233}
234
235pub fn field_from_read_construct(text: &str) -> FemmResult<FieldSummary> {
250 let (rest, versioned) = if let Some(rest) = text
251 .strip_prefix("#(femm/Field v1 ")
252 .and_then(|rest| rest.strip_suffix(')'))
253 {
254 (rest, true)
255 } else {
256 let rest = text
257 .strip_prefix("#(femm/field ")
258 .and_then(|rest| rest.strip_suffix(')'))
259 .ok_or_else(|| FemmError::InvalidGeometry("bad field read-construct".to_owned()))?;
260 (rest, false)
261 };
262 let mut parts = rest.split_whitespace();
263 let solution_id = parts
264 .next()
265 .and_then(|part| part.parse::<u64>().ok())
266 .ok_or_else(|| FemmError::InvalidGeometry("bad field solution id".to_owned()))?;
267 let projection = parts
268 .next()
269 .ok_or_else(|| FemmError::InvalidGeometry("missing field projection".to_owned()))?;
270 Ok(FieldSummary {
271 solution_id,
272 projection: if versioned {
273 projection.trim_matches('"').to_owned()
274 } else {
275 projection.to_owned()
276 },
277 })
278}
279
280fn projection_name(projection: &Projection) -> String {
281 match projection {
282 Projection::Potential => "potential".to_owned(),
283 Projection::Bx => "bx".to_owned(),
284 Projection::By => "by".to_owned(),
285 Projection::Bmag => "bmag".to_owned(),
286 Projection::Ex => "ex".to_owned(),
287 Projection::Ey => "ey".to_owned(),
288 Projection::Emag => "emag".to_owned(),
289 Projection::HeatFluxMag => "heat-flux-mag".to_owned(),
290 Projection::Custom(symbol) => symbol.to_string(),
291 }
292}