Question for train station balancing

Don't know how to use a machine? Looking for efficient setups? Stuck in a mission?
Post Reply
Taipion
Long Handed Inserter
Long Handed Inserter
Posts: 81
Joined: Tue May 02, 2017 6:58 pm
Contact:

Question for train station balancing

Post by Taipion »

So imagine this scenario:
You have a station to unload something like ore, which is actually several train stops with the same name, so you can unload several trains at once.

Now... you ofc have stack inserters and chests that buffer the trains cargo so it can be unloaded fast.

If you don't control where which trains goes and rely on rng, some train stops will be empty while others are full and you'll end up with a train trying to unload into one where the chests are full while the next one is empty.

To deal with that I made a few simple circuits years ago, I just now took a moment to look at it again and wondered if there is something better.
What I do is basically 3 simple steps:
- sum all the items from the initial chests of a train stop and name that signal accordingly ("A", "B", and so on...)
- for each train stop I compare that load with every other train stop, if there is at least one comparison that yields "this one has more items than another" => set the train signal for this train stop to red
- on another signal behind the initial one (which may be set to red through the circuits) I read out if it's red, which it only is if there's a train occupying this space,
and if it's red, I add 100.000.000 or so to the load value of that train stop for the purpose of comparison, basically flagging it as "full"

The goal is to have always only one train stop have a green light, at least if there are still items in the chests, and the one with the green light should be the one with the fewest items


This seems to work pretty well but is not that easily scalable if I wanted to build a station with more train stops,
and I wonder if there is a good design or some ingame feature that could help with it.

User avatar
Khagan
Fast Inserter
Fast Inserter
Posts: 233
Joined: Mon Mar 25, 2019 9:40 pm
Contact:

Re: Question for train station balancing

Post by Khagan »

Taipion wrote:
Thu Aug 17, 2023 7:02 am
If you don't control where which trains goes and rely on rng, some train stops will be empty while others are full and you'll end up with a train trying to unload into one where the chests are full while the next one is empty.
This is what per-stop train limits are for. In the simplest case, just set each stop's limit to 1, and there will never be any queues. If you want more fine-grained control, use local circuits to set the limit to 0/1/2 depending on how much stuff is stashed at that stop. You should never need to compare how much is at different stops (if you think you do, you are not producing enough of whatever it is the stops want).

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

I created a circuit that calculates how many trainloads still fit into the station chests and set an appropriate train limit. This way it's ensured that if a train stops at a station, there's enough space to unload the complete train content without waiting.

