Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

factorio-flow

License: MIT

A small Python DSL for calculating Factorio production flows. It models machines, recipes, modules, item sources/sinks, quality, symbolic variables, constraints, and linear solves.

The default catalog is Space Age 2.0.55. The project is tested on Python 3.11 and 3.12, and is licensed under the MIT License.

Install From Source

Use Python 3.11 or newer. The solver depends on highspy.

python3 -m venv .venv
. .venv/bin/activate
pip install -e .

Run the example script:

python examples/basic_gear.py
python examples/electronic_circuits.py 10
python examples/quality_recycling.py

Development

Run the test suite with the project virtual environment:

pip install -e ".[dev]"
.venv/bin/python -m unittest discover -v

Basic Usage

Import the default catalog registries:

from factorio_flow import flow, item, machine, module, print_flow, rate, recipe

Create a process by calling a machine with a recipe:

line = machine.assembling_machine_3(recipe.iron_gear_wheel)
print_flow(line)

Processes compose with + and scale with *:

line = (
    machine.assembling_machine_3(recipe.iron_gear_wheel) * 4
    + machine.assembling_machine_3(recipe.engine_unit)
)

Add modules with .modules(...):

line = machine.assembling_machine_3(recipe.iron_gear_wheel).modules(
    module.speed_module_3,
    module.speed_module_3,
)

flow(line) returns a mapping-compatible FlowResult:

result = flow(line)
print(result[item.iron_plate])
print(result.rates)
print(result.variables)

Index by item when reading rates:

result = flow(machine.assembling_machine_3(recipe.iron_gear_wheel))
print(result[item.iron_plate])
print(result[item.iron_gear_wheel])

Atomic Flows

Call an item to create a source or sink process:

from factorio_flow import item

source = item.iron_plate(10)                 # +10/s
sink = item.iron_gear_wheel(-10, seconds=10) # -1/s

line = source + sink

Positive rates add supply. Negative rates consume or remove items from the flow, which is useful for boxes, buses, or external demand.

Constraints

Use rate.<item> expressions in .where(...):

line = (
    machine.assembling_machine_3(recipe.iron_gear_wheel) * 4
).where(
    (rate.iron_gear_wheel >= 1)
    & (rate.iron_plate <= 10)
)

Supported expression operators include:

  • arithmetic: +, -, unary -, *, /
  • comparisons: <, <=, >, >=, ==, !=
  • predicates: &, |, ~

Use parentheses around comparisons because Python operator precedence applies. Equality uses a small floating-point tolerance.

Helpers are also available:

from factorio_flow import all_of, any_of, not_

Raw callables are accepted for concrete validation:

line.where(lambda values: values.get(item.iron_plate, 0.0) <= 10)

Variables And Binding

Create symbolic variables with var(...) or integer variables with int_var(...):

from factorio_flow import int_var, var

machines = int_var("machines")
sink = var("sink")

line = (
    machine.assembling_machine_3(recipe.iron_gear_wheel) * machines
    + item.iron_gear_wheel(-sink)
).where(
    (machines >= 0)
    & (sink >= 0)
)

Bind values with .bind(...):

concrete = flow(line.bind(machines, 4).bind(sink, 10))

Unbound variables remain symbolic in flow(...). Explicit bindings appear in FlowResult.variables.

Solving

Use .solve(min=[...], max=[...]) to solve linear constraints with HiGHS:

machines = var("machines")

line = (
    machine.assembling_machine_3(recipe.iron_gear_wheel) * machines
).where(
    (machines >= 0)
    & (rate.iron_gear_wheel == 10)
).solve(min=[machines])

result = flow(line)
print(result.variables[machines])

Objectives are lexicographic: all min variables are optimized in order, then all max variables in order. Only objective variables are bound back into the result; other variables may remain symbolic.

The solver supports linear constraints from where(...), including constraints over rate.<item> expressions. It rejects nonlinear expressions, raw callable predicates, |, ~, and != during solve.

Solver errors:

from factorio_flow import (
    InfeasibleSolveError,
    UnboundedSolveError,
    UnsupportedSolveError,
)

Quality

Quality is part of Item identity. Unqualified items, recipes, machines, modules, and rates mean normal quality.

line = item.iron_plate.rare(1)
result = flow(line)
print(result[item.iron_plate.rare])

Quality tier attributes:

item.iron_plate.normal
item.iron_plate.uncommon
item.iron_plate.rare
item.iron_plate.epic
item.iron_plate.legendary

The same tier attributes work on recipes, machines, modules, and rates:

line = machine.assembling_machine_3.legendary(
    recipe.iron_gear_wheel.rare
).modules(
    module.quality_module_3.legendary,
)

predicate = rate.iron_gear_wheel.rare >= 1

Recipe quality selects input/output item quality. Fluids stay unqualified and cannot be converted to non-normal quality:

item.water.rare  # raises ValueError

Quality modules split qualityable item outputs by expected value through legendary. Recipe output probabilities are modeled as expected throughput, not random samples.

Machine and module quality follows Factorio quality scaling:

  • machine crafting speed scales by quality
  • positive module effects scale by quality
  • negative module effects do not scale
  • legendary stat scaling is 2.5x

Printing

print_flow(process) prints item rates and any bound or solved variables:

print_flow(line)

Numeric rates use readable time units:

  • / s for rates at least 1/s
  • / min for slower rates at least 1/min
  • / h for very slow rates

Symbolic rates print as expressions per second.

Catalogs

The package exports the default registries:

from factorio_flow import item, rate, recipe, machine, module

Load a different catalog explicitly:

from factorio_flow import DEFAULT_CATALOG_PATH, load_catalog

catalog = load_catalog(DEFAULT_CATALOG_PATH.with_name("vanilla-2.0.55.json"))
line = catalog.machines.assembling_machine_3(
    catalog.recipes.iron_gear_wheel
)

Attribute names are normalized from Factorio prototype ids:

  • iron-plate -> iron_plate
  • assembling-machine-3 -> assembling_machine_3
  • quality-module-3 -> quality_module_3

See docs/data-format.md for the normalized JSON catalog format and importer notes.

About

A small Python DSL for calculating Factorio production flows

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages