fix: validate NaN/Inf in deserialized trials during journal loading
- Add `CompletedTrial::validate()` checking all f64 fields are finite - Call validate after deserializing each trial in journal storage - Make `distribution` and `param` modules public (types already in public fields)
This commit is contained in:
+2
-2
@@ -107,14 +107,14 @@ macro_rules! trace_debug {
|
||||
($($arg:tt)*) => {};
|
||||
}
|
||||
|
||||
mod distribution;
|
||||
pub mod distribution;
|
||||
mod error;
|
||||
mod fanova;
|
||||
mod importance;
|
||||
mod kde;
|
||||
pub mod multi_objective;
|
||||
pub mod objective;
|
||||
mod param;
|
||||
pub mod param;
|
||||
pub mod parameter;
|
||||
pub mod pareto;
|
||||
pub mod pruner;
|
||||
|
||||
@@ -175,6 +175,74 @@ impl<V> CompletedTrial<V> {
|
||||
pub fn user_attrs(&self) -> &HashMap<String, AttrValue> {
|
||||
&self.user_attrs
|
||||
}
|
||||
|
||||
/// Validates that all floating-point fields are finite (not NaN or
|
||||
/// Infinity).
|
||||
///
|
||||
/// Checks distribution bounds, parameter values, constraints, and
|
||||
/// intermediate values. Returns a description of the first invalid
|
||||
/// field found, or `Ok(())` if everything is valid.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `String` describing the first non-finite value found.
|
||||
pub fn validate(&self) -> core::result::Result<(), String> {
|
||||
for (id, dist) in &self.distributions {
|
||||
if let Distribution::Float(fd) = dist {
|
||||
if !fd.low.is_finite() {
|
||||
return Err(format!(
|
||||
"trial {}: float distribution for param {id} has non-finite low bound ({})",
|
||||
self.id, fd.low
|
||||
));
|
||||
}
|
||||
if !fd.high.is_finite() {
|
||||
return Err(format!(
|
||||
"trial {}: float distribution for param {id} has non-finite high bound ({})",
|
||||
self.id, fd.high
|
||||
));
|
||||
}
|
||||
if let Some(step) = fd.step
|
||||
&& !step.is_finite()
|
||||
{
|
||||
return Err(format!(
|
||||
"trial {}: float distribution for param {id} has non-finite step ({step})",
|
||||
self.id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id, pv) in &self.params {
|
||||
if let ParamValue::Float(v) = pv
|
||||
&& !v.is_finite()
|
||||
{
|
||||
return Err(format!(
|
||||
"trial {}: param {id} has non-finite float value ({v})",
|
||||
self.id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &c) in self.constraints.iter().enumerate() {
|
||||
if !c.is_finite() {
|
||||
return Err(format!(
|
||||
"trial {}: constraint[{i}] is non-finite ({c})",
|
||||
self.id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for &(step, v) in &self.intermediate_values {
|
||||
if !v.is_finite() {
|
||||
return Err(format!(
|
||||
"trial {}: intermediate value at step {step} is non-finite ({v})",
|
||||
self.id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A pending (running) trial with its parameters and distributions, but no objective value yet.
|
||||
|
||||
@@ -244,6 +244,7 @@ fn load_trials_from_file<V: DeserializeOwned>(
|
||||
}
|
||||
let trial: CompletedTrial<V> =
|
||||
serde_json::from_str(line).map_err(|e| crate::Error::Storage(e.to_string()))?;
|
||||
trial.validate().map_err(crate::Error::Storage)?;
|
||||
trials.push(trial);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user