///|
pub struct Candidate {
name : String
variables : Array[Float]
score : Float
} derive(Debug, Eq)
///|
pub fn candidate(
name : String,
variables : Array[Float],
score : Float,
) -> Candidate {
{ name, variables, score }
}
///|
pub fn Candidate::variable(self : Candidate, index : Int) -> Float? {
self.variables.get(index)
}
///|
pub fn Candidate::dimension(self : Candidate) -> Int {
self.variables.length()
}
///|
pub fn Candidate::is_finite(self : Candidate) -> Bool {
self.score == self.score &&
self.variables.filter(fn(value) { value != value }).length() == 0
}
///|
///|
pub struct Constraint {
index : Int
lower : Float
upper : Float
} derive(Debug, Eq)
///|
pub fn constraint(index : Int, lower : Float, upper : Float) -> Constraint {
{ index, lower, upper }
}
///|
pub fn Constraint::contains(self : Constraint, candidate : Candidate) -> Bool {
match candidate.variable(self.index) {
Some(value) => value >= self.lower && value <= self.upper
None => false
}
}
///|
pub fn feasible(candidate : Candidate, constraints : Array[Constraint]) -> Bool {
if !candidate.is_finite() {
false
} else {
for constraint in constraints {
if !constraint.contains(candidate) {
return false
}
}
true
}
}
///|
pub fn optimize(
candidates : Array[Candidate],
constraints : Array[Constraint],
) -> Candidate? {
let mut best : Candidate? = None
for candidate in candidates {
if feasible(candidate, constraints) {
match best {
Some(current) =>
if candidate.score > current.score {
best = Some(candidate)
}
None => best = Some(candidate)
}
}
}
best
}
///|
pub fn evaluate_candidates(
candidates : Array[Candidate],
constraints : Array[Constraint],
) -> Array[Candidate] {
let result : Array[Candidate] = []
for candidate in candidates {
if feasible(candidate, constraints) {
result.push(candidate)
}
}
result
}
///|
pub fn best_candidate(candidates : Array[Candidate]) -> Candidate? {
optimize(candidates, [])
}
///|
pub fn worst_candidate(candidates : Array[Candidate]) -> Candidate? {
if candidates.length() == 0 {
None
} else {
let mut result = candidates[0]
for candidate in candidates[1:] {
if candidate.score < result.score {
result = candidate
}
}
Some(result)
}
}
///|
pub fn pareto_front(candidates : Array[Candidate]) -> Array[Candidate] {
let result : Array[Candidate] = []
for candidate in candidates {
let mut dominated = false
for other in candidates {
if other.score > candidate.score &&
other.dimension() == candidate.dimension() {
dominated = true
}
}
if !dominated {
result.push(candidate)
}
}
result
}
///|
pub fn score_range(candidates : Array[Candidate]) -> Float {
if candidates.length() == 0 {
0.0
} else {
let mut minimum = candidates[0].score
let mut maximum = candidates[0].score
for candidate in candidates {
if candidate.score < minimum {
minimum = candidate.score
}
if candidate.score > maximum {
maximum = candidate.score
}
}
maximum - minimum
}
}
///|
pub fn normalize_scores(candidates : Array[Candidate]) -> Array[Candidate] {
let result : Array[Candidate] = []
let stats = summarize(candidates.map(fn(item) { item.score }))
for candidate in candidates {
let normalized : Float = if stats.maximum == stats.minimum {
0.0
} else {
(candidate.score - stats.minimum) / (stats.maximum - stats.minimum)
}
result.push({ ..candidate, score: normalized })
}
result
}
///|
pub fn candidate_table(candidates : Array[Candidate]) -> ReportTable {
let rows : Array[Array[String]] = []
for candidate in candidates {
rows.push([
candidate.name,
"{candidate.score}",
"{candidate.dimension()}",
"{candidate.is_finite()}",
])
}
table(["candidate", "score", "dimensions", "finite"], rows)
}
///|
pub struct WeightedObjective {
weights : Array[Float]
offset : Float
} derive(Debug, Eq)
///|
pub fn weighted_objective(
weights : Array[Float],
offset : Float,
) -> WeightedObjective {
{ weights, offset }
}
///|
pub fn WeightedObjective::evaluate(
self : WeightedObjective,
variables : Array[Float],
) -> Float {
let mut score : Float = self.offset
for index, weight in self.weights {
if index < variables.length() {
score = score + weight * variables[index]
}
}
score
}
///|
pub fn WeightedObjective::rank(
self : WeightedObjective,
candidates : Array[Candidate],
) -> Array[Candidate] {
let result = candidates.map(fn(candidate) {
{ ..candidate, score: self.evaluate(candidate.variables) }
})
result.sort_by(fn(left, right) {
if left.score > right.score {
-1
} else if left.score < right.score {
1
} else {
0
}
})
result
}
///|
pub fn constraint_violation(
candidate : Candidate,
constraints : Array[Constraint],
) -> Float {
let mut total : Float = 0.0
for item in constraints {
match candidate.variable(item.index) {
Some(value) =>
if value < item.lower {
total = total + item.lower - value
} else if value > item.upper {
total = total + value - item.upper
}
None => total = total + item.upper - item.lower
}
}
total
}
///|
pub fn penalty_score(
candidate : Candidate,
constraints : Array[Constraint],
penalty : Float,
) -> Float {
candidate.score - penalty * constraint_violation(candidate, constraints)
}
///|
pub fn penalized_best(
candidates : Array[Candidate],
constraints : Array[Constraint],
penalty : Float,
) -> Candidate? {
let scored = candidates.map(fn(candidate) {
{ ..candidate, score: penalty_score(candidate, constraints, penalty) }
})
best_candidate(scored)
}
///|
pub fn sweep_candidates(
config : SweepConfig,
objective : WeightedObjective,
) -> Array[Candidate] {
let result : Array[Candidate] = []
for value in config.values() {
let variables = [value]
result.push(
candidate("{config.label}", variables, objective.evaluate(variables)),
)
}
result
}
///|
pub fn optimization_report(
candidates : Array[Candidate],
constraints : Array[Constraint],
) -> ReportSection {
let feasible_candidates = evaluate_candidates(candidates, constraints)
{
title: "Optimization",
kind: Method,
body: candidate_table(feasible_candidates).to_markdown(),
}
}