The circuit first adds up the contents of all station chests, then subtracts that from the capacity of all chests (that's coming from a constant combinator). The result is the current space. This is divided by the train capacity (for an ore train of 4 wagons: 4*40*50=8000, also coming from the constant combinator). This results in the amount of trains that can be unloaded without the station chests becoming full. Use this value to set the train limit of the station accordingly. Usually, I use limit 0 if result=0, 1 otherwise. If there is a buffer area in front of the station, this varies according to the size of the buffer area.

Taipion
Long Handed Inserter
Long Handed Inserter
Posts: 81
Joined: Tue May 02, 2017 6:58 pm
Contact:

Re: Question for train station balancing

Post by Taipion »

Khagan wrote:
Thu Aug 17, 2023 8:50 am
This is what per-stop train limits are for.
Can you tell me more about those stop limits? What do they do?

User avatar
Khagan
Fast Inserter
Fast Inserter
Posts: 233
Joined: Mon Mar 25, 2019 9:40 pm
Contact:

Re: Question for train station balancing

Post by Khagan »

Taipion wrote:
Thu Aug 17, 2023 10:30 am
Can you tell me more about those stop limits? What do they do?
There's a good description in the original FF that introduced them: viewtopic.php?f=38&t=90207

Taipion
Long Handed Inserter
Long Handed Inserter
Posts: 81
Joined: Tue May 02, 2017 6:58 pm
Contact:

Re: Question for train station balancing

Post by Taipion »

Khagan wrote:
Thu Aug 17, 2023 10:45 am
Taipion wrote:
Thu Aug 17, 2023 10:30 am
Can you tell me more about those stop limits? What do they do?
There's a good description in the original FF that introduced them: viewtopic.php?f=38&t=90207
Yes, I read that, but I can't see how it would help with my train balancing.

With my setup, the next train always goes to the stop with the lowest amount of items in it and that is unoccupied,
I could use the stop limit instead of setting the train lights, but that's not really a difference.

What would be the point to use the stop limit instead?

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

@Taipion It's supposed you have more trains delivering items than the items being consumed from the train stations. In this case, the nearest station fills up until it is full, and if you use some circuit similar to what I mentioned above to set the train limit to 0 in this case, trains will then deliver their items to the next nearest station, until this is also full, then the next, and so on. The next nearest station will also be visited, if the nearest station has a train limit of 1 and a train is currently halting at it.

That's one of the points of train limits: limit the number of trains that target one station. If there are as many trains driving to a station or currently being unloaded as the train limit, the station is "full" and trains will target the next not full station. If the limit is set to 0, trains will never target this station.

Taipion
Long Handed Inserter
Long Handed Inserter
Posts: 81
Joined: Tue May 02, 2017 6:58 pm
Contact:

Re: Question for train station balancing

Post by Taipion »

Yea I get that, I just don't see an advantage over what I already do.

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

Well, your solution don't scale at all, which is your initial issue, while the proper usage of train limits makes it so that you can have as many trains as you like to deliver their items to as many train stations you like, and all at the same time, and no full train is ever waiting in front of an occupied station and blocking space. Trains just ignore stations where their train limits are set to 0 (or trains waiting at it and the limit is reached) while they continue to go to every other station.
You also don't need to link stations with circuits to compare their content - every station has an independent set of circuit items, which makes them blueprintable and infinitely reproducible.

Taipion
Long Handed Inserter
Long Handed Inserter
Posts: 81
Joined: Tue May 02, 2017 6:58 pm
Contact:

Re: Question for train station balancing

Post by Taipion »

yes, scalability, that may be better, it's just not as fine tuned as what I have :D

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

You need to employ more trains, and if this is for ore, more ore mines. As soon as you have more ore available than the smelter stations are able to consume, the stations get full on their own and every smelter has its supply, without any circuit arbitrating between them. This kind of micromanagement isn't really necessary.

It's happening all in parallel: there is not one single station that is open at a time as it seems the case with our setup, so the next train is forced to go there. Instead, all stations are open that are able to receive items. So if you have 5 stations and 5 trains, and 2 of the stations are "closed" (train limit=0) and 3 are "open" (train limit=1), and 3 of the trains happen to be filled by the miners just now, all 3 will drive to the currently open stations. No full train will just stand inactive while there is a non full station.

More important, in my opinion, is equal load and unload of the station chests. If you have enough supply so your station never gets empty, some chests tend to get more full than others, up to the extend that some wagons are delivering their items to almost full chests while other wagons are delivering their items to almost empty chests. This increases the time the train is waiting at the station to get empty. If not taken care of, this will result in the same issue you're currently facing, but only at a different level. The use of balancers at unloading station can help mitigating this issue, but will not get completely rid of it.

Premu
Fast Inserter
Fast Inserter
Posts: 100
Joined: Wed Nov 13, 2019 4:40 pm
Contact:

Re: Question for train station balancing

Post by Premu »

Hm, how does your station layout looks like?

In my experience, since the introduction of train limits you don't need any circuitry at all to distribute incoming trains to your unloading stations. As long as you use enough trains.

For example - I set the train limit for all unloading stations in my steel mill to two. So for each station there can be one getting unloaded and another one either on the way or waiting in a nearby stacker. Whenever I add an unloading station, I also add as many trains for this line as the train limit for that station. My steel mill has 8 unloading stations, so I added 16 new trains to transport iron plates. I also add one train to the line for each loading station on top. Loading stations might have a higher train limit, depending on how much is in their chests, but this extra limit is necessary that emptied trains can leave their unloading stations without causing a deadlock.

So everytime a train is empty, the empty train leaves to get new stuff, another train just waiting nearby takes its place to get unloaded, and one full train starts to move towards the stacker.

That method works very well if I produce more than I consume. And even if the production is not high enough it doesn't lead to permanent deadlocks. Empty trains might wait at the unloading station because no loading station has enough stuff to load it, but after some time the production should catch up and the train will be able to leave. In that case the solution is to get a higher production of input materials, of course.

aka13
Filter Inserter
Filter Inserter
Posts: 686
Joined: Sun Sep 29, 2013 1:18 pm
Contact:

Re: Question for train station balancing

Post by aka13 »

I never found a satisfying way to do it in vanilla. All circuit network solutions feel hacky at best.
Nowadays, with train station limits, I do the following:

1. Overproduce whatever it is you deliver with trains
2. Have 1-x train limit on stations
3. Have a train fleet for that cargo large enough to cover all your limits
4. Profit

Most consumers of cheap items get a limit of 2 trains. 1 Train unloading, 1 train on standby, to not wait for a delivery.
Large consumers - 2+ trains
Expensive items get exactly 1 train.

Pre station limits, the circuit network challenge was to work with stackers before stations, and turning them on and off correspondingly.

In my dream world, there has to be some advanced train controller working with demand and supply, a la LTN which I never played or Cybersyn.
Pony/Furfag avatar? Opinion discarded.

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

Premu wrote:
Thu Aug 17, 2023 4:45 pm
So everytime a train is empty, the empty train leaves to get new stuff, another train just waiting nearby takes its place to get unloaded, and one full train starts to move towards the stacker.
This works well if you don't have too many stations or a low station density. If you have a train limit of 2 on all your unloading stations, and trains can arrive on a station with full chests and wait for being unloaded, 2 full trains per station might wait until infinity - if this station's chests are currently not being emptied.

But you need to arrange for these two additional trains in your whole infrastructure and make sure there is no deadlock and congestion if all trains are currently running, for example in the initial phase or after some sort of interruption. It can also happen, that a train that just got emptied and is about to leave the station isn't actually leaving, because every next station on the schedule has its limit reached. And if you don't set a limit, trains might stack up on the main track if you don't have a large enough stacker in front of it to hold all possible trains. You can use stackers for empty trains and stackers for full trains (I do use this to buffer trains), but the more trains you have in general, the more these stackers need to grow - up to ridiculous sizes sometimes.

So I adjust the train limit of unloading stations dynamically from 2 or 1 down to 0, so in case a train is stopping, there is enough space in the station chests to get the train unloaded without any inserter stalling due to a full chest. So this train is back on the tracks in the fastest possible time, and in a bigger environment, every train less is less chance for congestion and confusion.

Addtionally, I have a stacker in front of a bunch of consumer stations, for full trains, with its own station, so it is actually a depot, not just a stacker. The same in front of the production area, for empy trains. So my schedule has 4 stations: depot for empty trains (1 station with big stacker)→pickup in production area (multiple stations with 0..2 stackers each→depot for full trains (1 station with big stacker)→unloading in consumption area (multiple stations with 0..2 stackers each).
I found this setup is able to give the best throughput. The benefit of having depot stations is that the production and consumption areas are uncoupled, and a train leaving one area doesn't need a free target in the other area to be able to leave- just space in the depot, so it can immediately leave and free the station it's supposed to leave.

Premu
Fast Inserter
Fast Inserter
Posts: 100
Joined: Wed Nov 13, 2019 4:40 pm
Contact:

Re: Question for train station balancing

Post by Premu »

Obviously the train network must be able to handle these amount of trains. But if you have fast enough trains by using nuclear fuel a rail network has a very large capacity, and trains are able to cross junctions quickly, which are the places where you might cause congestions. In earlier game phases I might just have solid fuel or even coal, but at these stages the amount of trains is too small to cause any problems.

The loading stations receive train limits as well, obviously, and these are dynamically set based on the amount of goods in their crates. The loading stations have also enough waiting spots for the maximum amount of trains which could target these. That's the reason that in case of unsufficient production my trains will wait empty at the unloading stations until a new delivery is available, so they won't block my actual main travel rails.

Also, if you have a limited amount of loading stations these will also effectively limit your train traffic for the goods they are providing. If I'd increase the train limit for some of my unloading stations to something like 4 and set up more trains, it will just lead to very large stackers full of loaded trains. But I would not be able to get all those trains loaded at the same time, so they will not travel in the same region at the same time. The amount of train traffic is not defined by the number of trains (unless you let them run around in circles empty), but by your production and loading capacity for those trains.

What's the advantage of having a seperate station in the stackers? The trains would have travelled there anyway and should not cause any blockade as you should know how many trains can wait at a station at most (if you use train limits), so you can design the station to have at least that many waiting places.

Tertius
Filter Inserter
Filter Inserter
Posts: 671
Joined: Fri Mar 19, 2021 5:58 pm
Contact:

Re: Question for train station balancing

Post by Tertius »

Premu wrote:
Thu Aug 17, 2023 10:50 pm
What's the advantage of having a seperate station in the stackers?
Reducing the buffer space in the stacker, if the stacker supplies multiple stations. Without separate station, trains directly target the unloading station. If you have a somewhat long distance between mines and smelters, you need a somewhat high train limit at the ore unloading station, otherwise you cannot achieve enough throughput. If one stacker supplies multiple stations, the amount of lanes must be the sum of all unloading station train limits minus 1. Minus 1 because there is one space at the unloading station itself, but beyond that any arriving train has to have a free lane at the stacker, otherwise it blocks the main track.
With a separate station for the stacker, the stacker can be more compact and have less lanes - only as many as required to continuously feed the unloading stations behind it.

I acknowledge this is a situation not always encountered for smaller setups, however I encountered it for my larger high throughput smelter arrays that pull ore with 4 blue belts from one station.

aka13
Filter Inserter
Filter Inserter
Posts: 686
Joined: Sun Sep 29, 2013 1:18 pm
Contact:

Re: Question for train station balancing

Post by aka13 »

Stackers are for buffering close to requesters. Sufficiently late in the game a drive from the mines can easily be 2+ min even without any congestions.
Pony/Furfag avatar? Opinion discarded.

sapzxc
Manual Inserter
Manual Inserter
Posts: 1
Joined: Tue Dec 26, 2023 9:11 pm
Contact:

Re: Question for train station balancing

Post by sapzxc »

I created and now testing scalable and simple for construction solution, but you need Moon Logic mod to run script.
Example of usage it is when you setup huge factory and produce 45 productivity packs/sec as example. So you need thousands of furnaces. And you need balance unloading by stops to make full productivity of your production but you can not build more furnaces. So you need to fulfill each station evenly. This problem can be resolved via this combinator and code.

Benefits:
  • Automatically detect stations, chests count and its contents. Detect inventory size.
  • Automatically set up train limit dynamically, control trains going to each station.
  • Automatically setup "enable/disable" rules for each station and send disable signal when it need.
Setup yourself:
  • Edit const_items_per_train = 8000
  • Edit const_trains_per_stop_max = 2
  • Read code to see other options: delay, dbg
"delay" can be used to tune overall reaction speed and UPS. I prefer to check everything every 5 sec (60 ticks*5).
Debug information useful to see correct setup and debug fulfilling, it show percentage and other standard station information in one place. Note "trains" in debug screen means how many fulfilled trains contents in chests. Chests it is total detected chests. Also you can uncomment lines and see total slots and max total items can be stored in chests. You can uncomment gps tag and see location in game chat when dbg=true.

Here is example of wires map:
screen11.png
screen11.png (1.05 MiB) Viewed 1064 times
You need connect stations with one wire color and chests with another wire color to prevent mix values. Be sure you does not connect chests from different stations using same wire color. Code get stations and all chests connected to each station then make calculations and send control signal to tran stops, so you need connect one more wire from Moon Logic Controller output to stations.

Example of debug information in game chat (enable by flag in code):
screen2.png
screen2.png (562.91 KiB) Viewed 1064 times
Source code to put in to Moon Logic Combinator:

Code: Select all

-- do return end -- uncomment to turn off logic

local const_items_per_train = 8000
local const_trains_per_stop_max = 2

delay=60*5 -- delay ticks between logic checks, tune for performance
local dbg=false -- game chat info


function dbgp(msg)
  if dbg == false then return end
  game.print(msg)
end 

dbgp("===")
local mlc_entity = _api.global.combinators[uid].e
local known_e={}
local known_rail_stations={}

function find_neigbors(this_entity)
  local neigbors = {}
  for i1,wire_color in pairs({'red','green'}) do
    for i2,e in pairs(this_entity.circuit_connected_entities[wire_color]) do
      -- find recursive all items
      if known_e[e.unit_number] == nil then
        
        table.insert(neigbors,e)
        known_e[e.unit_number] = 1

        if e.name == 'train-stop' then
          known_rail_stations[e.unit_number] = {
            rail_station=e,
            all_connected={}
          }
          for i3,e_train_stop_connected in pairs(find_neigbors(e)) do 
            table.insert(neigbors,e_train_stop_friendly)
            table.insert(known_rail_stations[e.unit_number].all_connected,e_train_stop_connected)
          end
        else
          for i3,e_any_other in pairs(find_neigbors(e)) do 
            table.insert(neigbors,e_any_other)
          end
        end
      end
    end
  end

  return neigbors
end

find_neigbors(mlc_entity)

local station_index=0
for unit_number,rail_station_info in pairs(known_rail_stations) do
  station_index = station_index + 1
  -- dbgp(">station="..rail_station_info.rail_station.backer_name)

  if station_index > 9 then 
    error("Too many stations. Max supported stations per one controller = 9")
  end

  local all_items_connected = rail_station_info.rail_station.get_merged_signals( _api.defines.circuit_connector_id.combinator_input )
  -- =  {xx = {{count = 76768, signal = {name = "steel-plate", type = "item"}}}}
  if all_items_connected == nil then
    all_items_connected = {}
  end

  local total_chests = 0;
  local total_slots = 0;
  local total_items = {};
  local total_items_all = 0;
  for i1,find_in_containers_data in pairs(all_items_connected) do
    -- example=>find_in_containers_data={count = 76768, signal = {name = "steel-plate", type = "item"}
    if find_in_containers_data.signal.type == 'item' then
      for i2,e in pairs(rail_station_info.all_connected) do
        if (
          e.type == 'container' or
          e.type == 'logistic-container'
        ) then
          total_chests = total_chests + 1
          total_slots = total_slots + #e.get_inventory(_api.defines.inventory.chest)
          if e.has_items_inside() then
            local contents_count = e.get_item_count(find_in_containers_data.signal.name)
            if contents_count ~= nil and contents_count > 0 then
              -- dbgp('chest>>'.. find_in_containers_data.signal.name ..' = '.. contents_count .. e.gps_tag)
              local items_name = find_in_containers_data.signal.name
              if total_items[items_name] == nil then
                total_items[items_name] = 0
              end

              total_items[items_name] = total_items[items_name] + contents_count
              total_items_all = total_items_all + contents_count
            end
          end
        end
      end
    end
  end

  -- if only one type of itms found, then calculate max items per station
  local max_items = 0;
  local total_items_table_length = 0
  local first_item_name='-'
  for items_name,_ in pairs(total_items) do 
    total_items_table_length = total_items_table_length + 1 
    first_item_name=items_name
  end

  if total_items_table_length == 1 then
    max_items = _api.game.item_prototypes[first_item_name].stack_size * total_slots
  end

  rail_station_info.total_chests = total_chests
  rail_station_info.total_slots = total_slots
  rail_station_info.max_total_items = max_items
  rail_station_info.total_items = total_items
  rail_station_info.total_items_all = total_items_all
  rail_station_info.full_trains_count = total_items_all / const_items_per_train

  rail_station_info.index = station_index
  rail_station_info.rail_station.trains_limit = 1
  local behavior = rail_station_info.rail_station.get_or_create_control_behavior()
  behavior.enable_disable = true
  behavior.circuit_condition = {condition={comparator="=",
                                            first_signal={type="virtual", name="signal-"..station_index},
                                            constant=0}}
end

local min_full_trains = 1000000;
local max_full_trains = 0;
for unit_number,rail_station_info in pairs(known_rail_stations) do
  local tc = rail_station_info.full_trains_count
  if tc > max_full_trains then 
    max_full_trains = tc
  end
  if tc < min_full_trains then 
    min_full_trains = tc
  end
end

local min_full_trains_floor = math.floor(min_full_trains);
local max_full_trains_floor = math.floor(max_full_trains);

for unit_number,rail_station_info in pairs(known_rail_stations) do
  local station=rail_station_info.rail_station
  local do_disable = false
  local items_tc_here_floor = math.floor(rail_station_info.full_trains_count)  
  
  -- if looks like each station has at least 1 full trains, so now just sutisfy by max trains per station
  if min_full_trains_floor >= 1 and max_full_trains_floor - min_full_trains_floor < 2 then
    station.trains_limit = const_trains_per_stop_max

  -- if not reached "rich" state so disable this to make another to be fulfiled
  elseif items_tc_here_floor > 0 and items_tc_here_floor == max_full_trains_floor then 
    station.trains_limit = 0
    do_disable = true

  -- if here less then 1 full train
  elseif items_tc_here_floor == 0 then
    -- and if no one going here
    if station.trains_count < 1 then
      station.trains_limit = 2
    -- or if going >1 then check if some another stations is completely empty, so set limit=1 to this
    elseif min_full_trains == 0 then
      station.trains_limit = 1
    end
  end

  out['signal-'.. rail_station_info.index] = (do_disable == true) and 1 or 0
  rail_station_info.do_disable=do_disable
end


for unit_number,rail_station_info in pairs(known_rail_stations) do
  local station=rail_station_info.rail_station
  dbgp('station >> [train-stop='..station.unit_number..'] '.. 
          -- station.backer_name .. station.gps_tag ..
          ' (L='..station.trains_count..'/'..(station.trains_limit)..') '..
          ' (D='..(rail_station_info.do_disable and 'yes' or 'no')..')'..
          ' (total items='..rail_station_info.total_items_all..'),'..
          ' trains='..(rail_station_info.full_trains_count)..', '..
          ' chests='..rail_station_info.total_chests ..', '..
          -- ' slots='..rail_station_info.total_slots ..', '..
          '  '..math.floor(rail_station_info.total_items_all * 100 / rail_station_info.max_total_items)..'%' .. 
          ''
          )

  for items_name,items_count in pairs(rail_station_info.total_items) do
    dbgp('        '..items_count..' [item='.. items_name.. '] '..items_name)
  end
end


dbgp("====end====")

Last edited by sapzxc on Wed Dec 27, 2023 9:13 pm, edited 1 time in total.

Trific
Fast Inserter
Fast Inserter
Posts: 147
Joined: Thu Dec 31, 2020 7:57 pm
Contact:

Re: Question for train station balancing

Post by Trific »

I built a setup like this for my first megabase. It doesn't balance across the entire base, just each "installation". For instance, an iron smelter installation has 8 input train racks, and 4 output train racks, and the trains are balanced across the 8 input racks and the 4 output racks. I used much the same scheme as the OP, right down to using letter signals to communicate between the racks. It works well, but I commented in the suggestions forum advocating for a train stop priority system, which would let stops signal through priorities how empty or full they were. Alas, the devs don't seem to be keen on stop priorities.

I am currently playing through Seablock, which is based on Bob's & Angel's mods, so I designed a scalable stop priority system to take care of byproducts and the ability to select preferred sources and destinations. Pickup stops communcate on red wires, deliver stops communicate on green wires, and I have a clocked "command signal" that dictates when each priority level is allowed to transmit its info. It will also do rough balancing between stops of the same priority. It allows 10 levels of priority, which is fine for this usage, but is not as versatile as a native train stop priority system would be. The base wide combinator circuit system to manage each good being transported requires 110 combinators each for pickup and deliver. Contrary to some of the objections around stop priorities, it does not trivialize handling byproducts. It makes them much easier, but by no means trivial.

Post Reply

Return to “Gameplay Help”