Skip to content

Instantly share code, notes, and snippets.

@apocalyptech
Created November 4, 2024 03:26
Show Gist options
  • Save apocalyptech/cc99a7467e4a10f7543d41c4a924a729 to your computer and use it in GitHub Desktop.
Save apocalyptech/cc99a7467e4a10f7543d41c4a924a729 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# vim: set expandtab tabstop=4 shiftwidth=4:
import os
import sys
import math
import argparse
# I spent a bit of time banging my head against these without really
# getting to the nub of the final Recycled loop. This post helped:
#
# https://www.reddit.com/r/SatisfactoryGame/comments/x94t7i/could_use_help_with_the_math_behind_the_oil_loop/
#
# ... but in the end it was Haxton from the Discord who pointed out
# what I missed.
#
# Specifically:
#
# Fp = (P * 17/27) + (R * 8/27) - (F * 1/27)
# Fr = (P * 7/27) + (R * 16/27) - (F * 2/27)
#
# F = Desired fuel output
# R = Desired Rubber Output
# P = Desired Plastic Output
# Fr = Fuel sent to the Recycled Rubber refineries
# Fp = Fuel sent to the Recycled Plastic refineries
# R0 = "seed" rubber created by the Residual Rubber refineries
# C = Amount of crude oil
#
# First up, some ratios and "basic" info that we know:
#
# HOR: 3 Crude Oil -> 4 HOR + 2 Resin (3:4:2)
# Residual: 4 Resin + 4 Water -> 2 Rubber (2:2:1)
# Diluted: 5 HOR + 10 Water -> 10 Fuel (1:2:2)
# Recycled Plastic: 6 Rubber + 6 Fuel -> 12 Plastic (1:1:2)
# Recycled Rubber: 6 Plastic + 6 Fuel -> 12 Rubber (1:1:2)
#
# C = (F + R + P)/3
# Resin output: (2/3)C
# HOR output: (4/3)C
# R0: 1/2 * (2/3)C -> (1/3)C
# Total fuel: 2 * (4/3)C -> (8/3)C
#
# Now the basic equations we'll use to solve:
#
# R = 2Fr - Fp + R0
# P = 2Fp - Fr
# R0 = (1/3)C
# F + Fr + Fp = (8/3)C
#
# First we'll solve for Fr; we start by getting an equation
# giving us R in terms of Fr, Fp, and F:
#
# C = (3/8)(F + Fr + Fp)
# R0 = (1/8)(F + Fr + Fp)
# R = 2Fr - Fp + (1/8)(F + Fr + Fp)
# R = ((16+1)/8)Fr + ((-8+1)/8)Fp + (1/8)F
# R = (17/8)Fr - (7/8)Fp + (1/8)F
#
# Now get Fp in terms of P and Fr, and sub it in to that one,
# which will eventually let us write Fr in terms of R, P, and F.
#
# 2Fp = P + Fr
# Fp = (1/2)P + (1/2)Fr
# R = (17/8)Fr - (7/8)((1/2)P + (1/2)Fr) + (1/8)F
# R = ((34-7)/16)Fr - (7/16)P + (1/8)F
# R = (27/16)Fr - (7/16)P + (2/16)F
# 16R = 27Fr - 7P + 2F
# 27Fr = 16R + 7P - 2F
# Fr = (16/27)R + (7/27)P - (2/27)F
#
# Technically that's all we need! There'd be a couple of ways
# to get Fp. One is that we know the total Fuel count, so just
# subtract Fr from that. We *could* get an equation for Fp
# basically the exact same way we did Fr; take our initial P=foo
# equation and sub in Fr:
#
# P = 2Fp - (16/27)R - (7/27)P + (2/27)F
# 2Fp = ((27+7)/27)P + (16/27)R - (2/27)F
# 2FP = (34/27)P + (16/27)R - (2/27)F
# Fp = (17/27)P + (8/27)R - (1/27)F
#
# Alternatively, could sub Fr into the `F + Fr + Fp = (8/3)C`
# equation. Not sure which is simpler:
#
# Fp = (8/3)C - Fr -F
# Fp = (8/3)((F+R+P)/3) - Fr - F
# Fp = (8/9)F + (8/9)R + (8/9)P - Fr - F
# Fp = (24/27)F + (24/27)R + (24/27)P - (16/27)R - (7/27)R + (2/27)R - (27/27)F
# Fp = ((24-16)/27)R + ((24-7)/27)P + ((24+2-27)/27)F
# Fp = (8/27)R + (17/27)P - (1/27)F
#
# (oops, put things in a slightly different order for that one.)
#
# Anyway, nice to have equations for it!
def num(value):
rounded = round(value, 4)
if round(value, 4) == int(value):
return int(value)
else:
return rounded
def normalize(label, value):
new_value = math.ceil(value/10.125) * 10.125
if round(value, 4) != round(new_value, 4):
print(f'NOTICE: Normalized {label} to: {num(new_value)}')
return new_value
parser = argparse.ArgumentParser(
description='Satisfactory Petrochemical Solver',
)
parser.add_argument('-p', '--plastic',
type=float,
default=0,
help='Amount of Plastic to consume',
)
parser.add_argument('-r', '--rubber',
type=float,
default=0,
help='Amount of Rubber to consume',
)
parser.add_argument('-f', '--fuel',
type=float,
default=0,
help='Amount of Fuel to consume',
)
parser.add_argument('-n', '--normalize',
action='store_true',
help='Normalize to "clean" numbers',
)
args = parser.parse_args()
if all([
args.plastic == 0,
args.rubber == 0,
args.fuel == 0,
]):
parser.error('At least one resource must be specified')
if args.plastic == 0 and args.rubber == 0 and args.fuel > 0:
print('ERROR: This utility does not currently support *only* fuel output.')
print('The math works out differently. Just send Crude Oil to HOR and then')
print("over to Diluted (packaged or otherwise), can't *quite* get 3x with")
print('that.')
print('')
sys.exit(1)
if args.normalize:
args.plastic = normalize('plastic', args.plastic)
args.rubber = normalize('rubber', args.rubber)
args.fuel = normalize('fuel', args.fuel)
total_resources = sum([
args.plastic,
args.rubber,
args.fuel,
])
crude_oil = total_resources/3
###
### HOR Refineries
###
# This should really be abstracted, but whatever
hor_in_crude = 30
hor_out_hor = 40
hor_out_resin = 20
num_hor = crude_oil/hor_in_crude
oil_input = num_hor*hor_in_crude
hor_output = num_hor*hor_out_hor
resin_output = num_hor*hor_out_resin
###
### Diluted Fuel
###
# Using the blender version for the math, but we'll normalize later
# if needed. The ratios are the same.
diluted_in_hor = 50
diluted_in_water = 100
diluted_out_fuel = 100
num_diluted = hor_output/diluted_in_hor
water_input_diluted = num_diluted*diluted_in_water
fuel_output = num_diluted*diluted_out_fuel
fuel_output_loop = fuel_output - args.fuel
fuel_output_rubber = (args.plastic * (7/27)) + (args.rubber * (16/27)) - (args.fuel * (2/27))
fuel_output_plastic = (args.plastic * (17/27)) + (args.rubber * (8/27)) - (args.fuel * (1/27))
###
### Residual Rubber
###
res_in_resin = 40
res_in_water = 40
res_out_rubber = 20
num_res = resin_output/res_in_resin
water_input_res = num_res*res_in_water
rubber_output_res = num_res*res_out_rubber
water_input_total = water_input_diluted + water_input_res
###
### Recycled Rubber
###
recycled_in_mat = 30
recycled_in_fuel = 30
recycled_out_mat = 60
num_recycled_rubber = fuel_output_rubber/recycled_in_fuel
recycled_rubber_output = num_recycled_rubber*recycled_out_mat
recycled_rubber_loop = recycled_rubber_output - args.rubber
###
### Recycled Plastic
###
num_recycled_plastic = fuel_output_plastic/recycled_in_fuel
recycled_plastic_output = num_recycled_plastic*recycled_out_mat
recycled_plastic_loop = recycled_plastic_output - args.plastic
###
### Report!
###
print(f'Crude Oil Input: {num(oil_input)}')
print(f'Water Input: {num(water_input_total)}')
print(f' -> {num(water_input_diluted)} to Diluted Fuel')
print(f' -> {num(water_input_res)} to Residual Rubber')
print('')
print(f'Number of HOR refineries: {num(num_hor)}')
print(f' -> HOR to Diluted Fuel: {num(hor_output)}')
print(f' -> Resin to Residual Rubber: {num(resin_output)}')
print('')
print(f'Number of Residual Rubber refineries: {num(num_res)}')
print(f' -> Rubber to Recycled Plastic: {num(rubber_output_res)}')
print('')
print(f'Number of Diluted Fuel blenders: {num(num_diluted)}')
num_packaged = num_diluted*(100/60)
print(f'(Or, number of Diluted Packaged Fuel loops: {num(num_packaged)})')
if args.fuel > 0:
print(f' -> FUEL OUTPUT: {num(args.fuel)}')
print(f' -> Total fuel to Recycling loop: {num(fuel_output_loop)}')
print(f' -> Fuel to Recycled Rubber: {num(fuel_output_rubber)}')
print(f' -> Fuel to Recycled Plastic: {num(fuel_output_plastic)}')
print('')
print(f'Number of Recycled Rubber refineries: {num(num_recycled_rubber)}')
print(f' -> Total rubber: {num(recycled_rubber_output)}')
print(f' -> Rubber looped to Recycled Plastic: {num(recycled_rubber_loop)}')
if args.rubber > 0:
print(f' -> RUBBER OUTPUT: {num(args.rubber)}')
print('')
print(f'Number of Recycled Plastic refineries: {num(num_recycled_plastic)}')
print(f' -> Total plastic: {num(recycled_plastic_output)}')
print(f' -> Plastic looped to Recycled Rubber: {num(recycled_plastic_loop)}')
if args.plastic > 0:
print(f' -> PLASTIC OUTPUT: {num(args.plastic)}')
print('')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment