///|
/// A named bound for one component of a filter state.
pub struct StateGuardRule {
index : Int
name : String
minimum : Double
maximum : Double
tolerance : Double
mut enabled : Bool
} derive(Debug)
///|
pub fn StateGuardRule::new(
index : Int,
name : String,
minimum : Double,
maximum : Double,
tolerance : Double,
) -> StateGuardRule {
{
index: index.max(0),
name,
minimum: minimum.min(maximum),
maximum: minimum.max(maximum),
tolerance: tolerance.max(0.0),
enabled: true,
}
}
///|
pub fn StateGuardRule::index(self : StateGuardRule) -> Int {
self.index
}
///|
pub fn StateGuardRule::name(self : StateGuardRule) -> String {
self.name
}
///|
pub fn StateGuardRule::minimum(self : StateGuardRule) -> Double {
self.minimum
}
///|
pub fn StateGuardRule::maximum(self : StateGuardRule) -> Double {
self.maximum
}
///|
pub fn StateGuardRule::tolerance(self : StateGuardRule) -> Double {
self.tolerance
}
///|
pub fn StateGuardRule::enabled(self : StateGuardRule) -> Bool {
self.enabled
}
///|
pub fn StateGuardRule::set_enabled(
self : StateGuardRule,
enabled : Bool,
) -> Unit {
self.enabled = enabled
}
///|
pub fn StateGuardRule::contains(self : StateGuardRule, value : Double) -> Bool {
value >= self.minimum - self.tolerance &&
value <= self.maximum + self.tolerance
}
///|
pub fn StateGuardRule::project(self : StateGuardRule, value : Double) -> Double {
value.clamp(min=self.minimum, max=self.maximum)
}
///|
pub enum StateGuardStatus {
StateGuardOk
StateGuardClipped
StateGuardRejected
StateGuardInvalid
} derive(Debug, Eq)
///|
pub struct StateGuardIssue {
index : Int
name : String
value : Double
projected : Double
excess : Double
status : StateGuardStatus
} derive(Debug)
///|
pub fn StateGuardIssue::new(
rule : StateGuardRule,
value : Double,
projected : Double,
status : StateGuardStatus,
) -> StateGuardIssue {
{
index: rule.index(),
name: rule.name(),
value,
projected,
excess: (value - projected).abs(),
status,
}
}
///|
pub fn StateGuardIssue::index(self : StateGuardIssue) -> Int {
self.index
}
///|
pub fn StateGuardIssue::name(self : StateGuardIssue) -> String {
self.name
}
///|
pub fn StateGuardIssue::value(self : StateGuardIssue) -> Double {
self.value
}
///|
pub fn StateGuardIssue::projected(self : StateGuardIssue) -> Double {
self.projected
}
///|
pub fn StateGuardIssue::excess(self : StateGuardIssue) -> Double {
self.excess
}
///|
pub fn StateGuardIssue::status(self : StateGuardIssue) -> StateGuardStatus {
self.status
}
///|
pub struct StateGuardReport {
original : Array[Double]
repaired : Array[Double]
issues : Array[StateGuardIssue]
accepted : Bool
changed : Bool
score : Double
} derive(Debug)
///|
pub fn StateGuardReport::new(
original : Array[Double],
repaired : Array[Double],
issues : Array[StateGuardIssue],
accepted : Bool,
) -> StateGuardReport {
let mut changed = false
for i in 0.. 1.0e-12 {
changed = true
}
}
let mut penalty = 0.0
for issue in issues {
penalty = penalty + issue.excess()
if issue.status() is StateGuardInvalid {
penalty = penalty + 1.0
}
}
{
original: original.copy(),
repaired: repaired.copy(),
issues: issues.copy(),
accepted,
changed,
score: (1.0 - penalty / (1.0 + original.length().to_double())).clamp(
min=0.0,
max=1.0,
),
}
}
///|
pub fn StateGuardReport::original(self : StateGuardReport) -> Array[Double] {
self.original.copy()
}
///|
pub fn StateGuardReport::repaired(self : StateGuardReport) -> Array[Double] {
self.repaired.copy()
}
///|
pub fn StateGuardReport::issues(
self : StateGuardReport,
) -> Array[StateGuardIssue] {
self.issues.copy()
}
///|
pub fn StateGuardReport::accepted(self : StateGuardReport) -> Bool {
self.accepted
}
///|
pub fn StateGuardReport::changed(self : StateGuardReport) -> Bool {
self.changed
}
///|
pub fn StateGuardReport::score(self : StateGuardReport) -> Double {
self.score
}
///|
pub fn StateGuardReport::is_usable(self : StateGuardReport) -> Bool {
self.accepted && self.score >= 0.5
}
///|
/// A state sanitizer applies bounds while preserving untouched dimensions.
pub struct StateGuard {
rules : Array[StateGuardRule]
reject_invalid : Bool
reject_excess : Double
mut checks : Int
mut rejected : Int
mut clipped : Int
} derive(Debug)
///|
pub fn StateGuard::new(
rules : Array[StateGuardRule],
reject_invalid : Bool,
reject_excess : Double,
) -> StateGuard {
{
rules: rules.copy(),
reject_invalid,
reject_excess: reject_excess.max(0.0),
checks: 0,
rejected: 0,
clipped: 0,
}
}
///|
pub fn StateGuard::rules(self : StateGuard) -> Array[StateGuardRule] {
self.rules.copy()
}
///|
pub fn StateGuard::checks(self : StateGuard) -> Int {
self.checks
}
///|
pub fn StateGuard::rejected(self : StateGuard) -> Int {
self.rejected
}
///|
pub fn StateGuard::clipped(self : StateGuard) -> Int {
self.clipped
}
///|
pub fn StateGuard::rejection_rate(self : StateGuard) -> Double {
if self.checks == 0 {
0.0
} else {
self.rejected.to_double() / self.checks.to_double()
}
}
///|
pub fn StateGuard::check(
self : StateGuard,
state : Array[Double],
) -> StateGuardReport {
self.checks = self.checks + 1
let repaired = state.copy()
let issues : Array[StateGuardIssue] = []
let mut rejected = false
for rule in self.rules {
if !rule.enabled() {
continue
}
if rule.index() < 0 || rule.index() >= state.length() {
issues.push(StateGuardIssue::new(rule, 0.0, 0.0, StateGuardInvalid))
rejected = true
continue
}
let value = state[rule.index()]
if value.is_nan() || value.is_inf() {
issues.push(StateGuardIssue::new(rule, value, 0.0, StateGuardInvalid))
rejected = true
continue
}
let projected = rule.project(value)
let excess = (value - projected).abs()
if excess <= rule.tolerance() {
issues.push(StateGuardIssue::new(rule, value, value, StateGuardOk))
} else if excess <= self.reject_excess || !self.reject_invalid {
repaired[rule.index()] = projected
self.clipped = self.clipped + 1
issues.push(
StateGuardIssue::new(rule, value, projected, StateGuardClipped),
)
} else {
issues.push(
StateGuardIssue::new(rule, value, projected, StateGuardRejected),
)
rejected = true
}
}
if rejected {
self.rejected = self.rejected + 1
}
StateGuardReport::new(state, repaired, issues, !rejected)
}
///|
pub fn StateGuard::repair(
self : StateGuard,
state : Array[Double],
) -> Array[Double] {
self.check(state).repaired()
}
///|
pub fn StateGuard::reset_metrics(self : StateGuard) -> Unit {
self.checks = 0
self.rejected = 0
self.clipped = 0
}
///|
pub fn state_guard_rules_for_dimension(
minimum : Array[Double],
maximum : Array[Double],
tolerance : Double,
) -> Array[StateGuardRule] {
let dimension = minimum.length().min(maximum.length())
let rules : Array[StateGuardRule] = []
for i in 0.. StateGuard {
let dimension = state
.length()
.min(covariance.rows())
.min(minimum.length())
.min(maximum.length())
let lower : Array[Double] = []
let upper : Array[Double] = []
for i in 0.. Array[StateGuardRule] {
let result = left.copy()
for rule in right {
let mut replaced = false
for i, current in result {
if current.index() == rule.index() {
result[i] = rule
replaced = true
}
}
if !replaced {
result.push(rule)
}
}
result
}
///|
pub fn state_guard_issue_summary(issue : StateGuardIssue) -> String {
let status = match issue.status() {
StateGuardOk => "ok"
StateGuardClipped => "clipped"
StateGuardRejected => "rejected"
StateGuardInvalid => "invalid"
}
issue.name() +
"=" +
issue.value().to_string() +
",status=" +
status +
",excess=" +
issue.excess().to_string()
}
///|
pub fn state_guard_report_summary(report : StateGuardReport) -> String {
"accepted=" +
report.accepted().to_string() +
",changed=" +
report.changed().to_string() +
",issues=" +
report.issues().length().to_string() +
",score=" +
report.score().to_string()
}
///|
pub fn state_guard_is_finite(state : Array[Double]) -> Bool {
vector_is_finite(state)
}
///|
pub fn state_guard_distance(
original : Array[Double],
repaired : Array[Double],
) -> Double {
vector_distance(original, repaired)
}
///|
pub fn state_guard_changed_indices(report : StateGuardReport) -> Array[Int] {
let original = report.original()
let repaired = report.repaired()
let result : Array[Int] = []
for i in 0.. 1.0e-12 {
result.push(i)
}
}
result
}
///|
pub fn state_guard_worst_issue(report : StateGuardReport) -> StateGuardIssue? {
let mut worst : StateGuardIssue? = None
for issue in report.issues() {
match worst {
None => worst = Some(issue)
Some(previous) =>
if issue.excess() > previous.excess() {
worst = Some(issue)
}
}
}
worst
}
///|
pub fn state_guard_validate_dimension(
state : Array[Double],
expected_dimension : Int,
) -> Bool {
expected_dimension >= 0 &&
state.length() == expected_dimension &&
vector_is_finite(state)
}
///|
pub fn state_guard_normalize_weights(weights : Array[Double]) -> Array[Double] {
let result = weights.map(value => value.max(0.0))
let total = vector_sum(result)
if total <= 0.0 {
Array::make(result.length(), 1.0 / result.length().max(1).to_double())
} else {
result.map(value => value / total)
}
}
///|
pub fn state_guard_weighted_distance(
left : Array[Double],
right : Array[Double],
weights : Array[Double],
) -> Double {
if left.length() != right.length() || left.length() != weights.length() {
return 1.0e300
}
let mut sum = 0.0
for i in 0..