factorio-base-definer/calc2.py

424 lines
15 KiB
Python
Raw Permalink Normal View History

2024-03-17 22:23:14 +00:00
import math
import typing as tp
2024-02-16 09:20:00 +00:00
from functools import lru_cache
import pdbp # noqa: F401
ResourceGroup = dict[str, float]
2024-02-16 05:05:12 +00:00
@lru_cache
def _dot_align(seq: tuple[float, ...]) -> dict[float, str]:
strs = [(f'{n:.9g}', n) for n in seq]
dots = [(s.find('.') if '.' in s else len(s), s, n) for s, n in strs]
md, _, _ = max(dots)
rv = {n: (' ' * (md - d) + s)[: md + 4] for d, s, n in dots}
assert not all(s.startswith(' ') for s in rv.values())
return rv
def dot_align(seq: tuple[float, ...], n) -> str:
return _dot_align(seq)[n]
2024-02-16 09:20:00 +00:00
recipies: dict[str, ResourceGroup] = {
2024-02-15 04:05:17 +00:00
# mining
'iron-ore': {'mining-drill': -1, 'iron-ore': 0.5},
'copper-ore': {'mining-drill': -1, 'copper-ore': 0.5},
'stone': {'mining-drill': -1, 'stone': 0.5},
'coal': {'mining-drill': -1, 'coal': 0.5},
'uranium-ore': {'mining-drill': -1, 'sulfuric-acid': -0.25, 'uranium-ore': 0.5},
2024-03-17 21:46:27 +00:00
'water': {'offshore-pump': -1, 'water': 1200},
'crude-oil': {'pumpjack': -1, 'crude-oil': 10}, # pumpjack @ 100% yield = 10 crude-oil / second
2024-02-15 04:05:17 +00:00
# oil
'basic-oil-processing': {'oil-refinery': -5, 'crude-oil': -100, 'petroleum-gas': 45},
'advanced-oil-processing': {
'oil-refinery': -5,
'crude-oil': -100,
'water': -50,
'petroleum-gas': 55,
'light-oil': 45,
'heavy-oil': 25,
},
'coal-liquefaction': {
'oil-refinery': -5,
'coal': -10,
'steam': -50,
'petroleum-gas': 10,
'light-oil': 20,
'heavy-oil': -25 + 90,
},
2024-02-17 03:50:45 +00:00
'heavy-oil-cracking': {'chemical-plant': -2, 'heavy-oil': -40, 'water': -30, 'light-oil': 30},
'light-oil-cracking': {'chemical-plant': -2, 'light-oil': -30, 'water': -30, 'petroleum-gas': 20},
2024-02-15 04:05:17 +00:00
# fuel
2024-02-29 03:46:04 +00:00
'solid-fuel': {'chemical-plant': -2, 'petroleum-gas': -20, 'solid-fuel': 1},
2024-02-15 04:05:17 +00:00
'solid-fuel-lo': {'chemical-plant': -2, 'light-oil': -10, 'solid-fuel': 1},
'solid-fuel-ho': {'chemical-plant': -2, 'heavy-oil': -20, 'solid-fuel': 1},
'rocket-fuel': {'assembler': -30, 'light-oil': -10, 'solid-fuel': -10, 'rocket-fuel': 1},
# smelting
'iron-plate': {'furnace': -3.2, 'iron-ore': -1, 'iron-plate': 1},
'copper-plate': {'furnace': -3.2, 'copper-ore': -1, 'copper-plate': 1},
'stone-brick': {'furnace': -3.2, 'stone': -2, 'stone-brick': 1},
2024-02-15 04:05:17 +00:00
'steel': {'furnace': -16, 'iron-plate': -5, 'steel': 1},
# crafting basics
'iron-gear': {'assembler': -0.5, 'iron-plate': -2, 'iron-gear': 1},
'iron-stick': {'assembler': -0.5, 'iron-plate': -1, 'iron-stick': 2},
'pipe': {'assembler': -0.5, 'iron-plate': -1, 'pipe': 1},
'copper-cable': {'assembler': -0.5, 'copper-plate': -1, 'copper-cable': 2},
# circuits
'circuit-a': {'assembler': -0.5, 'copper-cable': -3, 'iron-plate': -1, 'circuit-a': 1},
'circuit-b': {'assembler': -6, 'copper-cable': -4, 'circuit-a': -2, 'plastic': -2, 'circuit-b': 1},
2024-02-17 03:50:45 +00:00
'circuit-c': {'assembler': -10, 'circuit-b': -2, 'circuit-a': -20, 'sulfuric-acid': -5, 'circuit-c': 1},
2024-02-15 04:05:17 +00:00
# logistics
'belt-y': {'assembler': -0.5, 'iron-gear': -1, 'iron-plate': -1, 'belt-y': 2},
'inserter-y': {'assembler': -0.5, 'circuit-a': -1, 'iron-gear': -1, 'iron-plate': -1, 'inserter-y': 1},
# transport
'concrete': {'assembler': -10, 'iron-ore': -1, 'stone-brick': -5, 'water': -100, 'concrete': 10},
'rail': {'assembler': -0.5, 'iron-stick': -1, 'steel': -1, 'stone': -1, 'rail': 2},
2024-02-15 04:05:17 +00:00
# chemicals
'sulfur': {'chemical-plant': -1, 'petroleum-gas': -30, 'water': -30, 'sulfur': 2},
'sulfuric-acid': {'chemical-plant': -1, 'iron-plate': -1, 'sulfur': -5, 'water': -100, 'sulfuric-acid': 50},
'plastic': {'chemical-plant': -1, 'coal': -1, 'petroleum-gas': -20, 'plastic': 2},
'explosives': {'chemical-plant': -4, 'coal': -1, 'sulfur': -1, 'water': -10, 'explosives': 2},
'battery': {'chemical-plant': -4, 'copper-plate': -1, 'iron-plate': -1, 'sulfuric-acid': -20, 'battery': 1},
2024-02-16 09:20:00 +00:00
'lubricant': {'chemical-plant': -1, 'heavy-oil': -10, 'lubricant': 10},
2024-02-15 04:05:17 +00:00
# military
2024-02-29 03:46:04 +00:00
'radar': {'assembler': -0.5, 'circuit-a': -5, 'iron-gear': -5, 'iron-plate': -10, 'radar': 1},
2024-02-15 04:05:17 +00:00
'magazine-y': {'assembler': -1, 'iron-plate': -4, 'magazine-y': 1},
'magazine-r': {'assembler': -3, 'copper-plate': -5, 'magazine-y': -1, 'steel': -1, 'magazine-r': 1},
2024-02-15 04:05:17 +00:00
'grenade': {'assembler': -8, 'coal': -10, 'iron-plate': -5, 'grenade': 1},
'wall': {'assembler': -0.5, 'stone-brick': -5, 'wall': 1},
'rocket-y': {'assembler': -8, 'circuit-a': -1, 'explosives': -1, 'iron-plate': -2, 'rocket-y': 1},
'rocket-r': {'assembler': -8, 'explosives': -2, 'rocket-r': 1},
# intermediates
'engine-unit': {'assembler': -10, 'iron-gear': -1, 'pipe': -2, 'steel': -1, 'engine-unit': 1},
'engine-unit-electric': {
'assembler': -10,
'circuit-a': -2,
'engine-unit': -1,
'lubricant': -15,
'engine-unit-electric': 1,
},
'accumulator': {'assembler': -10, 'battery': -5, 'iron-plate': -2, 'accumulator': 1},
'solar-panel': {'assembler': -10, 'copper-plate': -5, 'circuit-a': -15, 'steel': -5, 'solar-panel': 1},
'electric-furnace': {'assembler': -5, 'circuit-b': -5, 'steel': -10, 'stone-brick': -10, 'electric-furnace': 1},
'flying-robot-frame': {
'assembler': -20,
'battery': -2,
'engine-unit-electric': -1,
'circuit-a': -3,
'steel': -1,
'flying-robot-frame': 1,
},
'low-density-structure': {
'assembler': -20,
'copper-plate': -20,
'plastic': -5,
'steel': -2,
'low-density-structure': 1,
},
# modules
'module-productivity': {'assembler': -15, 'circuit-b': -5, 'circuit-a': -5, 'module-productivity': 1},
'module-speed': {'assembler': -15, 'circuit-b': -5, 'circuit-a': -5, 'module-speed': 1},
# end-game
'rocket-control-unit': {'assembler': -30, 'circuit-c': -1, 'module-speed': -1, 'rocket-control-unit': 1},
'rocket-part': {
'rocket-silo': -3,
'low-density-structure': -10,
'rocket-control-unit': -10,
'rocket-fuel': -10,
'rocket-part': 1,
},
'sattelite': {
'assembler': -5,
'accumulator': -100,
'low-density-structure': -100,
'circuit-c': -100,
'radar': -5,
'rocket-fuel': -50,
'solar-panel': -100,
'sattelite': 1,
},
# science packs
'science-red': {'assembler': -5, 'copper-plate': -1, 'iron-gear': -1, 'science-red': 1},
'science-green': {'assembler': -6, 'belt-y': -1, 'inserter-y': -1, 'science-green': 1},
'science-gray': {'assembler': -10, 'magazine-r': -1, 'grenade': -1, 'wall': -2, 'science-gray': 2},
'science-blue': {'assembler': -24, 'sulfur': -1, 'circuit-b': -3, 'engine-unit': -2, 'science-blue': 2},
'science-purple': {
'assembler': -21,
'rail': -30,
'electric-furnace': -1,
'module-productivity': -1,
'science-purple': 3,
},
'science-yellow': {
'assembler': -21,
'circuit-c': -2,
'flying-robot-frame': -1,
'low-density-structure': -3,
'science-yellow': 3,
},
'science-white': {'rocket-part': -100, 'sattelite': -1, 'science-white': 1000},
}
2024-03-17 21:46:27 +00:00
MACHINES = {
'assembler',
'chemical-plant',
'oil-refinery',
'rocket-silo',
'furnace',
'mining-drill',
'pumpjack',
'offshore-pump',
}
2024-02-16 09:20:00 +00:00
2024-03-19 03:25:16 +00:00
MACHINES_REAL = {
'assembler': ('_assembler-2', 0.75),
}
2024-02-15 04:05:17 +00:00
2024-02-15 07:33:18 +00:00
def get_resource_name(orig_name: str, recipe_name: str) -> str:
2024-02-16 09:20:00 +00:00
if orig_name in MACHINES:
2024-02-15 07:33:18 +00:00
return f'{orig_name}[{recipe_name}]'
else:
return orig_name
2024-02-16 09:20:00 +00:00
def add_assembler_name(recipe_name: str, resources: ResourceGroup) -> ResourceGroup:
2024-02-15 07:33:18 +00:00
return {get_resource_name(resource, recipe_name): count for resource, count in resources.items()}
2024-02-15 07:33:18 +00:00
recipies = {name: add_assembler_name(name, resources) for name, resources in recipies.items()}
2024-02-16 09:20:00 +00:00
def add_recipe(targets: ResourceGroup, recipe: ResourceGroup, multiplier: float) -> ResourceGroup:
return {
resource: targets.get(resource, 0) - (multiplier * recipe.get(resource, 0))
for resource in targets.keys() | recipe.keys()
if targets.get(resource, 0) - (multiplier * recipe.get(resource, 0)) != 0
}
2024-03-17 22:23:14 +00:00
def sum_recipies(recipies: tp.Iterable[ResourceGroup]) -> ResourceGroup:
result = {}
for recipe in recipies:
result = add_recipe(result, recipe, -1)
return result
2024-02-16 09:20:00 +00:00
def reduce_to_base_resources(group: ResourceGroup, base: set[str]) -> tuple[ResourceGroup, ResourceGroup]:
group = group.copy()
reduced = {}
2024-02-16 04:13:33 +00:00
intermediates = {}
2024-02-15 07:33:18 +00:00
2024-02-16 09:20:00 +00:00
while len(group) > 0:
resource, count = next(iter(group.items()))
2024-02-15 07:33:18 +00:00
# print(f'{resource}: {count}')
2024-02-16 09:20:00 +00:00
if resource in base or '[' in resource:
reduced = add_recipe(reduced, {resource: count}, -1)
del group[resource]
2024-02-15 07:33:18 +00:00
continue
2024-02-16 09:20:00 +00:00
if resource not in recipies:
raise RuntimeError(f'{resource=} not in base or recipies')
2024-02-16 04:13:33 +00:00
intermediates = add_recipe(intermediates, {resource: count}, -1)
2024-02-15 07:33:18 +00:00
recipe = recipies[resource]
multiplier = count / recipe[resource]
2024-02-16 09:20:00 +00:00
group = add_recipe(group, recipe, multiplier)
return reduced, intermediates
2024-02-15 07:33:18 +00:00
2024-02-16 09:20:00 +00:00
2024-03-17 22:23:14 +00:00
def drop_machines(group: ResourceGroup) -> tuple[ResourceGroup, ResourceGroup]:
group_wo_machines = {k: v for k, v in group.items() if not k.startswith(tuple(MACHINES))}
group_machines = {k: v for k, v in group.items() if k.startswith(tuple(MACHINES))}
return group_wo_machines, group_machines
2024-02-16 09:20:00 +00:00
2024-03-19 03:25:16 +00:00
def get_real_resource_counts(group: ResourceGroup) -> list[tuple[str, float]]:
def get_real_rc(rc: tuple[str, float]) -> tuple[str, float]:
resource, count = rc
for resource_from, (resource_real, ratio) in MACHINES_REAL.items():
if resource.startswith(resource_from):
resource = resource.replace(resource_from, resource_real)
count = count / ratio
break
return resource, count
rcs = list(map(get_real_rc, group.items()))
return rcs
2024-02-16 09:20:00 +00:00
def print_resource_group(group: ResourceGroup) -> None:
2024-03-19 03:25:16 +00:00
rcs = get_real_resource_counts(group)
cs = tuple(map(lambda rc: rc[1], rcs))
for resource, count in sorted(rcs):
print(f' {resource:40}: {dot_align(cs, count)}')
2024-03-19 03:25:16 +00:00
# science-per-second
SPS = 1
2024-02-17 03:50:45 +00:00
bus_groups: list[ResourceGroup] = [
2024-03-19 03:25:16 +00:00
{'science-red': SPS},
{'science-green': SPS},
{'science-gray': SPS},
{'science-blue': SPS},
{'science-purple': SPS},
{'science-yellow': SPS},
{'science-white': SPS},
2024-02-15 07:33:18 +00:00
]
2024-02-16 09:20:00 +00:00
bus_base = {
2024-02-15 07:33:18 +00:00
'iron-plate',
'copper-plate',
'stone',
'stone-brick',
'coal',
'steel',
'sulfur',
2024-02-16 05:05:12 +00:00
'sulfuric-acid',
2024-02-16 09:20:00 +00:00
'plastic',
2024-02-16 05:05:12 +00:00
'lubricant',
2024-02-29 03:46:04 +00:00
'rocket-fuel',
2024-02-15 07:33:18 +00:00
}
2024-03-19 03:25:16 +00:00
print(f'{SPS=}')
2024-02-16 09:20:00 +00:00
print(f'{bus_base=}')
2024-02-16 05:05:12 +00:00
bus_inputs = {}
2024-02-17 03:50:45 +00:00
for group in bus_groups:
reduced, intermediates = reduce_to_base_resources(group, bus_base)
2024-02-15 07:33:18 +00:00
print()
2024-02-17 03:50:45 +00:00
print(f'{group=}')
2024-02-16 04:13:33 +00:00
print('intermediates')
2024-02-16 09:20:00 +00:00
print_resource_group(intermediates)
print('reduced')
print_resource_group(reduced)
2024-02-16 05:05:12 +00:00
2024-02-16 09:20:00 +00:00
bus_inputs = add_recipe(bus_inputs, reduced, -1)
2024-02-16 05:05:12 +00:00
2024-03-17 22:23:14 +00:00
bus_inputs, bus_machines = drop_machines(bus_inputs)
2024-02-16 05:05:12 +00:00
print()
2024-02-17 03:50:45 +00:00
print('main bus inputs')
2024-02-16 09:20:00 +00:00
print_resource_group(bus_inputs)
2024-02-16 05:22:29 +00:00
2024-02-17 03:50:45 +00:00
# chemical processing
2024-02-29 03:46:04 +00:00
chemical_outputs_keys = {'sulfur', 'sulfuric-acid', 'plastic', 'rocket-fuel', 'lubricant'}
2024-02-17 03:50:45 +00:00
chemical_outputs = {k: v for k, v in bus_inputs.items() if k in chemical_outputs_keys}
bus_inputs = {k: v for k, v in bus_inputs.items() if k not in chemical_outputs_keys}
2024-02-16 09:20:00 +00:00
2024-02-17 03:50:45 +00:00
chemical_base = {'petroleum-gas', 'light-oil', 'heavy-oil', 'water', 'iron-plate', 'coal'}
2024-02-16 09:20:00 +00:00
print()
2024-02-17 03:50:45 +00:00
print('chemical processing')
2024-02-16 09:20:00 +00:00
print()
2024-02-17 03:50:45 +00:00
print(f'{chemical_outputs=}')
print(f'{chemical_base=}')
chemical_inputs, intermediates = reduce_to_base_resources(chemical_outputs, chemical_base)
2024-02-16 09:20:00 +00:00
print('intermediates')
print_resource_group(intermediates)
print('reduced')
2024-02-17 03:50:45 +00:00
print_resource_group(chemical_inputs)
2024-02-16 09:20:00 +00:00
2024-03-17 22:23:14 +00:00
chemical_inputs, chemical_machines = drop_machines(chemical_inputs)
2024-02-16 09:20:00 +00:00
print()
2024-02-17 03:50:45 +00:00
print('chemical processing inputs')
print_resource_group(chemical_inputs)
2024-02-16 09:20:00 +00:00
# oil refinery
2024-02-17 03:50:45 +00:00
oil_outputs_keys = {'petroleum-gas', 'light-oil', 'heavy-oil'}
oil_outputs = {k: v for k, v in chemical_inputs.items() if k in oil_outputs_keys}
chemical_inputs = {k: v for k, v in chemical_inputs.items() if k not in oil_outputs_keys}
# TODO: create exactly enough oil such that there is enough petroleum-gas
# free A, B, C, define m, n where A * X + B * Y + C * Z + m * U + n * V = outputs
# A, B, C = multiplier on basic/advanced/coal refinery
# m, n = multiplier on heavy/light cracking
# define J, K, L proportional to A, B, C
ratio = {
'basic-oil-processing': 0,
2024-02-29 03:46:04 +00:00
'advanced-oil-processing': 35,
2024-02-17 04:24:00 +00:00
'coal-liquefaction': 5,
2024-02-17 03:50:45 +00:00
}
2024-03-17 21:46:27 +00:00
# ratio = {
# 'basic-oil-processing': 0,
# 'advanced-oil-processing': 10,
# 'coal-liquefaction': 5,
# }
2024-02-17 03:50:45 +00:00
oil_inputs = oil_outputs.copy()
for recipe_name, ratio in ratio.items():
or_secs = recipies[recipe_name][f'oil-refinery[{recipe_name}]']
oil_inputs = add_recipe(oil_inputs, recipies[recipe_name], -ratio / or_secs)
hoc = recipies['heavy-oil-cracking']
oil_inputs = add_recipe(oil_inputs, hoc, oil_inputs['heavy-oil'] / hoc['heavy-oil'])
loc = recipies['light-oil-cracking']
oil_inputs = add_recipe(oil_inputs, loc, oil_inputs['light-oil'] / loc['light-oil'])
print()
2024-02-17 04:24:00 +00:00
print('oil processing')
2024-02-17 03:50:45 +00:00
print_resource_group(oil_inputs)
2024-02-16 09:20:00 +00:00
2024-03-17 22:23:14 +00:00
oil_inputs, oil_machines = drop_machines(oil_inputs)
2024-02-17 04:24:00 +00:00
if pg := oil_inputs.pop('petroleum-gas', 0) > 0:
raise RuntimeError(f'petrolium-gas > 0: {pg=}')
2024-03-19 03:25:16 +00:00
# ignore heavy/light oil overflow
oil_inputs.pop('light-oil', 0)
oil_inputs.pop('heavy-oil', 0)
2024-02-17 04:24:00 +00:00
smelting_outputs = add_recipe(bus_inputs, oil_inputs, -1)
smelting_outputs = add_recipe(smelting_outputs, chemical_inputs, -1)
2024-02-17 04:24:00 +00:00
print()
print('smelting outputs')
print_resource_group(smelting_outputs)
# smelting
2024-03-19 03:25:16 +00:00
smelting_base = {'furnace', 'iron-ore', 'copper-ore', 'coal', 'stone', 'steam', 'water', 'crude-oil', 'light-oil'}
2024-02-16 09:20:00 +00:00
# combine to compute mining requirements
2024-02-17 04:24:00 +00:00
smelting_inputs, intermediates = reduce_to_base_resources(smelting_outputs, smelting_base)
print('intermediates')
print_resource_group(intermediates)
2024-03-17 21:46:27 +00:00
print('reduced')
2024-02-17 04:24:00 +00:00
print_resource_group(smelting_inputs)
2024-02-16 05:22:29 +00:00
2024-02-16 09:20:00 +00:00
# mining
2024-03-17 22:23:14 +00:00
mining_outputs, smelting_machines = drop_machines(smelting_inputs)
2024-03-17 21:46:27 +00:00
mining_base = {'mining-drill', 'pumpjack', 'offshore-pump', 'steam'}
mining_inputs, intermediates = reduce_to_base_resources(mining_outputs, mining_base)
print()
print('mining intermediates')
print_resource_group(intermediates)
print('reduced')
print_resource_group(mining_inputs)
2024-03-17 22:23:14 +00:00
_, mining_machines = drop_machines(mining_inputs)
def despecify_machines(group: ResourceGroup) -> ResourceGroup:
# assembler[belt-y] -> assembler, etc.
result = {}
2024-03-19 03:25:16 +00:00
for resource, count in get_real_resource_counts(group):
2024-03-17 22:23:14 +00:00
if '[' in resource:
resource = resource[: resource.find('[')]
count = math.ceil(count)
result[resource] = result.get(resource, 0) + count
return result
# TODO: assembers work at 0.75 efficiency...
machines = sum_recipies([bus_machines, chemical_machines, oil_machines, smelting_machines, mining_machines])
machines = despecify_machines(machines)
print()
print('machines (despecified)')
print_resource_group(machines)
# TODO: total machine count
# TODO: belt bandwidth