///|
pub fn default_options() -> Options {
{ max_iterations: 100, tolerance: 1.0e-8, }
}
///|
fn finite(x : Double) -> Bool {
x == x && x.abs() <= 1.0e300
}
///|
fn tolerance_scale(target : Double) -> Double {
if target > 1.0 {
target
} else {
1.0
}
}
///|
fn valid_options(options : Options) -> Bool {
options.max_iterations >= 1 &&
options.max_iterations <= 1000 &&
finite(options.tolerance) &&
options.tolerance >= 1.0e-10 &&
options.tolerance <= 1.0e-3
}
///|
fn compile(
problem : Problem,
tolerance : Double,
) -> Result[Compiled, RakeError] {
if problem.rows.is_empty() ||
problem.rows.length() > 5000 ||
problem.margins.is_empty() ||
problem.margins.length() > 8 {
return Err(SizeLimit)
}
let rows = problem.rows.copy()
rows.sort_by(fn(a, b) { a.id.lexical_compare(b.id) })
for i, row in rows {
if row.id == "" ||
row.cells.length() != problem.margins.length() ||
!finite(row.base_weight) ||
!finite(row.lower) ||
!finite(row.upper) ||
row.lower < 0.0 ||
row.upper > 1.0e12 ||
row.lower > row.base_weight ||
row.base_weight > row.upper {
return Err(InvalidRow(row.id))
}
if row.upper == 0.0 {
if row.base_weight != 0.0 || row.lower != 0.0 {
return Err(InvalidRow(row.id))
}
} else if row.lower < 1.0e-12 {
return Err(InvalidRow(row.id))
}
if i > 0 && rows[i - 1].id == row.id {
return Err(DuplicateRow(row.id))
}
}
let names : Map[String, Bool] = Map([])
let groups : Array[Group] = []
let mut first_total = 0.0
let mut number_targets = 0
for col, margin in problem.margins {
if margin.name == "" ||
names.contains(margin.name) ||
margin.targets.is_empty() {
return Err(InvalidMargin(margin.name))
}
names[margin.name] = true
number_targets += margin.targets.length()
if number_targets > 256 {
return Err(SizeLimit)
}
let targets = margin.targets.copy()
targets.sort_by(fn(a, b) { a.category.lexical_compare(b.category) })
let categories : Map[String, Bool] = Map([])
let mut total = 0.0
for target in targets {
if target.category == "" ||
categories.contains(target.category) ||
!finite(target.total) ||
target.total < 0.0 ||
target.total > 1.0e12 {
return Err(InvalidMargin(margin.name))
}
categories[target.category] = true
}
for row in rows {
if !categories.contains(row.cells[col]) {
return Err(UnknownCategory(margin.name, row.cells[col]))
}
}
for target in targets {
total += target.total
let members : Array[Int] = []
let mut minimum = 0.0
let mut maximum = 0.0
for i, row in rows {
if row.cells[col] == target.category {
members.push(i)
minimum += row.lower
maximum += row.upper
}
}
let epsilon = tolerance * tolerance_scale(target.total)
if target.total < minimum - epsilon || target.total > maximum + epsilon {
return Err(InfeasibleMargin(margin.name, target.category))
}
// A zero-support group cannot be made positive by a tolerance loophole.
if target.total > 0.0 && maximum == 0.0 {
return Err(InfeasibleMargin(margin.name, target.category))
}
groups.push({
margin: margin.name,
category: target.category,
target: target.total,
members,
})
}
if col == 0 {
first_total = total
} else if (total - first_total).abs() >
tolerance * tolerance_scale(first_total) {
return Err(InconsistentTotals)
}
}
if first_total <= 0.0 {
return Err(InconsistentTotals)
}
Ok({ rows, groups, })
}