67 lines
3.6 KiB
GDScript
67 lines
3.6 KiB
GDScript
|
|
extends SceneTree
|
||
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
||
|
|
const Q = preload("res://world/owniverse/quantity_math.gd")
|
||
|
|
var checks: int = 0
|
||
|
|
var failures: int = 0
|
||
|
|
func _initialize(): call_deferred("run")
|
||
|
|
func check(value: bool, message: String):
|
||
|
|
checks += 1
|
||
|
|
if not value:
|
||
|
|
failures += 1
|
||
|
|
push_error(message)
|
||
|
|
static func reference_split(remainders: Dictionary, key: String, count: Variant, weights: Dictionary) -> Dictionary:
|
||
|
|
var result: Dictionary = {}
|
||
|
|
var sum_weights: Variant = Q.sum(weights)
|
||
|
|
if (E.le(count, 0) or E.le(sum_weights, 0)): return result
|
||
|
|
var ids: Array = weights.keys()
|
||
|
|
var expected: Array = []
|
||
|
|
var remainder_keys: Array = []
|
||
|
|
var positive_sum: Variant = 0
|
||
|
|
for id in ids:
|
||
|
|
var remainder_key: String = key + ":" + str(id)
|
||
|
|
remainder_keys.append(remainder_key)
|
||
|
|
var value: Variant = E.add(E.mul_div(count, weights[id], sum_weights), remainders.get(remainder_key, 0))
|
||
|
|
expected.append(value)
|
||
|
|
positive_sum = E.add(positive_sum, E.maximum(0, value))
|
||
|
|
var order: Array = []
|
||
|
|
var rests: Array = []
|
||
|
|
var assigned: Variant = 0
|
||
|
|
for i in ids.size():
|
||
|
|
var normalized: Variant = E.mul_div(E.maximum(0, expected[i]), count, positive_sum) if E.gt(positive_sum, 0) else 0
|
||
|
|
result[ids[i]] = E.floor_value(normalized)
|
||
|
|
assigned = E.add(assigned, result[ids[i]])
|
||
|
|
rests.append(E.subtract(normalized, result[ids[i]]))
|
||
|
|
order.append(i)
|
||
|
|
order.sort_custom(func(a: int, b: int) -> bool: return E.gt(rests[a], rests[b]) if E.ne(rests[a], rests[b]) else str(ids[a]) < str(ids[b]))
|
||
|
|
for i in order:
|
||
|
|
if E.ge(assigned, count): break
|
||
|
|
result[ids[i]] = E.add(result[ids[i]], 1)
|
||
|
|
assigned = E.add(assigned, 1)
|
||
|
|
for i in ids.size():
|
||
|
|
remainders[remainder_keys[i]] = E.clamp_value(E.subtract(expected[i], result[ids[i]]), E.neg(1), 1)
|
||
|
|
return result
|
||
|
|
|
||
|
|
func run():
|
||
|
|
for count in [0, 1, 7, E.of("1.375"), 1000000, E.of("1e40"), E.of("1e80")]:
|
||
|
|
for weights in [{}, {"a": 0, "b": 0}, {"a": 0, "b": 7}, {"b": 0, "a": 7}, {"a": 1, "b": 1, "c": 1}, {"a": E.of("0.017"), "b": E.of("0.333333333333"), "c": 0}, {"a": E.of("1e40"), "b": 17, "c": 1}]:
|
||
|
|
for carry in [0, E.of("0.25"), E.of("-0.75")]:
|
||
|
|
var actual: Dictionary = {"case:a": carry}
|
||
|
|
var expected: Dictionary = actual.duplicate(true)
|
||
|
|
for iteration in 5:
|
||
|
|
var reference: Dictionary = reference_split(expected, "case", count, weights)
|
||
|
|
var result: Dictionary = Q.split(actual, "case", count, weights)
|
||
|
|
check(E.eq(result, reference), "Split preserves allocation")
|
||
|
|
check(E.eq(actual, expected), "Split preserves rounding carries")
|
||
|
|
for a in [0, 7, E.of("1e40"), E.of("-1e40"), E.of("1.375")]:
|
||
|
|
var values: Array = [0, 1, -7, E.of("0.017"), E.of("1e30")]
|
||
|
|
for divisor in [1, 3, E.of("0.017"), E.of("1e40")]:
|
||
|
|
var results: Array = E.mul_div_many(a, values, divisor)
|
||
|
|
for i in values.size(): check(E.eq(results[i], E.mul_div(a, values[i], divisor)), "Grouped ratios preserve scalar results")
|
||
|
|
for count in [1, 7, 1000000000, E.of("1e40"), E.of("1e80")]:
|
||
|
|
for factor in [200, 1000]:
|
||
|
|
var weights: Array = [0, 17, E.of("1e40"), E.multiply(count, factor)]
|
||
|
|
var results: Array = E.population_quotas(count, weights, factor)
|
||
|
|
for i in weights.size(): check(E.eq(results[i], E.mul_div(count, weights[i], E.multiply(count, factor))), "Cancelled population preserves exact scalar quotas")
|
||
|
|
print("Split optimizations: %d checks, %d failures" % [checks, failures])
|
||
|
|
quit(1 if failures else 0)
|