1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::io::Write;
4pub mod agsi;
5use crate::agsi::AgsiDataParameterValue;
6
7#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum ConvertType {
9 SoilParams,
10 GroundModel,
11}
12
13impl ConvertType {
14 pub fn from_str(s: &str) -> Result<Self, &'static str> {
15 match s.to_lowercase().as_str() {
16 "soilparams" => Ok(ConvertType::SoilParams),
17 "groundmodel" => Ok(ConvertType::GroundModel),
18 _ => Err("Invalid convert type. Use 'soilparams' or 'groundmodel'"),
19 }
20 }
21}
22
23pub fn convert_agsi_file(
24 file_path: &str,
25 convert_type: ConvertType,
26 output_path: Option<&str>,
27) -> Result<String, Box<dyn std::error::Error>> {
28 let text = fs::read_to_string(file_path)?;
29 let agsi: serde_json::Value = serde_json::from_str(&text)?;
30
31 let output_json = match convert_type {
32 ConvertType::GroundModel => {
33 let ground_model = GroundModel::from_agsi_file(&agsi);
34 serde_json::to_string_pretty(&ground_model)?
35 }
36 ConvertType::SoilParams => {
37 let mut soil_params = Vec::new();
38
39 if let Some(agsi_models) = agsi["agsiModel"].as_array() {
40 if let Some(first_model) = agsi_models.get(0) {
41 if let Some(elements) = first_model["agsiModelElement"].as_array() {
42 for element in elements {
43 if let Some(param_values) = element["agsiDataParameterValue"].as_array()
44 {
45 let params_data: Vec<AgsiDataParameterValue> = param_values
46 .iter()
47 .filter_map(|p| {
48 Some(AgsiDataParameterValue {
49 code_id: p["codeID"].as_str()?.parse().ok()?,
50 case_id: None,
51 data_id: None,
52 remarks: None,
53 value_numeric: p["valueNumeric"].as_f64(),
54 value_profile: None,
55 value_profile_ind_var_code_id: None,
56 value_text: None,
57 })
58 })
59 .collect();
60
61 if !params_data.is_empty() {
62 soil_params
63 .push(SoilParams::from_agsi_data_parameters(¶ms_data));
64 }
65 }
66 }
67 }
68 }
69 }
70
71 serde_json::to_string_pretty(&soil_params)?
72 }
73 };
74
75 if let Some(output_file) = output_path {
76 let mut file = fs::File::create(output_file)?;
77 file.write_all(output_json.as_bytes())?;
78 println!("Output written to {}", output_file);
79 } else {
80 println!("{}", output_json);
81 }
82
83 Ok(output_json)
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct AdvancedParameter {
88 pub name: String,
89 pub value: f64,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
93pub enum SoilType {
94 Cohesive,
95 Granular,
96 Rock,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct PartialFactors {
101 pub gamma_phi: f64,
102 pub gamma_c: f64,
103 pub gamma_gamma: f64,
104 pub gamma_cu: f64,
105}
106
107impl PartialFactors {
108 pub fn new(gamma_phi: f64, gamma_c: f64, gamma_gamma: f64, gamma_cu: f64) -> Self {
109 PartialFactors {
110 gamma_phi,
111 gamma_c,
112 gamma_gamma,
113 gamma_cu,
114 }
115 }
116}
117
118impl Default for PartialFactors {
119 fn default() -> Self {
120 PartialFactors {
121 gamma_phi: 1.0,
122 gamma_c: 1.0,
123 gamma_gamma: 1.0,
124 gamma_cu: 1.0,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SoilParams {
131 pub reference: String,
132 pub behaviour: SoilType,
133 pub phi_prime: Option<f64>,
134 pub c_prime: Option<f64>,
135 pub unit_weight: f64,
136 pub cu: Option<f64>,
137 pub mv: f64,
138 pub youngs_modulus: f64,
139 pub poissons_ratio: f64,
140 pub coefficient_of_consolidation: f64,
141 pub gsi: Option<f64>,
142 pub ucs: Option<f64>,
143 pub mi: Option<f64>,
144 pub disturbance: f64,
145 pub advanced_parameters: Option<Vec<AdvancedParameter>>,
146 pub factored: bool,
147 pub factors: Option<PartialFactors>,
148}
149
150impl SoilParams {
151 pub fn new(
152 reference: String,
153 mv: f64,
154 youngs_modulus: f64,
155 poissons_ratio: f64,
156 coefficient_of_consolidation: f64,
157 behaviour: SoilType,
158 unit_weight: f64,
159 ) -> Self {
160 SoilParams {
161 reference,
162 behaviour,
163 phi_prime: None,
164 c_prime: None,
165 unit_weight,
166 cu: None,
167 mv,
168 youngs_modulus,
169 poissons_ratio,
170 coefficient_of_consolidation,
171 gsi: None,
172 ucs: None,
173 mi: None,
174 disturbance: 0.0,
175 advanced_parameters: None,
176 factored: false,
177 factors: None,
178 }
179 }
180
181 pub fn from_agsi_data_parameters(data: &[AgsiDataParameterValue]) -> Self {
182 let mut sp = SoilParams::default();
183
184 for item in data {
185 match item.code_id.as_str() {
186 "UnitWeight" => {
187 sp.unit_weight = item.value_numeric.unwrap_or(0.0);
188 }
189 "AngleFriction" | "EffectiveFrictionAngle" => {
190 sp.phi_prime = item.value_numeric;
191 }
192 "UndrainedShearStrength" => {
193 if let Some(value) = item.value_numeric {
194 if value > 0.0 {
195 sp.cu = Some(value);
196 sp.behaviour = SoilType::Cohesive;
197 } else {
198 sp.cu = Some(0.0);
199 }
200 } else {
201 sp.cu = Some(0.0);
202 }
203 }
204 "YoungsModulus" => {
205 sp.youngs_modulus = item.value_numeric.unwrap_or(0.0);
206 }
207 "Cohesion" | "EffectiveCohesion" => {
208 sp.c_prime = item.value_numeric;
209 }
210 "ModulusOfVolumeCompressibility" => {
211 sp.mv = item.value_numeric.unwrap_or(0.0);
212 }
213 "GeologicalStrengthIndex" => {
214 sp.gsi = item.value_numeric;
215 }
216 "UnconfinedCompressiveStrength" => {
217 sp.ucs = item.value_numeric;
218 if item.value_numeric.is_some() {
219 sp.behaviour = SoilType::Rock;
220 }
221 }
222 "HoekBrownParamMi" => {
223 sp.mi = item.value_numeric;
224 }
225 "Disturbance" => {
226 sp.disturbance = item.value_numeric.unwrap_or(0.0);
227 }
228 _ => {
229 if sp.advanced_parameters.is_none() {
230 sp.advanced_parameters = Some(Vec::new());
231 }
232 sp.advanced_parameters
233 .as_mut()
234 .unwrap()
235 .push(AdvancedParameter {
236 name: item.code_id.to_string(),
237 value: item.value_numeric.unwrap_or(0.0),
238 });
239 }
240 }
241 }
242
243 sp
244 }
245
246 pub fn with_all_fields(
247 reference: String,
248 behaviour: SoilType,
249 phi_prime: Option<f64>,
250 c_prime: Option<f64>,
251 unit_weight: f64,
252 cu: Option<f64>,
253 mv: f64,
254 youngs_modulus: f64,
255 poissons_ratio: f64,
256 coefficient_of_consolidation: f64,
257 gsi: Option<f64>,
258 ucs: Option<f64>,
259 mi: Option<f64>,
260 disturbance: f64,
261 ) -> Self {
262 SoilParams {
263 reference,
264 behaviour,
265 phi_prime,
266 c_prime,
267 unit_weight,
268 cu,
269 mv,
270 youngs_modulus,
271 poissons_ratio,
272 coefficient_of_consolidation,
273 gsi,
274 ucs,
275 mi,
276 disturbance,
277 advanced_parameters: None,
278 factored: false,
279 factors: None,
280 }
281 }
282
283 pub fn apply_partial_factors(&self, pf: &PartialFactors) -> SoilParams {
284 let mut result = self.clone();
285
286 if let Some(phi) = self.phi_prime {
287 result.phi_prime = Some((phi.tan() / pf.gamma_phi).atan());
288 }
289
290 if let Some(c) = self.c_prime {
291 result.c_prime = Some(c / pf.gamma_c);
292 }
293
294 result.unit_weight = self.unit_weight / pf.gamma_gamma;
295
296 if let Some(cu_val) = self.cu {
297 result.cu = Some(cu_val / pf.gamma_cu);
298 }
299
300 result.factored = true;
301 result.factors = Some(pf.clone());
302 result
303 }
304
305 pub fn remove_partial_factors(&self) -> Result<SoilParams, &'static str> {
306 let pf = self
307 .factors
308 .as_ref()
309 .ok_or("No factors stored on this instance")?;
310 let mut result = self.clone();
311
312 if let Some(phi) = self.phi_prime {
313 result.phi_prime = Some((phi.tan() * pf.gamma_phi).atan());
314 }
315
316 if let Some(c) = self.c_prime {
317 result.c_prime = Some(c * pf.gamma_c);
318 }
319
320 result.unit_weight = self.unit_weight * pf.gamma_gamma;
321
322 if let Some(cu_val) = self.cu {
323 result.cu = Some(cu_val * pf.gamma_cu);
324 }
325
326 result.factored = false;
327 result.factors = None;
328 Ok(result)
329 }
330
331 fn coulomb_ka(&self, phi: f64, beta: f64) -> f64 {
332 let cos_beta = beta.cos();
333 let cos_phi = phi.cos();
334 let discriminant = cos_beta.powi(2) - cos_phi.powi(2);
335 let sqrt_disc = discriminant.sqrt();
336
337 (cos_beta - sqrt_disc) / (cos_beta + sqrt_disc)
338 }
339
340 pub fn get_k_active(&self, slope: Option<f64>) -> Result<f64, &'static str> {
341 let phi = self.phi_prime.ok_or("Phi must be a value")?;
342
343 match slope {
344 None => Ok((1.0 - phi.sin()) / (1.0 + phi.sin())),
345 Some(beta) => Ok(self.coulomb_ka(phi, beta)),
346 }
347 }
348
349 pub fn get_k_passive(&self, slope: Option<f64>) -> Result<f64, &'static str> {
350 match slope {
351 None => Ok(1.0 / self.get_k_active(None)?),
352 Some(_) => self.get_k_passive(None), }
354 }
355
356 pub fn k0(&self) -> Result<f64, &'static str> {
357 let phi = self.phi_prime.ok_or("Phi must be a value")?;
358 Ok(1.0 - phi.sin())
359 }
360
361 pub fn mb(&self) -> Result<f64, &'static str> {
362 let mi = self
363 .mi
364 .ok_or("mi (rock mass factor) must be set for mb calculation")?;
365 let gsi = self
366 .gsi
367 .ok_or("gsi (geological strength index) must be set for mb calculation")?;
368
369 let exponent = (gsi - 100.0) / (28.0 - (14.0 * self.disturbance));
370 Ok(mi * exponent.exp())
371 }
372
373 pub fn s(&self) -> Result<f64, &'static str> {
374 let gsi = self
375 .gsi
376 .ok_or("gsi (geological strength index) must be set for s calculation")?;
377
378 let exponent = (gsi - 100.0) / (9.0 - (3.0 * self.disturbance));
379 Ok(exponent.exp())
380 }
381
382 pub fn a(&self) -> Result<f64, &'static str> {
383 let gsi = self
384 .gsi
385 .ok_or("gsi (geological strength index) must be set for a calculation")?;
386
387 Ok(0.5 + ((-gsi / 15.0).exp() - (-20.0_f64 / 3.0).exp()))
388 }
389
390 fn hb_to_mc_conv(&self, sig3: f64) -> Result<f64, &'static str> {
391 let ucs = self.ucs.ok_or("ucs (unconfined compressive strength) must be set and nonzero for Hoek-Brown conversion")?;
392 if ucs == 0.0 {
393 return Err(
394 "ucs (unconfined compressive strength) must be nonzero for Hoek-Brown conversion",
395 );
396 }
397
398 let sig3n = sig3 / ucs;
399 let first_bit = 6.0 * self.a()? * self.mb()?;
400 let second_bit = (self.s()? + (self.mb()? * sig3n)).powf(self.a()? - 1.0);
401
402 Ok(first_bit * second_bit)
403 }
404
405 pub fn hb_equiv_phi_ang(&self, sig3: f64) -> Result<f64, &'static str> {
406 let top = self.hb_to_mc_conv(sig3)?;
407 let bottom = (2.0 * (1.0 + self.a()?) * (2.0 + self.a()?)) + top;
408
409 if bottom == 0.0 {
410 return Err("Denominator for equivalent phi angle calculation is zero");
411 }
412
413 Ok(top / bottom)
414 }
415
416 pub fn hb_equiv_c_prime(&self, sig3: f64) -> Result<f64, &'static str> {
417 let ucs = self.ucs.ok_or("ucs (unconfined compressive strength) must be set and nonzero for equivalent cohesion calculation")?;
418 if ucs == 0.0 {
419 return Err("ucs must be nonzero for equivalent cohesion calculation");
420 }
421
422 let sig3n = sig3 / ucs;
423 let a_val = self.a()?;
424 let s_val = self.s()?;
425 let mb_val = self.mb()?;
426
427 let first_brack = ((1.0 + (2.0 * a_val)) * s_val) + ((1.0 - a_val) * mb_val * sig3n);
428 let top = ucs * first_brack * ((s_val * mb_val * sig3n).powf(a_val - 1.0));
429 let denom = (1.0 + a_val) * (2.0 + a_val);
430
431 if denom == 0.0 {
432 return Err("Denominator for equivalent cohesion calculation is zero");
433 }
434
435 let sqrt_bit = 1.0 + (self.hb_to_mc_conv(sig3)? / denom);
436 if sqrt_bit < 0.0 {
437 return Err("sqrtBit for equivalent cohesion calculation is negative");
438 }
439
440 let bottom = denom * sqrt_bit.sqrt();
441 if bottom == 0.0 {
442 return Err("Denominator for equivalent cohesion calculation is zero");
443 }
444
445 Ok(top / bottom)
446 }
447
448 pub fn rock_e_val(&self) -> Result<f64, &'static str> {
449 let ucs = self.ucs.ok_or(
450 "ucs (unconfined compressive strength) must be set for Young's Modulus calculation",
451 )?;
452 let gsi = self
453 .gsi
454 .ok_or("gsi (geological strength index) must be set for Young's Modulus calculation")?;
455
456 let rock_val = if ucs < 100.0 { 1.0 } else { ucs / 100.0 };
457
458 Ok((1.0 - (self.disturbance / 2.0))
459 * rock_val.sqrt()
460 * (10.0_f64.powf((gsi - 10.0) / 40.0)))
461 }
462
463 pub fn convert_equivalent_rock(&self, sig3: f64) -> Result<SoilParams, &'static str> {
464 Ok(SoilParams::with_all_fields(
465 self.reference.clone(),
466 self.behaviour,
467 Some(self.hb_equiv_phi_ang(sig3)?),
468 Some(self.hb_equiv_c_prime(sig3)?),
469 self.unit_weight,
470 self.cu,
471 self.mv,
472 self.rock_e_val()?,
473 self.poissons_ratio,
474 self.coefficient_of_consolidation,
475 self.gsi,
476 self.ucs,
477 self.mi,
478 self.disturbance,
479 ))
480 }
481}
482
483impl Default for SoilParams {
484 fn default() -> Self {
485 SoilParams {
486 reference: String::new(),
487 behaviour: SoilType::Granular,
488 phi_prime: None, c_prime: None,
490 unit_weight: 0.0,
491 cu: None,
492 mv: 0.0,
493 youngs_modulus: 0.0,
494 poissons_ratio: 0.0,
495 coefficient_of_consolidation: 0.0,
496 gsi: None,
497 ucs: None,
498 mi: None,
499 disturbance: 0.0,
500 advanced_parameters: None,
501 factored: false,
502 factors: None,
503 }
504 }
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct SoilLayer {
509 pub unit_reference: String,
510 pub top_level: f64,
511 pub base_level: Option<f64>,
512 pub base_unit_reference: Option<String>,
513 pub typical_description: String,
514 pub geol_code: String,
515 pub reference: String, }
517
518impl SoilLayer {
519 pub fn new(top_level: f64, base_level: f64, reference: String) -> Self {
520 SoilLayer {
521 unit_reference: String::new(),
522 top_level,
523 base_level: Some(base_level),
524 base_unit_reference: None,
525 typical_description: String::new(),
526 geol_code: String::new(),
527 reference,
528 }
529 }
530
531 pub fn with_unit_reference(
532 top_level: f64,
533 base_level: f64,
534 reference: String,
535 unit_reference: String,
536 ) -> Self {
537 SoilLayer {
538 unit_reference,
539 top_level,
540 base_level: Some(base_level),
541 base_unit_reference: None,
542 typical_description: String::new(),
543 geol_code: String::new(),
544 reference,
545 }
546 }
547
548 pub fn with_all_fields(
549 unit_reference: String,
550 top_level: f64,
551 base_level: Option<f64>,
552 base_unit_reference: Option<String>,
553 typical_description: String,
554 geol_code: String,
555 ) -> Self {
556 SoilLayer {
557 unit_reference,
558 top_level,
559 base_level,
560 base_unit_reference,
561 typical_description,
562 geol_code,
563 reference: String::new(),
564 }
565 }
566
567 pub fn excavate_layer(&self, level: f64) -> (SoilLayer, bool) {
568 let base_level = self.base_level.unwrap_or(0.0);
569
570 if level < self.top_level && level > base_level {
571 (
573 SoilLayer {
574 unit_reference: self.unit_reference.clone(),
575 top_level: level,
576 base_level: self.base_level,
577 base_unit_reference: self.base_unit_reference.clone(),
578 typical_description: self.typical_description.clone(),
579 geol_code: self.geol_code.clone(),
580 reference: self.reference.clone(),
581 },
582 false,
583 )
584 } else if level > self.top_level {
585 (self.clone(), false)
587 } else {
588 (
590 SoilLayer {
591 unit_reference: self.unit_reference.clone(),
592 top_level: self.top_level,
593 base_level: self.base_level,
594 base_unit_reference: self.base_unit_reference.clone(),
595 typical_description: self.typical_description.clone(),
596 geol_code: "DELETE".to_string(),
597 reference: self.reference.clone(),
598 },
599 true,
600 )
601 }
602 }
603
604 pub fn process_layer(&self, sig3: f64, soil_type: SoilType) -> SoilLayer {
605 match soil_type {
606 SoilType::Rock => {
607 let mut processed = self.clone();
610 processed.typical_description = format!(
611 "{} (processed with sig3: {})",
612 processed.typical_description, sig3
613 );
614 processed
615 }
616 _ => self.clone(),
617 }
618 }
619}
620
621impl Default for SoilLayer {
622 fn default() -> Self {
623 SoilLayer {
624 unit_reference: String::new(),
625 top_level: 100.0,
626 base_level: Some(0.0),
627 base_unit_reference: None,
628 typical_description: String::new(),
629 geol_code: String::new(),
630 reference: String::new(),
631 }
632 }
633}
634
635#[derive(Debug, Serialize, Deserialize, Clone)]
636pub struct GroundModel {
637 pub soil_layers: Vec<SoilLayer>,
638 pub soil_params: Vec<SoilParams>,
639 pub rigid_boundary: Option<f64>,
640 pub groundwater: f64,
641 pub reference: String,
642}
643
644impl GroundModel {
645 pub fn new(soil_layers: Vec<SoilLayer>, soil_params: Vec<SoilParams>) -> Self {
646 GroundModel {
647 soil_layers,
648 soil_params,
649 rigid_boundary: None,
650 groundwater: 0.0,
651 reference: String::new(),
652 }
653 }
654
655 pub fn from_agsi_file(agsi_json: &serde_json::Value) -> Self {
656 let mut soil_params = Vec::new();
657
658 if let Some(agsi_models) = agsi_json["agsiModel"].as_array() {
659 if let Some(first_model) = agsi_models.get(0) {
660 if let Some(elements) = first_model["agsiModelElement"].as_array() {
661 for element in elements {
662 if let Some(param_values) = element["agsiDataParameterValue"].as_array() {
663 let params_data: Vec<AgsiDataParameterValue> = param_values
664 .iter()
665 .filter_map(|p| {
666 Some(AgsiDataParameterValue {
667 code_id: p["codeID"].as_str()?.parse().ok()?,
668 case_id: None,
669 data_id: None,
670 remarks: None,
671 value_numeric: p["valueNumeric"].as_f64(),
672 value_profile: None,
673 value_profile_ind_var_code_id: None,
674 value_text: None,
675 })
676 })
677 .collect();
678
679 if !params_data.is_empty() {
680 let mut soil_param =
681 SoilParams::from_agsi_data_parameters(¶ms_data);
682 soil_param.reference = element["elementName"]
683 .as_str()
684 .unwrap_or("unknown")
685 .to_string();
686 soil_params.push(soil_param);
687 }
688 }
689 }
690 }
691 }
692 }
693
694 GroundModel::new(Vec::new(), soil_params)
695 }
696
697 pub fn with_all_fields(
698 soil_layers: Vec<SoilLayer>,
699 soil_params: Vec<SoilParams>,
700 rigid_boundary: Option<f64>,
701 groundwater: f64,
702 reference: String,
703 ) -> Self {
704 GroundModel {
705 soil_layers,
706 soil_params,
707 rigid_boundary,
708 groundwater,
709 reference,
710 }
711 }
712
713 pub fn get_base_level(&self) -> f64 {
714 let mut base = 10000.0;
715 for layer in &self.soil_layers {
716 let layer_base = layer.base_level.unwrap_or(0.0);
717 if base > layer_base {
718 base = layer_base;
719 }
720 }
721 base
722 }
723
724 pub fn get_top_level(&self) -> f64 {
725 let mut top = -10000.0;
726 for layer in &self.soil_layers {
727 if top < layer.top_level {
728 top = layer.top_level;
729 }
730 }
731 top
732 }
733
734 pub fn get_soil_params(&self, reference: &str) -> Option<&SoilParams> {
735 self.soil_params
736 .iter()
737 .find(|param| param.reference == reference)
738 }
739
740 pub fn get_params_at_level(&self, level: f64) -> Result<&SoilParams, &'static str> {
741 for layer in &self.soil_layers {
742 let base_level = layer.base_level.unwrap_or(0.0);
743 if layer.top_level >= level && level >= base_level {
744 let unit_ref = &layer.unit_reference;
745 for param in &self.soil_params {
746 if param.reference == *unit_ref {
747 return Ok(param);
748 }
749 }
750 }
751 }
752 Err("Layer not present")
753 }
754
755 pub fn get_layer_at_level(&self, level: f64) -> Result<&SoilLayer, &'static str> {
756 for layer in &self.soil_layers {
757 let base_level = layer.base_level.unwrap_or(0.0);
758 if layer.top_level >= level && level >= base_level {
759 return Ok(layer);
760 }
761 }
762 Err("Layer not present")
763 }
764
765 pub fn get_soil_params_at_level(&self, level: f64) -> Option<&SoilParams> {
766 for (i, layer) in self.soil_layers.iter().enumerate() {
767 let base_level = layer.base_level.unwrap_or(0.0);
768 if level <= layer.top_level && level >= base_level {
769 return self.soil_params.get(i);
770 }
771 }
772 None
773 }
774
775 fn get_unit_weight_at_level(&self, level: f64) -> Result<f64, &'static str> {
776 Ok(self.get_params_at_level(level)?.unit_weight)
777 }
778
779 pub fn get_pwp_at_level(&self, level: f64) -> f64 {
780 if level > self.groundwater {
781 0.0
782 } else {
783 10.0 * (self.groundwater - level)
784 }
785 }
786
787 pub fn get_total_stress_at_level(&self, level: f64) -> f64 {
788 let top = self.get_top_level();
789 let spacing = 0.1;
790 let mut result = 0.0;
791 let mut current_level = level;
792
793 while current_level <= top {
794 if let Ok(unit_weight) = self.get_unit_weight_at_level(current_level) {
795 result += unit_weight;
796 }
797 current_level += spacing;
798 }
799
800 result * spacing
801 }
802
803 pub fn get_effective_stress_at_level(&self, level: f64) -> f64 {
804 self.get_total_stress_at_level(level) - self.get_pwp_at_level(level)
805 }
806
807 pub fn quick_init(soil_params: SoilParams, top_level: f64, groundwater_level: f64) -> Self {
808 let reference = if soil_params.reference.is_empty() {
809 "gm_soil".to_string()
810 } else {
811 soil_params.reference.clone()
812 };
813
814 let mut params = soil_params;
815 params.reference = reference.clone();
816
817 let layer = SoilLayer::with_all_fields(
818 reference.clone(),
819 top_level,
820 Some(top_level - 1000.0),
821 None,
822 String::new(),
823 String::new(),
824 );
825
826 GroundModel {
827 soil_layers: vec![layer],
828 soil_params: vec![params],
829 rigid_boundary: None,
830 groundwater: groundwater_level,
831 reference,
832 }
833 }
834
835 pub fn get_depth_at_level(&self, level: f64) -> Option<f64> {
836 if self.soil_layers.is_empty() {
837 return None;
838 }
839 let ground_surface = self.soil_layers[0].top_level;
840 if level > ground_surface {
841 return None;
842 }
843 Some(ground_surface - level)
844 }
845}
846
847impl Default for GroundModel {
848 fn default() -> Self {
849 GroundModel {
850 soil_layers: vec![SoilLayer::default()],
851 soil_params: vec![SoilParams::default()],
852 rigid_boundary: None,
853 groundwater: 0.0,
854 reference: String::new(),
855 }
856 }
857}
858
859#[cfg(test)]
860mod tests {
861 use super::*;
862
863 #[test]
864 fn test_ground_model_creation() {
865 let layers = vec![
866 SoilLayer::new(10.0, 5.0, "Layer 1".to_string()),
867 SoilLayer::new(5.0, 0.0, "Layer 2".to_string()),
868 ];
869 let params = vec![
870 SoilParams::new(
871 "Cohesive soil".to_string(),
872 0.1,
873 10000.0,
874 0.3,
875 1e-8,
876 SoilType::Cohesive,
877 18.0,
878 ),
879 SoilParams::new(
880 "Granular soil".to_string(),
881 0.0,
882 50000.0,
883 0.25,
884 0.0,
885 SoilType::Granular,
886 20.0,
887 ),
888 ];
889
890 let ground_model = GroundModel::new(layers, params);
891 assert_eq!(ground_model.soil_layers.len(), 2);
892 assert_eq!(ground_model.soil_params.len(), 2);
893 }
894
895 #[test]
896 fn test_get_soil_params_at_level() {
897 let layers = vec![
898 SoilLayer::new(10.0, 5.0, "Layer 1".to_string()),
899 SoilLayer::new(5.0, 0.0, "Layer 2".to_string()),
900 ];
901 let params = vec![
902 SoilParams::new(
903 "Cohesive soil".to_string(),
904 0.1,
905 10000.0,
906 0.3,
907 1e-8,
908 SoilType::Cohesive,
909 18.0,
910 ),
911 SoilParams::new(
912 "Granular soil".to_string(),
913 0.0,
914 50000.0,
915 0.25,
916 0.0,
917 SoilType::Granular,
918 20.0,
919 ),
920 ];
921
922 let ground_model = GroundModel::new(layers, params);
923
924 let soil_at_7 = ground_model.get_soil_params_at_level(7.0);
925 assert!(soil_at_7.is_some());
926 assert_eq!(soil_at_7.unwrap().behaviour, SoilType::Cohesive);
927
928 let soil_at_3 = ground_model.get_soil_params_at_level(3.0);
929 assert!(soil_at_3.is_some());
930 assert_eq!(soil_at_3.unwrap().behaviour, SoilType::Granular);
931
932 let soil_at_15 = ground_model.get_soil_params_at_level(15.0);
933 assert!(soil_at_15.is_none());
934 }
935
936 #[test]
937 fn test_get_depth_at_level() {
938 let layers = vec![SoilLayer::new(10.0, 5.0, "Layer 1".to_string())];
939 let params = vec![SoilParams::default()];
940
941 let ground_model = GroundModel::new(layers, params);
942
943 assert_eq!(ground_model.get_depth_at_level(8.0), Some(2.0));
944 assert_eq!(ground_model.get_depth_at_level(10.0), Some(0.0));
945 assert_eq!(ground_model.get_depth_at_level(12.0), None);
946 }
947
948 #[test]
949 fn test_get_base_and_top_level() {
950 let layers = vec![
951 SoilLayer::new(15.0, 8.0, "Layer 1".to_string()),
952 SoilLayer::new(8.0, 2.0, "Layer 2".to_string()),
953 SoilLayer::new(2.0, -5.0, "Layer 3".to_string()),
954 ];
955 let params = vec![SoilParams::default(); 3];
956
957 let ground_model = GroundModel::new(layers, params);
958
959 assert_eq!(ground_model.get_top_level(), 15.0);
960 assert_eq!(ground_model.get_base_level(), -5.0);
961 }
962
963 #[test]
964 fn test_get_soil_params_by_reference() {
965 let params = vec![
966 SoilParams::new(
967 "clay".to_string(),
968 0.1,
969 10000.0,
970 0.3,
971 1e-8,
972 SoilType::Cohesive,
973 18.0,
974 ),
975 SoilParams::new(
976 "sand".to_string(),
977 0.0,
978 50000.0,
979 0.25,
980 0.0,
981 SoilType::Granular,
982 20.0,
983 ),
984 ];
985
986 let ground_model = GroundModel::new(vec![], params);
987
988 let clay_params = ground_model.get_soil_params("clay");
989 assert!(clay_params.is_some());
990 assert_eq!(clay_params.unwrap().behaviour, SoilType::Cohesive);
991
992 let sand_params = ground_model.get_soil_params("sand");
993 assert!(sand_params.is_some());
994 assert_eq!(sand_params.unwrap().behaviour, SoilType::Granular);
995
996 let unknown_params = ground_model.get_soil_params("rock");
997 assert!(unknown_params.is_none());
998 }
999
1000 #[test]
1001 fn test_get_params_at_level_with_unit_reference() {
1002 let layers = vec![
1003 SoilLayer::with_unit_reference(10.0, 5.0, "Layer 1".to_string(), "clay".to_string()),
1004 SoilLayer::with_unit_reference(5.0, 0.0, "Layer 2".to_string(), "sand".to_string()),
1005 ];
1006 let params = vec![
1007 SoilParams::new(
1008 "clay".to_string(),
1009 0.1,
1010 10000.0,
1011 0.3,
1012 1e-8,
1013 SoilType::Cohesive,
1014 18.0,
1015 ),
1016 SoilParams::new(
1017 "sand".to_string(),
1018 0.0,
1019 50000.0,
1020 0.25,
1021 0.0,
1022 SoilType::Granular,
1023 20.0,
1024 ),
1025 ];
1026
1027 let ground_model = GroundModel::new(layers, params);
1028
1029 let params_at_7 = ground_model.get_params_at_level(7.0);
1030 assert!(params_at_7.is_ok());
1031 assert_eq!(params_at_7.unwrap().behaviour, SoilType::Cohesive);
1032
1033 let params_at_3 = ground_model.get_params_at_level(3.0);
1034 assert!(params_at_3.is_ok());
1035 assert_eq!(params_at_3.unwrap().behaviour, SoilType::Granular);
1036 }
1037
1038 #[test]
1039 fn test_pwp_calculation() {
1040 let ground_model = GroundModel::with_all_fields(
1041 vec![],
1042 vec![],
1043 None,
1044 5.0, "test".to_string(),
1046 );
1047
1048 assert_eq!(ground_model.get_pwp_at_level(6.0), 0.0);
1050 assert_eq!(ground_model.get_pwp_at_level(5.0), 0.0);
1051
1052 assert_eq!(ground_model.get_pwp_at_level(4.0), 10.0);
1054 assert_eq!(ground_model.get_pwp_at_level(3.0), 20.0);
1055 }
1056
1057 #[test]
1058 fn test_quick_init() {
1059 let soil_params = SoilParams::new(
1060 "test_soil".to_string(),
1061 0.1,
1062 10000.0,
1063 0.3,
1064 1e-8,
1065 SoilType::Cohesive,
1066 18.0,
1067 );
1068
1069 let ground_model = GroundModel::quick_init(soil_params, 10.0, 5.0);
1070
1071 assert_eq!(ground_model.soil_layers.len(), 1);
1072 assert_eq!(ground_model.soil_params.len(), 1);
1073 assert_eq!(ground_model.groundwater, 5.0);
1074 assert_eq!(ground_model.soil_layers[0].top_level, 10.0);
1075 assert_eq!(ground_model.soil_layers[0].base_level, Some(-990.0));
1076 assert_eq!(ground_model.soil_params[0].reference, "test_soil");
1077 }
1078
1079 #[test]
1080 fn test_quick_init_with_empty_reference() {
1081 let soil_params = SoilParams::default(); let ground_model = GroundModel::quick_init(soil_params, 10.0, 5.0);
1084
1085 assert_eq!(ground_model.soil_params[0].reference, "gm_soil");
1086 assert_eq!(ground_model.reference, "gm_soil");
1087 }
1088
1089 #[test]
1090 fn test_layer_at_level() {
1091 let layers = vec![
1092 SoilLayer::new(10.0, 5.0, "Upper layer".to_string()),
1093 SoilLayer::new(5.0, 0.0, "Lower layer".to_string()),
1094 ];
1095
1096 let ground_model = GroundModel::new(layers, vec![]);
1097
1098 let layer_at_7 = ground_model.get_layer_at_level(7.0);
1099 assert!(layer_at_7.is_ok());
1100 assert_eq!(layer_at_7.unwrap().reference, "Upper layer");
1101
1102 let layer_at_3 = ground_model.get_layer_at_level(3.0);
1103 assert!(layer_at_3.is_ok());
1104 assert_eq!(layer_at_3.unwrap().reference, "Lower layer");
1105
1106 let layer_at_15 = ground_model.get_layer_at_level(15.0);
1107 assert!(layer_at_15.is_err());
1108 }
1109
1110 #[test]
1111 fn test_soil_layer_new_structure() {
1112 let layer = SoilLayer::with_all_fields(
1113 "CLAY".to_string(),
1114 10.0,
1115 Some(5.0),
1116 Some("SAND".to_string()),
1117 "Firm brown clay".to_string(),
1118 "CL".to_string(),
1119 );
1120
1121 assert_eq!(layer.unit_reference, "CLAY");
1122 assert_eq!(layer.top_level, 10.0);
1123 assert_eq!(layer.base_level, Some(5.0));
1124 assert_eq!(layer.base_unit_reference, Some("SAND".to_string()));
1125 assert_eq!(layer.typical_description, "Firm brown clay");
1126 assert_eq!(layer.geol_code, "CL");
1127 }
1128
1129 #[test]
1130 fn test_excavate_layer() {
1131 let layer = SoilLayer::with_all_fields(
1132 "CLAY".to_string(),
1133 10.0,
1134 Some(5.0),
1135 None,
1136 "Firm brown clay".to_string(),
1137 "CL".to_string(),
1138 );
1139
1140 let (excavated, should_delete) = layer.excavate_layer(7.0);
1142 assert!(!should_delete);
1143 assert_eq!(excavated.top_level, 7.0);
1144 assert_eq!(excavated.base_level, Some(5.0));
1145
1146 let (excavated, should_delete) = layer.excavate_layer(12.0);
1148 assert!(!should_delete);
1149 assert_eq!(excavated.top_level, 10.0);
1150
1151 let (excavated, should_delete) = layer.excavate_layer(3.0);
1153 assert!(should_delete);
1154 assert_eq!(excavated.geol_code, "DELETE");
1155 }
1156
1157 #[test]
1158 fn test_process_layer() {
1159 let layer = SoilLayer::with_all_fields(
1160 "ROCK".to_string(),
1161 10.0,
1162 Some(5.0),
1163 None,
1164 "Weathered limestone".to_string(),
1165 "LS".to_string(),
1166 );
1167
1168 let processed = layer.process_layer(100.0, SoilType::Rock);
1169 assert!(processed
1170 .typical_description
1171 .contains("processed with sig3: 100"));
1172
1173 let processed_soil = layer.process_layer(0.0, SoilType::Cohesive);
1174 assert_eq!(processed_soil.typical_description, "Weathered limestone");
1175 }
1176
1177 #[test]
1178 fn test_updated_ground_model_with_optional_base_level() {
1179 let layers = vec![
1180 SoilLayer::with_all_fields(
1181 "clay".to_string(),
1182 10.0,
1183 Some(5.0),
1184 None,
1185 "Firm brown clay".to_string(),
1186 "CL".to_string(),
1187 ),
1188 SoilLayer::with_all_fields(
1189 "sand".to_string(),
1190 5.0,
1191 None, None,
1193 "Dense sand".to_string(),
1194 "SP".to_string(),
1195 ),
1196 ];
1197 let params = vec![
1198 SoilParams::new(
1199 "clay".to_string(),
1200 0.1,
1201 10000.0,
1202 0.3,
1203 1e-8,
1204 SoilType::Cohesive,
1205 18.0,
1206 ),
1207 SoilParams::new(
1208 "sand".to_string(),
1209 0.0,
1210 50000.0,
1211 0.25,
1212 0.0,
1213 SoilType::Granular,
1214 20.0,
1215 ),
1216 ];
1217
1218 let ground_model = GroundModel::new(layers, params);
1219
1220 let params_at_7 = ground_model.get_params_at_level(7.0);
1222 assert!(params_at_7.is_ok());
1223 assert_eq!(params_at_7.unwrap().behaviour, SoilType::Cohesive);
1224
1225 let params_at_3 = ground_model.get_params_at_level(3.0);
1227 assert!(params_at_3.is_ok());
1228 assert_eq!(params_at_3.unwrap().behaviour, SoilType::Granular);
1229 }
1230
1231 #[test]
1232 fn test_soil_layer_default() {
1233 let layer = SoilLayer::default();
1234 assert_eq!(layer.unit_reference, "");
1235 assert_eq!(layer.top_level, 100.0);
1236 assert_eq!(layer.base_level, Some(0.0));
1237 assert_eq!(layer.base_unit_reference, None);
1238 assert_eq!(layer.typical_description, "");
1239 assert_eq!(layer.geol_code, "");
1240 }
1241
1242 #[test]
1243 fn test_soil_params_new_structure() {
1244 let params = SoilParams::with_all_fields(
1245 "test_soil".to_string(),
1246 SoilType::Cohesive,
1247 Some(0.5), Some(10.0), 18.0, Some(50.0), 0.1, 10000.0, 0.3, 1e-8, Some(65.0), Some(25.0), Some(10.0), 0.0, );
1260
1261 assert_eq!(params.reference, "test_soil");
1262 assert_eq!(params.behaviour, SoilType::Cohesive);
1263 assert_eq!(params.phi_prime, Some(0.5));
1264 assert_eq!(params.c_prime, Some(10.0));
1265 assert_eq!(params.unit_weight, 18.0);
1266 assert_eq!(params.cu, Some(50.0));
1267 assert_eq!(params.gsi, Some(65.0));
1268 assert_eq!(params.ucs, Some(25.0));
1269 assert_eq!(params.mi, Some(10.0));
1270 assert!(!params.factored);
1271 }
1272
1273 #[test]
1274 fn test_partial_factors() {
1275 let pf = PartialFactors::new(1.25, 1.25, 1.1, 1.4); let params = SoilParams::with_all_fields(
1277 "test".to_string(),
1278 SoilType::Cohesive,
1279 Some(0.5), Some(20.0),
1281 18.0,
1282 Some(100.0),
1283 0.1,
1284 10000.0,
1285 0.3,
1286 1e-8,
1287 None,
1288 None,
1289 None,
1290 0.0,
1291 );
1292
1293 let factored = params.apply_partial_factors(&pf);
1294 assert!(factored.factored);
1295 assert!(factored.factors.is_some());
1296
1297 assert!(factored.phi_prime.unwrap() < params.phi_prime.unwrap());
1299 assert!(factored.c_prime.unwrap() < params.c_prime.unwrap());
1300 assert!(factored.unit_weight < params.unit_weight); assert!(factored.cu.unwrap() < params.cu.unwrap());
1302
1303 let unfactored = factored.remove_partial_factors().unwrap();
1305 assert!(!unfactored.factored);
1306 assert!(unfactored.factors.is_none());
1307
1308 let phi_diff = (unfactored.phi_prime.unwrap() - params.phi_prime.unwrap()).abs();
1310 assert!(phi_diff < 1e-10);
1311 }
1312
1313 #[test]
1314 fn test_earth_pressure_coefficients() {
1315 let params = SoilParams::with_all_fields(
1316 "test".to_string(),
1317 SoilType::Granular,
1318 Some(30.0_f64.to_radians()), Some(0.0),
1320 20.0,
1321 None,
1322 0.0,
1323 50000.0,
1324 0.25,
1325 0.0,
1326 None,
1327 None,
1328 None,
1329 0.0,
1330 );
1331
1332 let k_active = params.get_k_active(None).unwrap();
1334 let expected_ka = (1.0 - 30.0_f64.to_radians().sin()) / (1.0 + 30.0_f64.to_radians().sin());
1335 assert!((k_active - expected_ka).abs() < 1e-10);
1336
1337 let k_passive = params.get_k_passive(None).unwrap();
1339 let expected_kp = 1.0 / expected_ka;
1340 assert!((k_passive - expected_kp).abs() < 1e-10);
1341
1342 let k0 = params.k0().unwrap();
1344 let expected_k0 = 1.0 - 30.0_f64.to_radians().sin();
1345 assert!((k0 - expected_k0).abs() < 1e-10);
1346 }
1347
1348 #[test]
1349 fn test_hoek_brown_parameters() {
1350 let params = SoilParams::with_all_fields(
1351 "rock".to_string(),
1352 SoilType::Rock,
1353 None,
1354 None,
1355 25.0,
1356 None,
1357 0.0,
1358 0.0,
1359 0.25,
1360 0.0,
1361 Some(65.0), Some(25.0), Some(10.0), 0.0, );
1366
1367 let mb = params.mb().unwrap();
1369 assert!(mb > 0.0);
1370
1371 let s = params.s().unwrap();
1373 assert!(s > 0.0);
1374
1375 let a = params.a().unwrap();
1377 assert!(a > 0.0 && a < 1.0);
1378
1379 let e_val = params.rock_e_val().unwrap();
1381 assert!(e_val > 0.0);
1382 }
1383
1384 #[test]
1385 fn test_hoek_brown_conversion() {
1386 let params = SoilParams::with_all_fields(
1387 "rock".to_string(),
1388 SoilType::Rock,
1389 None,
1390 None,
1391 25.0,
1392 None,
1393 0.0,
1394 0.0,
1395 0.25,
1396 0.0,
1397 Some(65.0), Some(25.0), Some(10.0), 0.0, );
1402
1403 let sig3 = 100.0; let phi_equiv = params.hb_equiv_phi_ang(sig3).unwrap();
1407 assert!(phi_equiv >= 0.0 && phi_equiv <= 1.0); let c_equiv = params.hb_equiv_c_prime(sig3).unwrap();
1411 assert!(c_equiv > 0.0);
1412
1413 let converted = params.convert_equivalent_rock(sig3).unwrap();
1415 assert!(converted.phi_prime.is_some());
1416 assert!(converted.c_prime.is_some());
1417 assert!(converted.phi_prime.unwrap() > 0.0);
1418 assert!(converted.c_prime.unwrap() > 0.0);
1419 assert_eq!(converted.unit_weight, params.unit_weight);
1420 }
1421
1422 #[test]
1423 fn test_error_handling() {
1424 let params = SoilParams::default();
1425
1426 assert!(params.get_k_active(None).is_err());
1428 assert!(params.k0().is_err());
1429 assert!(params.mb().is_err());
1430 assert!(params.s().is_err());
1431 assert!(params.a().is_err());
1432 assert!(params.rock_e_val().is_err());
1433 assert!(params.hb_equiv_phi_ang(100.0).is_err());
1434 assert!(params.hb_equiv_c_prime(100.0).is_err());
1435 assert!(params.convert_equivalent_rock(100.0).is_err());
1436
1437 assert!(params.remove_partial_factors().is_err());
1439 }
1440
1441 #[test]
1442 fn test_from_agsi_data_parameters() {
1443 let data = vec![
1444 AgsiDataParameterValue {
1445 code_id: "UnitWeight".parse().unwrap(),
1446 case_id: None,
1447 data_id: None,
1448 remarks: None,
1449 value_numeric: Some(18.0),
1450 value_profile: None,
1451 value_profile_ind_var_code_id: None,
1452 value_text: None,
1453 },
1454 AgsiDataParameterValue {
1455 code_id: "EffectiveCohesion".parse().unwrap(),
1456 case_id: None,
1457 data_id: None,
1458 remarks: None,
1459 value_numeric: Some(5.0),
1460 value_profile: None,
1461 value_profile_ind_var_code_id: None,
1462 value_text: None,
1463 },
1464 AgsiDataParameterValue {
1465 code_id: "EffectiveFrictionAngle".parse().unwrap(),
1466 case_id: None,
1467 data_id: None,
1468 remarks: None,
1469 value_numeric: Some(30.0),
1470 value_profile: None,
1471 value_profile_ind_var_code_id: None,
1472 value_text: None,
1473 },
1474 AgsiDataParameterValue {
1475 code_id: "YoungsModulus".parse().unwrap(),
1476 case_id: None,
1477 data_id: None,
1478 remarks: None,
1479 value_numeric: Some(50000.0),
1480 value_profile: None,
1481 value_profile_ind_var_code_id: None,
1482 value_text: None,
1483 },
1484 ];
1485
1486 let soil_params = SoilParams::from_agsi_data_parameters(&data);
1487
1488 assert_eq!(soil_params.unit_weight, 18.0);
1489 assert_eq!(soil_params.phi_prime, Some(30.0));
1490 assert_eq!(soil_params.c_prime, Some(5.0));
1491 assert_eq!(soil_params.youngs_modulus, 50000.0);
1492 assert_eq!(soil_params.behaviour, SoilType::Granular); }
1494
1495 #[test]
1496 fn test_from_agsi_data_parameters_cohesive() {
1497 let data = vec![AgsiDataParameterValue {
1498 code_id: "UndrainedShearStrength".parse().unwrap(),
1499 case_id: None,
1500 data_id: None,
1501 remarks: None,
1502 value_numeric: Some(100.0),
1503 value_profile: None,
1504 value_profile_ind_var_code_id: None,
1505 value_text: None,
1506 }];
1507
1508 let soil_params = SoilParams::from_agsi_data_parameters(&data);
1509
1510 assert_eq!(soil_params.cu, Some(100.0));
1511 assert_eq!(soil_params.behaviour, SoilType::Cohesive);
1512 }
1513
1514 #[test]
1515 fn test_from_agsi_data_parameters_rock() {
1516 let data = vec![
1517 AgsiDataParameterValue {
1518 code_id: "UnconfinedCompressiveStrength".parse().unwrap(),
1519 case_id: None,
1520 data_id: None,
1521 remarks: None,
1522 value_numeric: Some(25.0),
1523 value_profile: None,
1524 value_profile_ind_var_code_id: None,
1525 value_text: None,
1526 },
1527 AgsiDataParameterValue {
1528 code_id: "GeologicalStrengthIndex".parse().unwrap(),
1529 case_id: None,
1530 data_id: None,
1531 remarks: None,
1532 value_numeric: Some(65.0),
1533 value_profile: None,
1534 value_profile_ind_var_code_id: None,
1535 value_text: None,
1536 },
1537 AgsiDataParameterValue {
1538 code_id: "HoekBrownParamMi".parse().unwrap(),
1539 case_id: None,
1540 data_id: None,
1541 remarks: None,
1542 value_numeric: Some(10.0),
1543 value_profile: None,
1544 value_profile_ind_var_code_id: None,
1545 value_text: None,
1546 },
1547 ];
1548
1549 let soil_params = SoilParams::from_agsi_data_parameters(&data);
1550
1551 assert_eq!(soil_params.ucs, Some(25.0));
1552 assert_eq!(soil_params.gsi, Some(65.0));
1553 assert_eq!(soil_params.mi, Some(10.0));
1554 assert_eq!(soil_params.behaviour, SoilType::Rock);
1555 }
1556
1557 #[test]
1558 fn test_from_agsi_data_parameters_advanced_params() {
1559 let data = vec![
1560 AgsiDataParameterValue {
1561 code_id: "CustomParameter".parse().unwrap(),
1562 case_id: None,
1563 data_id: None,
1564 remarks: None,
1565 value_numeric: Some(42.0),
1566 value_profile: None,
1567 value_profile_ind_var_code_id: None,
1568 value_text: None,
1569 },
1570 AgsiDataParameterValue {
1571 code_id: "AnotherCustom".parse().unwrap(),
1572 case_id: None,
1573 data_id: None,
1574 remarks: None,
1575 value_numeric: Some(123.0),
1576 value_profile: None,
1577 value_profile_ind_var_code_id: None,
1578 value_text: None,
1579 },
1580 ];
1581
1582 let soil_params = SoilParams::from_agsi_data_parameters(&data);
1583
1584 assert!(soil_params.advanced_parameters.is_some());
1585 let advanced = soil_params.advanced_parameters.unwrap();
1586 assert_eq!(advanced.len(), 2);
1587 assert_eq!(advanced[0].name, "CustomParameter");
1588 assert_eq!(advanced[0].value, 42.0);
1589 assert_eq!(advanced[1].name, "AnotherCustom");
1590 assert_eq!(advanced[1].value, 123.0);
1591 }
1592}