Enhance simulation performance and accuracy by optimizing physical amount calculations and refining arithmetic operations
This commit is contained in:
@@ -41,6 +41,11 @@ Stage обращается непосредственно к `world.presentation
|
||||
|
||||
В `owniverse.gd` остались создание состояния и модулей, скорость и проверка входного delta перед запуском эволюции. Дублирующее поле new_state и закомментированная старая реализация удалены. Единственный источник текущего состояния — `world.state`.
|
||||
|
||||
Внутри одной фазы снимок физических количеств переиспользуется для сверки
|
||||
этик, звёздного бюджета и бюджета структур. После списания партий или переходов
|
||||
создаётся новый снимок. Операции над одним типом объекта используют
|
||||
`physical_amount`, не пересчитывая полную карту вселенной.
|
||||
|
||||
### Порядок шага
|
||||
|
||||
1. Определить обработанную десятую года. При продвижении времени восстановить недостающие партии жизни и проверить вместимость.
|
||||
|
||||
@@ -216,7 +216,12 @@ func produce_newborn_lifetime(target: Dictionary, id: String, count: Variant, bo
|
||||
object_years = E.subtract(object_years, E.multiply(E.maximum(0, added), world.batches.expired_years(to_year, batch.start_year, batch.end_year)))
|
||||
object_years = E.maximum(0, object_years)
|
||||
for resource in products:
|
||||
var output: Variant = E.multiply(E.multiply(object_years, products[resource]), WorldParameters.production(target, world.catalog.entities[id]))
|
||||
var coefficient: Variant = E.multiply(products[resource], WorldParameters.production(target, world.catalog.entities[id]))
|
||||
# A giant cohort can accumulate more than E27 object-years. Clamp the
|
||||
# multiplicand before multiplying so the exact-number backend never
|
||||
# constructs an out-of-range intermediate value.
|
||||
var safe_years: Variant = (E.minimum(object_years, E.divide(E.max_value(), coefficient)) if E.gt(coefficient, 0) else 0)
|
||||
var output: Variant = E.multiply(safe_years, coefficient)
|
||||
if E.contains(["H", "S"], resource):
|
||||
output = E.minimum(output, target.energy)
|
||||
var actual: Variant = world.ledger.credit(target, resource, output)
|
||||
|
||||
@@ -76,8 +76,8 @@ static func consume_untriggered(world, target: Dictionary, plan: Dictionary, pop
|
||||
plan.to = E.maximum(plan.to, year)
|
||||
return removed
|
||||
|
||||
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary) -> Dictionary:
|
||||
State.reconcile(world, target, rules)
|
||||
static func prepare(world, target: Dictionary, expired: Dictionary, year: Variant, budget: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||
State.reconcile(world, target, rules, physical)
|
||||
var remaining: Dictionary = target.ethics.duplicate(true)
|
||||
var plans: Dictionary = {}
|
||||
var colonies: Array = []
|
||||
|
||||
@@ -10,10 +10,11 @@ static func is_space_civilization(id: String, rules: Dictionary) -> bool:
|
||||
|
||||
# Preserve any existing ethics. New populations start equally distributed;
|
||||
# reductions (including blasts) are proportional and never leave negative counts.
|
||||
static func reconcile(world, target: Dictionary, rules: Dictionary) -> void:
|
||||
static func reconcile(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||
if (not target.has("ethics")):
|
||||
target.ethics = {}
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
for id in rules:
|
||||
var count: Variant = physical.get(id, 0)
|
||||
var current: Dictionary = target.ethics.get(id, {})
|
||||
@@ -40,8 +41,9 @@ static func detach_space_civilizations(target: Dictionary, rules: Dictionary) ->
|
||||
|
||||
# A shared budget per stellar family, including stars bound into structures.
|
||||
# Reservations affect capacity, not the physical stock or its production.
|
||||
static func stellar_budget(world, target: Dictionary, rules: Dictionary) -> Dictionary:
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
static func stellar_budget(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
var capacity: Dictionary = {}
|
||||
for id in physical:
|
||||
var entity = world.catalog.entities[id]
|
||||
@@ -62,7 +64,7 @@ static func stellar_budget(world, target: Dictionary, rules: Dictionary) -> Dict
|
||||
var free: Dictionary = {}
|
||||
for home in capacity:
|
||||
free[home] = E.maximum(0, E.subtract(capacity[home], requested.get(home, 0)))
|
||||
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations, "habitats": habitat_budget(world, target, rules)}
|
||||
return {"capacity": capacity, "requested": requested, "free": free, "populations": populations, "habitats": habitat_budget(world, target, rules, physical)}
|
||||
|
||||
# Account for the source's existing reservation when changing stages. A failed
|
||||
# transition retains its population at the source, rather than losing objects.
|
||||
@@ -105,8 +107,9 @@ static func admit_transition(budget: Dictionary, rules: Dictionary, source: Stri
|
||||
# advanced populations priority and lets a demotion cascade in the same cycle.
|
||||
static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year: Variant) -> void:
|
||||
detach_space_civilizations(target, rules)
|
||||
reconcile(world, target, rules)
|
||||
var budget: Dictionary = stellar_budget(world, target, rules)
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
reconcile(world, target, rules, physical)
|
||||
var budget: Dictionary = stellar_budget(world, target, rules, physical)
|
||||
var remaining: Dictionary = budget.capacity.duplicate()
|
||||
var remaining_homes: Dictionary = budget.habitats.capacity.duplicate()
|
||||
var stages: Array = []
|
||||
@@ -141,10 +144,10 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
||||
target.ethics[previous][ethic] = E.add(target.ethics[previous].get(ethic, 0), removed[ethic])
|
||||
if E.eq(previous, "C1.9"):
|
||||
var stars: Dictionary = {}
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
for star in physical:
|
||||
if (E.eq(world.catalog.entities[star].event_group, "DwStar") and E.gt(physical[star], 0)):
|
||||
stars[star] = physical[star]
|
||||
var current_physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
for star in current_physical:
|
||||
if (E.eq(world.catalog.entities[star].event_group, "DwStar") and E.gt(current_physical[star], 0)):
|
||||
stars[star] = current_physical[star]
|
||||
var weights: Dictionary = QuantityMath.normalize_distribution(stars)
|
||||
# Worlds are aggregates: a star may host several inhabited planets.
|
||||
var assigned: Variant = 0
|
||||
@@ -158,13 +161,15 @@ static func enforce_capacity(world, target: Dictionary, rules: Dictionary, year:
|
||||
target.binds[owner] = {}
|
||||
target.binds[owner][previous] = E.add(target.binds[owner].get(previous, 0), amount)
|
||||
target.amount[previous] = E.subtract(target.amount[previous], amount)
|
||||
reconcile(world, target, rules)
|
||||
assign_homes(world, target, rules)
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
reconcile(world, target, rules, physical)
|
||||
assign_homes(world, target, rules, physical)
|
||||
|
||||
# Unity's structure chain: direct Claim resets the required structure count;
|
||||
# intervening stages require ten times more units of the preceding structure.
|
||||
static func habitat_budget(world, target: Dictionary, rules: Dictionary) -> Dictionary:
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
static func habitat_budget(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> Dictionary:
|
||||
if physical.is_empty():
|
||||
physical = world.ledger.physical_amounts(target)
|
||||
var structures: Array = []
|
||||
var capacity: Dictionary = {}
|
||||
for id in world.catalog.entities:
|
||||
@@ -198,8 +203,8 @@ static func habitat_budget(world, target: Dictionary, rules: Dictionary) -> Dict
|
||||
requested[req.id] = E.add(requested.get(req.id, 0), E.multiply(physical.get(id, 0), req.units))
|
||||
return {"capacity": capacity, "requested": requested, "requirements": requirements, "structures": structures, "anchors": anchors}
|
||||
|
||||
static func assign_homes(world, target: Dictionary, rules: Dictionary) -> void:
|
||||
var budget: Dictionary = habitat_budget(world, target, rules)
|
||||
static func assign_homes(world, target: Dictionary, rules: Dictionary, physical: Dictionary = {}) -> void:
|
||||
var budget: Dictionary = habitat_budget(world, target, rules, physical)
|
||||
var used: Dictionary = {}
|
||||
target.civilization_homes = {}
|
||||
var stages: Array = budget.anchors.keys()
|
||||
|
||||
@@ -92,8 +92,9 @@ func evolve_batch_generation(target: Dictionary, to_year: Variant, completed: Di
|
||||
return false
|
||||
var fractions: Dictionary = {}
|
||||
var outputs: Dictionary = {}
|
||||
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules)
|
||||
var civilization_transaction: Dictionary = CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules)
|
||||
var current_quantities: Dictionary = world.ledger.physical_amounts(target)
|
||||
var star_budget: Dictionary = CivilizationState.stellar_budget(world, target, Global.CivilizationRules, current_quantities)
|
||||
var civilization_transaction: Dictionary = CivilizationEvolution.prepare(world, target, expired, to_year, star_budget, Global.CivilizationRules, current_quantities)
|
||||
for id in expired:
|
||||
var total: Variant = 0
|
||||
for part : Dictionary in expired[id]:
|
||||
|
||||
@@ -11,12 +11,14 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
|
||||
trial.binds = target.binds.duplicate(true)
|
||||
trial.auto_resources = target.auto_resources.duplicate()
|
||||
trial.batches = target.batches.duplicate()
|
||||
# Physical object totals do not change while resources are withdrawn from
|
||||
# this transaction. Keep one snapshot instead of scanning every resource.
|
||||
var physical: Dictionary = world.ledger.physical_amounts(trial)
|
||||
var copied_batches: Dictionary = {}
|
||||
for resource in entity.cost:
|
||||
var cost: Variant = E.round_value(E.multiply(E.maximum(0, entity.cost[resource]), amount))
|
||||
if (automatic and E.lt(trial.auto_resources.get(resource, 0), cost)):
|
||||
return false
|
||||
var before: Dictionary = world.ledger.physical_amounts(trial)
|
||||
var taken: Dictionary = world.ledger.take_available(trial, resource, cost)
|
||||
var total: Variant = 0
|
||||
for quantity in taken.values():
|
||||
@@ -30,7 +32,7 @@ func try_production(target: Dictionary, entity: OwniverseEntity, amount: Variant
|
||||
if (trial.batches.has(id) and (not copied_batches.has(id))):
|
||||
trial.batches[id] = trial.batches[id].duplicate(true)
|
||||
copied_batches[id] = true
|
||||
var factor: Variant = (E.subtract(1, E.divide(taken[id], before[id])) if E.gt(before.get(id, 0), 0) else 0)
|
||||
var factor: Variant = (E.subtract(1, E.divide(taken[id], physical[id])) if E.gt(physical.get(id, 0), 0) else 0)
|
||||
world.batches.scale_batches(trial, id, factor)
|
||||
if (not world.ledger.bind_new_owners(trial, {entity.id: amount}, true)):
|
||||
return false
|
||||
|
||||
@@ -21,6 +21,11 @@ polarity × (high × 10^14 + low + fraction)
|
||||
## Операции
|
||||
|
||||
Сложение, вычитание и сравнение работают непосредственно с блоками.
|
||||
Деление int64 и объединённое умножение/деление при безопасном произведении
|
||||
используют нативную арифметику. Перевод больших целых между блоками и временными
|
||||
разрядами, извлечение коэффициента и сборка результата обходятся без строк.
|
||||
Дробный коэффициент нормализуется численно до 16 значащих цифр; последние биты
|
||||
float могут отличаться от результата прежнего строкового преобразования.
|
||||
Для умножения и деления `big_integer.gd` используется как временный вычислитель
|
||||
с безопасными разрядами по основанию 10^9. Результат сразу возвращается в
|
||||
фиксированную структуру. Размер временных дробей определяется входными float,
|
||||
|
||||
@@ -8,31 +8,55 @@ const MIN_INT: int = -9223372036854775807 - 1
|
||||
const BLOCK: int = 100000000000000 # 10^14
|
||||
const LIMIT: Array = [0, 0, 0, 10] # 10^28, exclusive
|
||||
|
||||
static func max_value():
|
||||
return make([999999999, 999999999, 9999])
|
||||
|
||||
static func make(n: Array, d: Array = [1], polarity: int = 1):
|
||||
assert(d != [0], "Zero denominator")
|
||||
if n.size() <= 2 and d.size() <= 2:
|
||||
var numerator: int = n[0] + (n[1] * SMALL if n.size() == 2 else 0)
|
||||
var denominator: int = d[0] + (d[1] * SMALL if d.size() == 2 else 0)
|
||||
var whole: int = numerator / denominator
|
||||
var fraction: float = minf(float(numerator % denominator) / float(denominator), 1.0 - pow(2.0, -53))
|
||||
return _compose(whole / BLOCK, whole % BLOCK, fraction, polarity)
|
||||
var qr: Array = Big.divmod(n, d)
|
||||
var fraction: float = 0.0
|
||||
if qr[1] != [0]:
|
||||
var numerator_text: String = Big.decimal(qr[1])
|
||||
var denominator_text: String = Big.decimal(d)
|
||||
if denominator_text.length() < 300:
|
||||
fraction = float(numerator_text) / float(denominator_text)
|
||||
else:
|
||||
var exponent: int = maxi(0, numerator_text.length() - 16) - maxi(0, denominator_text.length() - 16)
|
||||
fraction = float(numerator_text.left(16)) / float(denominator_text.left(16))
|
||||
fraction *= pow(10.0, maxi(-308, exponent))
|
||||
fraction *= pow(10.0, mini(0, exponent + 308))
|
||||
# A remainder below one must not round up and create a whole object.
|
||||
fraction = minf(fraction, 1.0 - pow(2.0, -53))
|
||||
var fraction: float = _fraction_ratio(qr[1], d)
|
||||
return _from_whole(qr[0], fraction, polarity)
|
||||
|
||||
static func _fraction_ratio(n: Array, d: Array) -> float:
|
||||
if n == [0]: return 0.0
|
||||
var numerator: float = 0.0
|
||||
var denominator: float = 0.0
|
||||
var n_start: int = maxi(0, n.size() - 3)
|
||||
var d_start: int = maxi(0, d.size() - 3)
|
||||
for i in range(n.size() - 1, n_start - 1, -1): numerator = numerator * SMALL + n[i]
|
||||
for i in range(d.size() - 1, d_start - 1, -1): denominator = denominator * SMALL + d[i]
|
||||
var exponent: int = 9 * (n_start - d_start)
|
||||
var fraction: float = numerator / denominator * pow(10.0, maxi(-308, exponent))
|
||||
fraction *= pow(10.0, mini(0, exponent + 308))
|
||||
return minf(fraction, 1.0 - pow(2.0, -53))
|
||||
|
||||
static func _from_whole(n: Array, fraction: float, polarity: int):
|
||||
var text: String = Big.decimal(n)
|
||||
assert(Big.compare(n, LIMIT) < 0, "Number exceeds E27 range")
|
||||
var high: int = int(text.left(text.length() - 14)) if text.length() > 14 else 0
|
||||
var low: int = int(text.right(14))
|
||||
var middle: int = n[1] if n.size() > 1 else 0
|
||||
var low: int = n[0] + (middle % 100000) * SMALL
|
||||
var high: int = middle / 100000
|
||||
if n.size() > 2: high += n[2] * 10000
|
||||
if n.size() > 3: high += n[3] * 10000000000000
|
||||
return _compose(high, low, fraction, polarity)
|
||||
|
||||
static func _whole_limbs(high: int, low: int) -> Array:
|
||||
return Big.trim([low % SMALL, low / SMALL + (high % 10000) * 100000,
|
||||
(high / 10000) % SMALL, high / 10000000000000])
|
||||
|
||||
static func _int_limbs(value: int) -> Array:
|
||||
var result: Array = []
|
||||
# Modulo/division before abs also supports MIN_INT.
|
||||
while value != 0:
|
||||
result.append(absi(value % SMALL))
|
||||
value /= SMALL
|
||||
return result if not result.is_empty() else [0]
|
||||
|
||||
static func _compose(high: int, low: int, fraction: float, polarity: int):
|
||||
if fraction >= 1.0:
|
||||
low += int(floorf(fraction))
|
||||
@@ -91,17 +115,23 @@ static func of(value):
|
||||
|
||||
static func parts(value) -> Array:
|
||||
value = of(value)
|
||||
if value is int: return [Big.parse(str(value).trim_prefix("-")), [1], -1 if value < 0 else 1]
|
||||
var whole: Array = Big.parse(str(value.high) + "%014d" % value.low)
|
||||
if value is int: return [_int_limbs(value), [1], -1 if value < 0 else 1]
|
||||
var whole: Array = _whole_limbs(value.high, value.low)
|
||||
if value.fraction == 0.0: return [whole, [1], value.polarity]
|
||||
# Bound temporary decimal work to 16 significant digits of the float.
|
||||
# Normalize first so tiny rates retain their significant digits as well.
|
||||
var exponent: int = maxi(-323, int(floor(log(value.fraction) / log(10.0))))
|
||||
var text: String = String.num(value.fraction / pow(10.0, exponent), 15)
|
||||
var point: int = text.find(".")
|
||||
if point >= 0: exponent -= text.length() - point - 1
|
||||
var n: Array = Big.parse(text.replace(".", ""))
|
||||
var d: Array = Big.parse("1" + "0".repeat(-exponent))
|
||||
var mantissa: int = roundi((value.fraction / pow(10.0, exponent)) * 1000000000000000.0)
|
||||
exponent -= 15
|
||||
while mantissa % 10 == 0 and mantissa > 0:
|
||||
mantissa /= 10
|
||||
exponent += 1
|
||||
var n: Array = _int_limbs(mantissa)
|
||||
var d: Array = []
|
||||
d.resize((-exponent) / 9 + 1)
|
||||
d.fill(0)
|
||||
const POWERS: Array = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000]
|
||||
d[d.size() - 1] = POWERS[(-exponent) % 9]
|
||||
return [Big.add(Big.multiply(whole, d), n), d, value.polarity]
|
||||
|
||||
static func numeric(value) -> bool:
|
||||
@@ -208,6 +238,8 @@ static func multiply(a, b):
|
||||
## Fused ratio: the temporary product is never stored as a simulation value.
|
||||
static func mul_div(a, b, divisor):
|
||||
if not finite(a) or not finite(b) or not finite(divisor): return of(approx(a) * approx(b) / approx(divisor))
|
||||
if a is int and b is int and a != MIN_INT and b != MIN_INT:
|
||||
if b == 0 or absi(a) <= MAX_INT / absi(b): return divide(a * b, divisor)
|
||||
var x: Array = parts(a)
|
||||
var y: Array = parts(b)
|
||||
var z: Array = parts(divisor)
|
||||
@@ -220,7 +252,16 @@ static func divide(a, b):
|
||||
if eq(b, 1): return of(a)
|
||||
if a is int and b is int and b != 0 and not (a == MIN_INT and b == -1):
|
||||
if a % b == 0: return a / b
|
||||
return mul_div(a, 1, b)
|
||||
if a != MIN_INT and b != MIN_INT:
|
||||
var numerator: int = absi(a)
|
||||
var denominator: int = absi(b)
|
||||
var whole: int = numerator / denominator
|
||||
var fraction: float = minf(float(numerator % denominator) / float(denominator), 1.0 - pow(2.0, -53))
|
||||
return _compose(whole / BLOCK, whole % BLOCK, fraction, -1 if (a < 0) != (b < 0) else 1)
|
||||
var x: Array = parts(a)
|
||||
var y: Array = parts(b)
|
||||
assert(y[0] != [0], "Division by zero")
|
||||
return make(Big.multiply(x[0], y[1]), Big.multiply(x[1], y[0]), x[2] * y[2])
|
||||
|
||||
static func floor_value(a):
|
||||
if not finite(a) or a is int: return a
|
||||
|
||||
@@ -6,6 +6,17 @@ const WorldParameters = preload("res://world/owniverse/world_parameters.gd")
|
||||
|
||||
func scale_batches(target: Dictionary, id: String, factor: Variant) -> void:
|
||||
var batches: Array = target.batches.get(id, [])
|
||||
if batches.is_empty():
|
||||
return
|
||||
# The common case for short-lived stellar events is a single cohort. Avoid
|
||||
# allocating the shares array and sorting it when no redistribution is
|
||||
# needed.
|
||||
if batches.size() == 1:
|
||||
var only: Dictionary = batches[0]
|
||||
var expected: Variant = E.multiply(only.amount, E.maximum(0, factor))
|
||||
only.amount = E.round_value(expected)
|
||||
only.withdraw_remainder = E.minimum(only.get("withdraw_remainder", 0), only.amount)
|
||||
return
|
||||
var total: Variant = 0
|
||||
var assigned: Variant = 0
|
||||
var shares: Array = []
|
||||
|
||||
@@ -15,6 +15,13 @@ func physical_amounts(source: Dictionary) -> Dictionary:
|
||||
return amounts
|
||||
|
||||
|
||||
func physical_amount(source: Dictionary, id: String) -> Variant:
|
||||
var amount: Variant = E.maximum(0, source.amount.get(id, 0))
|
||||
for bindings: Dictionary in source.binds.values():
|
||||
amount = E.add(amount, E.maximum(0, bindings.get(id, 0)))
|
||||
return amount
|
||||
|
||||
|
||||
func available(source: Dictionary, id: String) -> Variant:
|
||||
var amount: Variant = 0
|
||||
for member in world.catalog.members(id):
|
||||
|
||||
@@ -69,7 +69,6 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
||||
if (E.le(ratio, 0) or not E.contains([1, 2], level)):
|
||||
return
|
||||
var held: Dictionary = target.binds.get(owner_id, {})
|
||||
var physical: Dictionary = world.ledger.physical_amounts(target)
|
||||
var rocky: Variant = 0
|
||||
for id in held.keys():
|
||||
if ((not world.catalog.entities.has(id)) or E.ne(world.catalog.entities[id].group_as, "Planet")):
|
||||
@@ -82,8 +81,10 @@ func destroy_planets(target: Dictionary, owner_id: String, ratio: Variant, level
|
||||
target.destruction_remainders[key] = 0
|
||||
if E.le(destroyed, 0):
|
||||
continue
|
||||
var physical: Variant = world.ledger.physical_amount(target, id)
|
||||
held[id] = E.subtract(held[id], destroyed)
|
||||
world.batches.scale_batches(target, id, E.subtract(1, E.divide(destroyed, physical[id])))
|
||||
if E.gt(physical, 0):
|
||||
world.batches.scale_batches(target, id, E.subtract(1, E.divide(destroyed, physical)))
|
||||
if E.eq(level, 1):
|
||||
rocky = E.add(rocky, destroyed)
|
||||
if E.gt(rocky, 0):
|
||||
|
||||
Reference in New Issue
Block a user