use crate::error::Result;
use crate::types::basic::{OSString, UnsignedInt, Value};
use crate::types::distributions::{DistributionSampler, ValidateDistribution};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Stochastic {
#[serde(rename = "StochasticDistribution")]
pub distributions: Vec<StochasticDistribution>,
#[serde(rename = "@numberOfTestRuns")]
pub number_of_test_runs: UnsignedInt,
#[serde(rename = "@randomSeed", skip_serializing_if = "Option::is_none")]
pub random_seed: Option<Value<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StochasticDistribution {
#[serde(flatten)]
pub distribution_type: StochasticDistributionType,
#[serde(rename = "@parameterName")]
pub parameter_name: OSString,
#[serde(rename = "@randomSeed", skip_serializing_if = "Option::is_none")]
pub random_seed: Option<OSString>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum StochasticDistributionType {
ProbabilityDistributionSet(ProbabilityDistributionSet),
NormalDistribution(NormalDistribution),
LogNormalDistribution(LogNormalDistribution),
UniformDistribution(UniformDistribution),
PoissonDistribution(PoissonDistribution),
Histogram(Histogram),
UserDefinedDistribution(crate::types::distributions::UserDefinedDistribution),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProbabilityDistributionSet {
#[serde(rename = "Element")]
pub elements: Vec<ProbabilityDistributionSetElement>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProbabilityDistributionSetElement {
#[serde(rename = "@value")]
pub value: OSString,
#[serde(rename = "@weight")]
pub weight: OSString,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NormalDistribution {
#[serde(rename = "@expectedValue")]
pub expected_value: OSString,
#[serde(rename = "@variance")]
pub variance: OSString,
#[serde(rename = "@range", skip_serializing_if = "Option::is_none")]
pub range: Option<Range>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LogNormalDistribution {
#[serde(rename = "@expectedValue")]
pub expected_value: OSString,
#[serde(rename = "@variance")]
pub variance: OSString,
#[serde(rename = "@range", skip_serializing_if = "Option::is_none")]
pub range: Option<Range>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UniformDistribution {
#[serde(rename = "@range")]
pub range: Range,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PoissonDistribution {
#[serde(rename = "@expectedValue")]
pub expected_value: OSString,
#[serde(rename = "@range", skip_serializing_if = "Option::is_none")]
pub range: Option<Range>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Histogram {
#[serde(rename = "HistogramBin")]
pub bins: Vec<HistogramBin>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HistogramBin {
#[serde(rename = "@range")]
pub range: Range,
#[serde(rename = "@weight")]
pub weight: OSString,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Range {
#[serde(rename = "@lowerLimit")]
pub lower_limit: OSString,
#[serde(rename = "@upperLimit")]
pub upper_limit: OSString,
}
impl Default for Stochastic {
fn default() -> Self {
Self {
distributions: Vec::new(),
number_of_test_runs: Value::Literal(1),
random_seed: None,
}
}
}
impl ValidateDistribution for Stochastic {
fn validate(&self) -> Result<()> {
for dist in &self.distributions {
dist.validate()?;
}
Ok(())
}
}
impl ValidateDistribution for StochasticDistribution {
fn validate(&self) -> Result<()> {
match &self.distribution_type {
StochasticDistributionType::ProbabilityDistributionSet(dist) => dist.validate(),
StochasticDistributionType::NormalDistribution(dist) => dist.validate(),
StochasticDistributionType::LogNormalDistribution(dist) => dist.validate(),
StochasticDistributionType::UniformDistribution(dist) => dist.validate(),
StochasticDistributionType::PoissonDistribution(dist) => dist.validate(),
StochasticDistributionType::Histogram(dist) => dist.validate(),
StochasticDistributionType::UserDefinedDistribution(dist) => dist.validate(),
}
}
}
impl ValidateDistribution for ProbabilityDistributionSet {
fn validate(&self) -> Result<()> {
if self.elements.is_empty() {
return Err(crate::error::Error::validation_error(
"elements",
"ProbabilityDistributionSet must have at least one element",
));
}
Ok(())
}
}
impl ValidateDistribution for NormalDistribution {
fn validate(&self) -> Result<()> {
Ok(())
}
}
impl ValidateDistribution for LogNormalDistribution {
fn validate(&self) -> Result<()> {
Ok(())
}
}
impl ValidateDistribution for UniformDistribution {
fn validate(&self) -> Result<()> {
self.range.validate()
}
}
impl ValidateDistribution for PoissonDistribution {
fn validate(&self) -> Result<()> {
Ok(())
}
}
impl ValidateDistribution for Histogram {
fn validate(&self) -> Result<()> {
if self.bins.is_empty() {
return Err(crate::error::Error::validation_error(
"bins",
"Histogram must have at least one bin",
));
}
for bin in &self.bins {
bin.validate()?;
}
Ok(())
}
}
impl ValidateDistribution for HistogramBin {
fn validate(&self) -> Result<()> {
self.range.validate()
}
}
impl ValidateDistribution for Range {
fn validate(&self) -> Result<()> {
Ok(())
}
}
impl DistributionSampler for ProbabilityDistributionSet {
type Output = String;
fn sample(&self) -> Result<Self::Output> {
if let Some(first_element) = self.elements.first() {
match &first_element.value {
Value::Literal(val) => Ok(val.clone()),
Value::Parameter(_) => Err(crate::error::Error::validation_error("sampling",
"Cannot sample from parameterized distribution without parameter resolution"
)),
Value::Expression(_) => Err(crate::error::Error::validation_error("sampling",
"Cannot sample from expression-based distribution without expression evaluation"
)),
}
} else {
Err(crate::error::Error::validation_error(
"sampling",
"Cannot sample from empty probability distribution set",
))
}
}
fn is_deterministic(&self) -> bool {
false
}
}
impl DistributionSampler for UniformDistribution {
type Output = String;
fn sample(&self) -> Result<Self::Output> {
match (&self.range.lower_limit, &self.range.upper_limit) {
(OSString::Literal(lower), OSString::Literal(upper)) => {
Ok(format!("uniform({}, {})", lower, upper))
}
_ => Err(crate::error::Error::validation_error(
"sampling",
"Cannot sample from parameterized distribution without parameter resolution",
)),
}
}
fn is_deterministic(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_probability_distribution_set_validation() {
let valid_set = ProbabilityDistributionSet {
elements: vec![
ProbabilityDistributionSetElement {
value: OSString::Literal("A".to_string()),
weight: OSString::Literal("0.6".to_string()),
},
ProbabilityDistributionSetElement {
value: OSString::Literal("B".to_string()),
weight: OSString::Literal("0.4".to_string()),
},
],
};
assert!(valid_set.validate().is_ok());
let empty_set = ProbabilityDistributionSet { elements: vec![] };
assert!(empty_set.validate().is_err());
}
#[test]
fn test_range_validation() {
let valid_range = Range {
lower_limit: Value::Literal("0.0".to_string()),
upper_limit: Value::Literal("10.0".to_string()),
};
assert!(valid_range.validate().is_ok());
}
#[test]
fn test_uniform_distribution_sampling() {
let uniform = UniformDistribution {
range: Range {
lower_limit: Value::Literal("0.0".to_string()),
upper_limit: Value::Literal("10.0".to_string()),
},
};
let sample = uniform.sample().unwrap();
assert!(sample.contains("uniform"));
assert!(!uniform.is_deterministic());
}
#[test]
fn test_histogram_validation() {
let valid_histogram = Histogram {
bins: vec![HistogramBin {
range: Range {
lower_limit: Value::Literal("0.0".to_string()),
upper_limit: Value::Literal("5.0".to_string()),
},
weight: Value::Literal("0.3".to_string()),
}],
};
assert!(valid_histogram.validate().is_ok());
let empty_histogram = Histogram { bins: vec![] };
assert!(empty_histogram.validate().is_err());
}
}