///|
/// Integer optimization utilities for candidate plans.
pub struct IntegerObjective {
values : Array[Int]
directions : Array[PortfolioDirection]
}
///|
/// Create an objective vector.
pub fn integer_objective(
values : Array[Int],
directions : Array[PortfolioDirection],
) -> IntegerObjective {
{ values: values.copy(), directions: directions.copy() }
}
///|
/// Return objective dimension.
pub fn IntegerObjective::length(self : IntegerObjective) -> Int {
self.values.length()
}
///|
/// Return whether this vector dominates another.
pub fn IntegerObjective::dominates(
self : IntegerObjective,
other : IntegerObjective,
) -> Bool {
if self.values.length() != other.values.length() {
return false
}
let mut strict = false
for index in 0..= other.values[index]
}
if !better {
return false
}
if self.values[index] != other.values[index] {
strict = true
}
}
strict
}
///|
/// Compare vectors lexicographically.
pub fn IntegerObjective::compare(
self : IntegerObjective,
other : IntegerObjective,
) -> Int {
let limit = if self.values.length() < other.values.length() {
self.values.length()
} else {
other.values.length()
}
for index in 0.. other.values[index]
}
return if better { -1 } else { 1 }
}
if self.values.length() == other.values.length() {
0
} else if self.values.length() < other.values.length() {
-1
} else {
1
}
}
///|
/// Return copied objective values.
pub fn IntegerObjective::values(self : IntegerObjective) -> Array[Int] {
self.values.copy()
}
///|
/// A weighted item used by integer knapsack helpers.
pub struct UtilityItem {
id : Int
weight : Int
value : Int
}
///|
/// Create a utility item.
pub fn utility_item(id : Int, weight : Int, value : Int) -> UtilityItem {
{ id, weight: if weight < 0 { 0 } else { weight }, value }
}
///|
/// Return value density using a scale factor.
pub fn UtilityItem::density(self : UtilityItem, scale : Int) -> Int {
if self.weight == 0 {
return self.value * scale
}
self.value * scale / self.weight
}
///|
/// Select a greedy value-density subset.
pub fn greedy_utility_subset(
items : Array[UtilityItem],
capacity : Int,
) -> Array[Int] {
let order = items.copy()
for left in 0.. order[left].density(1000) {
let temporary = order[left]
order[left] = order[right]
order[right] = temporary
}
}
}
let result : Array[Int] = []
let mut load = 0
for item in order {
if load + item.weight <= capacity {
result.push(item.id)
load += item.weight
}
}
result
}
///|
/// Return total weight of selected items.
pub fn utility_subset_weight(
items : Array[UtilityItem],
selected : Array[Int],
) -> Int {
let mut result = 0
for item in items {
if selected.contains(item.id) {
result += item.weight
}
}
result
}
///|
/// Return total value of selected items.
pub fn utility_subset_value(
items : Array[UtilityItem],
selected : Array[Int],
) -> Int {
let mut result = 0
for item in items {
if selected.contains(item.id) {
result += item.value
}
}
result
}
///|
/// Validate a selected subset.
pub fn validate_utility_subset(
items : Array[UtilityItem],
selected : Array[Int],
capacity : Int,
) -> Bool {
if utility_subset_weight(items, selected) > capacity {
return false
}
for left in 0.. (Array[Int], Bool) {
let result = selected.copy()
let before = utility_subset_value(items, result)
for item in items {
if result.contains(item.id) {
continue
}
for index in 0.. before {
return (result, true)
}
result[index] = removed
}
}
(result, false)
}
///|
/// Improve a subset through repeated swaps.
pub fn improve_utility_subset(
items : Array[UtilityItem],
selected : Array[Int],
capacity : Int,
) -> Array[Int] {
let result = selected.copy()
let mut changed = true
while changed {
let replacement = utility_best_swap(items, result, capacity)
if replacement.1 {
for index in 0.. Int {
let limit = if values.length() < weights.length() {
values.length()
} else {
weights.length()
}
let mut result = 0
for index in 0.. Array[Int]? {
if rows.length() == 0 {
return None
}
let mut result = rows[0]
for row in rows {
let candidate = integer_objective(row, directions)
let incumbent = integer_objective(result, directions)
if candidate.compare(incumbent) < 0 {
result = row
}
}
Some(result.copy())
}
///|
/// Return nondominated rows.
pub fn pareto_rows(
rows : Array[Array[Int]],
directions : Array[PortfolioDirection],
) -> Array[Array[Int]] {
let result : Array[Array[Int]] = []
for row in rows {
let candidate = integer_objective(row, directions)
let mut dominated = false
for other in rows {
if !same_int_array(row, other) &&
integer_objective(other, directions).dominates(candidate) {
dominated = true
}
}
if !dominated {
result.push(row.copy())
}
}
result
}
///|
/// Return an objective row signature.
pub fn objective_row_signature(row : Array[Int]) -> Int {
let mut result = 23
for value in row {
result = result * 37 + value
}
result
}
///|
/// Return a min/max normalized score.
pub fn normalize_objective(value : Int, lower : Int, upper : Int) -> Int {
if upper <= lower {
return 0
}
(value - lower) * 1000 / (upper - lower)
}
///|
/// Return whether an objective value meets a bound.
pub fn objective_meets(
direction : PortfolioDirection,
value : Int,
bound : Int,
) -> Bool {
if direction is Minimize {
value <= bound
} else {
value >= bound
}
}