Linkgraph: Add asymmetric: equal and nearest demand distribution modes
This commit is contained in:
@@ -1816,6 +1816,8 @@ STR_CONFIG_SETTING_LINKGRAPH_NOT_DAYLENGTH_SCALED :Do not scale th
|
||||
STR_CONFIG_SETTING_LINKGRAPH_NOT_DAYLENGTH_SCALED_HELPTEXT :When enabled, the linkgraph recalculation interval and time are in units of unscaled, original days, instead of day-length scaled calendar days.
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_MANUAL :manual
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC :asymmetric
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_EQ :asymmetric (equal distribution)
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_NEAREST :asymmetric (nearest)
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_SYMMETRIC :symmetric
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_PAX :Distribution mode for passengers: {STRING2}
|
||||
STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT :"symmetric" means that roughly the same number of passengers will go from a station A to a station B as from B to A. "asymmetric" means that arbitrary numbers of passengers can go in either direction. "manual" means that no automatic distribution will take place for passengers.
|
||||
|
@@ -3,6 +3,8 @@
|
||||
#include "../stdafx.h"
|
||||
#include "demands.h"
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <tuple>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
@@ -119,6 +121,58 @@ public:
|
||||
inline bool HasDemandLeft(const Node &to) { return to.Demand() > 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* A scaler for asymmetric distribution (equal supply).
|
||||
*/
|
||||
class AsymmetricScalerEq : public Scaler {
|
||||
public:
|
||||
/**
|
||||
* Count a node's supply into the sum of supplies.
|
||||
* @param node Node.
|
||||
*/
|
||||
inline void AddNode(const Node &node)
|
||||
{
|
||||
this->supply_sum += node.Supply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the mean demand per node using the sum of supplies.
|
||||
* @param num_demands Number of accepting nodes.
|
||||
*/
|
||||
inline void SetDemandPerNode(uint num_demands)
|
||||
{
|
||||
this->demand_per_node = CeilDiv(this->supply_sum, num_demands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective supply of one node towards another one. In symmetric
|
||||
* distribution the supply of the other node is weighed in.
|
||||
* @param from The supplying node.
|
||||
* @param to The receiving node.
|
||||
* @return Effective supply.
|
||||
*/
|
||||
inline uint EffectiveSupply(const Node &from, const Node &to)
|
||||
{
|
||||
return max<int>(min<int>(from.Supply(), ((int) this->demand_per_node) - ((int) to.ReceivedDemand())), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any acceptance left for this node. In asymmetric (equal) distribution
|
||||
* nodes accept as long as their demand > 0 and received_demand < demand_per_node.
|
||||
* @param to The node to be checked.
|
||||
*/
|
||||
inline bool HasDemandLeft(const Node &to)
|
||||
{
|
||||
return to.Demand() > 0 && to.ReceivedDemand() < this->demand_per_node;
|
||||
}
|
||||
|
||||
void SetDemands(LinkGraphJob &job, NodeID from, NodeID to, uint demand_forw);
|
||||
|
||||
private:
|
||||
uint supply_sum; ///< Sum of all supplies in the component.
|
||||
uint demand_per_node; ///< Mean demand associated with each node.
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the demands between two nodes using the given base demand. In symmetric mode
|
||||
* this sets demands in both directions.
|
||||
@@ -142,6 +196,19 @@ void SymmetricScaler::SetDemands(LinkGraphJob &job, NodeID from_id, NodeID to_id
|
||||
this->Scaler::SetDemands(job, from_id, to_id, demand_forw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the demands between two nodes using the given base demand.
|
||||
* @param job The link graph job.
|
||||
* @param from_id The supplying node.
|
||||
* @param to_id The receiving node.
|
||||
* @param demand_forw Demand calculated for the "forward" direction.
|
||||
*/
|
||||
void AsymmetricScalerEq::SetDemands(LinkGraphJob &job, NodeID from_id, NodeID to_id, uint demand_forw)
|
||||
{
|
||||
this->Scaler::SetDemands(job, from_id, to_id, demand_forw);
|
||||
job[to_id].ReceiveDemand(demand_forw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the demands between two nodes using the given base demand. In asymmetric mode
|
||||
* this only sets demand in the "forward" direction.
|
||||
@@ -186,6 +253,7 @@ void DemandCalculator::CalcDemand(LinkGraphJob &job, Tscaler scaler)
|
||||
* symmetric this is relative to remote supply, otherwise it is
|
||||
* relative to remote demand. */
|
||||
scaler.SetDemandPerNode(num_demands);
|
||||
|
||||
uint chance = 0;
|
||||
|
||||
while (!supplies.empty() && !demands.empty()) {
|
||||
@@ -251,6 +319,56 @@ void DemandCalculator::CalcDemand(LinkGraphJob &job, Tscaler scaler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual demand calculation, called from constructor.
|
||||
* @param job Job to calculate the demands for.
|
||||
* @tparam Tscaler Scaler to be used for scaling demands.
|
||||
*/
|
||||
template<class Tscaler>
|
||||
void DemandCalculator::CalcMinimisedDistanceDemand(LinkGraphJob &job, Tscaler scaler)
|
||||
{
|
||||
std::vector<NodeID> supplies;
|
||||
std::vector<NodeID> demands;
|
||||
|
||||
for (NodeID node = 0; node < job.Size(); node++) {
|
||||
scaler.AddNode(job[node]);
|
||||
if (job[node].Supply() > 0) {
|
||||
supplies.push_back(node);
|
||||
}
|
||||
if (job[node].Demand() > 0) {
|
||||
demands.push_back(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (supplies.empty() || demands.empty()) return;
|
||||
|
||||
scaler.SetDemandPerNode(demands.size());
|
||||
|
||||
struct EdgeCandidate {
|
||||
NodeID from_id;
|
||||
NodeID to_id;
|
||||
uint distance;
|
||||
};
|
||||
std::vector<EdgeCandidate> candidates;
|
||||
candidates.reserve(supplies.size() * demands.size() - min(supplies.size(), demands.size()));
|
||||
for (NodeID from_id : supplies) {
|
||||
for (NodeID to_id : demands) {
|
||||
if (from_id != to_id) {
|
||||
candidates.push_back({ from_id, to_id, DistanceMaxPlusManhattan(job[from_id].XY(), job[to_id].XY()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(candidates.begin(), candidates.end(), [](const EdgeCandidate &a, const EdgeCandidate &b) {
|
||||
return std::tie(a.distance, a.from_id, a.to_id) < std::tie(b.distance, b.from_id, b.to_id);
|
||||
});
|
||||
for (const EdgeCandidate &candidate : candidates) {
|
||||
if (job[candidate.from_id].UndeliveredSupply() == 0) continue;
|
||||
if (!scaler.HasDemandLeft(job[candidate.to_id])) continue;
|
||||
|
||||
scaler.SetDemands(job, candidate.from_id, candidate.to_id, min(job[candidate.from_id].UndeliveredSupply(), scaler.EffectiveSupply(job[candidate.from_id], job[candidate.to_id])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DemandCalculator and immediately do the calculation.
|
||||
* @param job Job to calculate the demands for.
|
||||
@@ -276,6 +394,12 @@ DemandCalculator::DemandCalculator(LinkGraphJob &job) :
|
||||
case DT_ASYMMETRIC:
|
||||
this->CalcDemand<AsymmetricScaler>(job, AsymmetricScaler());
|
||||
break;
|
||||
case DT_ASYMMETRIC_EQ:
|
||||
this->CalcMinimisedDistanceDemand<AsymmetricScalerEq>(job, AsymmetricScalerEq());
|
||||
break;
|
||||
case DT_ASYMMETRIC_NEAR:
|
||||
this->CalcMinimisedDistanceDemand<AsymmetricScaler>(job, AsymmetricScaler());
|
||||
break;
|
||||
default:
|
||||
/* Nothing to do. */
|
||||
break;
|
||||
|
@@ -20,6 +20,9 @@ private:
|
||||
|
||||
template<class Tscaler>
|
||||
void CalcDemand(LinkGraphJob &job, Tscaler scaler);
|
||||
|
||||
template<class Tscaler>
|
||||
void CalcMinimisedDistanceDemand(LinkGraphJob &job, Tscaler scaler);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -21,23 +21,14 @@ static const LinkGraphID INVALID_LINK_GRAPH_JOB = UINT16_MAX;
|
||||
typedef uint16 NodeID;
|
||||
static const NodeID INVALID_NODE = UINT16_MAX;
|
||||
|
||||
enum DistributionType {
|
||||
DT_BEGIN = 0,
|
||||
DT_MIN = 0,
|
||||
enum DistributionType : byte {
|
||||
DT_MANUAL = 0, ///< Manual distribution. No link graph calculations are run.
|
||||
DT_ASYMMETRIC = 1, ///< Asymmetric distribution. Usually cargo will only travel in one direction.
|
||||
DT_MAX_NONSYMMETRIC = 1, ///< Maximum non-symmetric distribution.
|
||||
DT_SYMMETRIC = 2, ///< Symmetric distribution. The same amount of cargo travels in each direction between each pair of nodes.
|
||||
DT_MAX = 2,
|
||||
DT_NUM = 3,
|
||||
DT_END = 3
|
||||
};
|
||||
|
||||
/* It needs to be 8bits, because we save and load it as such
|
||||
* Define basic enum properties
|
||||
*/
|
||||
template <> struct EnumPropsT<DistributionType> : MakeEnumPropsT<DistributionType, byte, DT_BEGIN, DT_END, DT_NUM> {};
|
||||
typedef TinyEnumT<DistributionType> DistributionTypeByte; // typedefing-enumification of DistributionType
|
||||
DT_ASYMMETRIC_EQ = 20, ///< Asymmetric distribution (equal). Usually cargo will only travel in one direction. Attempt to distribute the same amount of cargo to each sink.
|
||||
DT_ASYMMETRIC_NEAR = 21, ///< Asymmetric distribution (nearest). Usually cargo will only travel in one direction. Attempt to distribute cargo to the nearest sink.
|
||||
};
|
||||
|
||||
/**
|
||||
* Special modes for updating links. 'Restricted' means that vehicles with
|
||||
|
@@ -257,6 +257,7 @@ void LinkGraphJob::EdgeAnnotation::Init()
|
||||
void LinkGraphJob::NodeAnnotation::Init(uint supply)
|
||||
{
|
||||
this->undelivered_supply = supply;
|
||||
this->received_demand = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -48,6 +48,7 @@ private:
|
||||
*/
|
||||
struct NodeAnnotation {
|
||||
uint undelivered_supply; ///< Amount of supply that hasn't been distributed yet.
|
||||
uint received_demand; ///< Received demand towards this node.
|
||||
PathList paths; ///< Paths through this node, sorted so that those with flow == 0 are in the back.
|
||||
FlowStatMap flows; ///< Planned flows to other nodes.
|
||||
void Init(uint supply);
|
||||
@@ -237,6 +238,12 @@ public:
|
||||
*/
|
||||
uint UndeliveredSupply() const { return this->node_anno.undelivered_supply; }
|
||||
|
||||
/**
|
||||
* Get amount of supply that hasn't been delivered, yet.
|
||||
* @return Undelivered supply.
|
||||
*/
|
||||
uint ReceivedDemand() const { return this->node_anno.received_demand; }
|
||||
|
||||
/**
|
||||
* Get the flows running through this node.
|
||||
* @return Flows.
|
||||
@@ -272,6 +279,15 @@ public:
|
||||
this->node_anno.undelivered_supply -= amount;
|
||||
(*this)[to].AddDemand(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive some demand, adding demand to the respective edge.
|
||||
* @param amount Amount of demand to be received.
|
||||
*/
|
||||
void ReceiveDemand(uint amount)
|
||||
{
|
||||
this->node_anno.received_demand += amount;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -584,10 +584,10 @@ struct LinkGraphSettings {
|
||||
uint16 recalc_time; ///< time (in days) for recalculating each link graph component.
|
||||
uint16 recalc_interval; ///< time (in days) between subsequent checks for link graphs to be calculated.
|
||||
bool recalc_not_scaled_by_daylength; ///< whether the time should be in daylength-scaled days (false) or unscaled days (true)
|
||||
DistributionTypeByte distribution_pax; ///< distribution type for passengers
|
||||
DistributionTypeByte distribution_mail; ///< distribution type for mail
|
||||
DistributionTypeByte distribution_armoured; ///< distribution type for armoured cargo class
|
||||
DistributionTypeByte distribution_default; ///< distribution type for all other goods
|
||||
DistributionType distribution_pax; ///< distribution type for passengers
|
||||
DistributionType distribution_mail; ///< distribution type for mail
|
||||
DistributionType distribution_armoured; ///< distribution type for armoured cargo class
|
||||
DistributionType distribution_default; ///< distribution type for all other goods
|
||||
uint8 accuracy; ///< accuracy when calculating things on the link graph. low accuracy => low running time
|
||||
uint8 demand_size; ///< influence of supply ("station size") on the demand function
|
||||
uint8 demand_distance; ///< influence of distance between stations on the demand function
|
||||
|
@@ -66,6 +66,22 @@ static int OrderTownGrowthRate(uint nth);
|
||||
|
||||
/* End - GUI order callbacks */
|
||||
|
||||
static const SettingDescEnumEntry _linkgraph_mode_symmetric[] = {
|
||||
{ DT_MANUAL, STR_CONFIG_SETTING_DISTRIBUTION_MANUAL },
|
||||
{ DT_SYMMETRIC, STR_CONFIG_SETTING_DISTRIBUTION_SYMMETRIC },
|
||||
{ DT_ASYMMETRIC, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC },
|
||||
{ DT_ASYMMETRIC_EQ, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_EQ },
|
||||
{ DT_ASYMMETRIC_NEAR, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_NEAREST },
|
||||
{ 0, STR_NULL }
|
||||
};
|
||||
static const SettingDescEnumEntry _linkgraph_mode_asymmetric[] = {
|
||||
{ DT_MANUAL, STR_CONFIG_SETTING_DISTRIBUTION_MANUAL },
|
||||
{ DT_ASYMMETRIC, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC },
|
||||
{ DT_ASYMMETRIC_EQ, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_EQ },
|
||||
{ DT_ASYMMETRIC_NEAR, STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC_NEAREST },
|
||||
{ 0, STR_NULL }
|
||||
};
|
||||
|
||||
/* Some settings do not need to be synchronised when playing in multiplayer.
|
||||
* These include for example the GUI settings and will not be saved with the
|
||||
* savegame.
|
||||
@@ -814,60 +830,44 @@ strhelp = STR_CONFIG_SETTING_LINKGRAPH_NOT_DAYLENGTH_SCALED_HELPTEXT
|
||||
extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_LINKGRAPH_DAY_SCALE)
|
||||
patxname = ""linkgraph_day_scale.linkgraph.recalc_not_scaled_by_daylength""
|
||||
|
||||
[SDT_VAR]
|
||||
[SDT_ENUM]
|
||||
base = GameSettings
|
||||
var = linkgraph.distribution_pax
|
||||
type = SLE_UINT8
|
||||
from = SLV_183
|
||||
guiflags = SGF_MULTISTRING
|
||||
def = DT_MANUAL
|
||||
min = DT_MIN
|
||||
max = DT_MAX
|
||||
interval = 1
|
||||
enumlist = _linkgraph_mode_symmetric
|
||||
str = STR_CONFIG_SETTING_DISTRIBUTION_PAX
|
||||
strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL
|
||||
strhelp = STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT
|
||||
|
||||
[SDT_VAR]
|
||||
[SDT_ENUM]
|
||||
base = GameSettings
|
||||
var = linkgraph.distribution_mail
|
||||
type = SLE_UINT8
|
||||
from = SLV_183
|
||||
guiflags = SGF_MULTISTRING
|
||||
def = DT_MANUAL
|
||||
min = DT_MIN
|
||||
max = DT_MAX
|
||||
interval = 1
|
||||
enumlist = _linkgraph_mode_symmetric
|
||||
str = STR_CONFIG_SETTING_DISTRIBUTION_MAIL
|
||||
strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL
|
||||
strhelp = STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT
|
||||
|
||||
[SDT_VAR]
|
||||
[SDT_ENUM]
|
||||
base = GameSettings
|
||||
var = linkgraph.distribution_armoured
|
||||
type = SLE_UINT8
|
||||
from = SLV_183
|
||||
guiflags = SGF_MULTISTRING
|
||||
def = DT_MANUAL
|
||||
min = DT_MIN
|
||||
max = DT_MAX
|
||||
interval = 1
|
||||
enumlist = _linkgraph_mode_symmetric
|
||||
str = STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED
|
||||
strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL
|
||||
strhelp = STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT
|
||||
|
||||
[SDT_VAR]
|
||||
[SDT_ENUM]
|
||||
base = GameSettings
|
||||
var = linkgraph.distribution_default
|
||||
type = SLE_UINT8
|
||||
from = SLV_183
|
||||
guiflags = SGF_MULTISTRING
|
||||
def = DT_MANUAL
|
||||
min = DT_BEGIN
|
||||
max = DT_MAX_NONSYMMETRIC
|
||||
interval = 1
|
||||
enumlist = _linkgraph_mode_asymmetric
|
||||
str = STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT
|
||||
strval = STR_CONFIG_SETTING_DISTRIBUTION_MANUAL
|
||||
strhelp = STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT
|
||||
|
||||
[SDT_VAR]
|
||||
|
Reference in New Issue
Block a user