mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
287 lines
11 KiB
Rust
287 lines
11 KiB
Rust
use crate::{
|
|
sim::WorldSim,
|
|
site::{
|
|
economy::{Good, Labor},
|
|
Site,
|
|
},
|
|
util::MapVec,
|
|
Index,
|
|
};
|
|
use common::store::Id;
|
|
use tracing::debug;
|
|
|
|
const MONTH: f32 = 30.0;
|
|
const YEAR: f32 = 12.0 * MONTH;
|
|
const TICK_PERIOD: f32 = 3.0 * MONTH; // 3 months
|
|
const HISTORY_DAYS: f32 = 500.0 * YEAR; // 500 years
|
|
|
|
const GENERATE_CSV: bool = false;
|
|
|
|
pub fn simulate(index: &mut Index, world: &mut WorldSim) {
|
|
use std::io::Write;
|
|
let mut f = if GENERATE_CSV {
|
|
let mut f = std::fs::File::create("economy.csv").unwrap();
|
|
write!(f, "Population,").unwrap();
|
|
for g in Good::list() {
|
|
write!(f, "{:?} Value,", g).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?} LaborVal,", g).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?} Stock,", g).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?} Surplus,", g).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?} Labor,", l).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?} Productivity,", l).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?} Yields,", l).unwrap();
|
|
}
|
|
writeln!(f).unwrap();
|
|
Some(f)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
for i in 0..(HISTORY_DAYS / TICK_PERIOD) as i32 {
|
|
if (index.time / YEAR) as i32 % 50 == 0 && (index.time % YEAR) as i32 == 0 {
|
|
debug!("Year {}", (index.time / YEAR) as i32);
|
|
}
|
|
|
|
tick(index, world, TICK_PERIOD);
|
|
|
|
if let Some(f) = f.as_mut() {
|
|
if i % 5 == 0 {
|
|
let site = index.sites.values().next().unwrap();
|
|
write!(f, "{},", site.economy.pop).unwrap();
|
|
for g in Good::list() {
|
|
write!(f, "{:?},", site.economy.values[*g].unwrap_or(-1.0)).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?},", site.economy.labor_values[*g].unwrap_or(-1.0)).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?},", site.economy.stocks[*g]).unwrap();
|
|
}
|
|
for g in Good::list() {
|
|
write!(f, "{:?},", site.economy.marginal_surplus[*g]).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?},", site.economy.labors[*l] * site.economy.pop).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?},", site.economy.productivity[*l]).unwrap();
|
|
}
|
|
for l in Labor::list() {
|
|
write!(f, "{:?},", site.economy.yields[*l]).unwrap();
|
|
}
|
|
writeln!(f).unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32) {
|
|
for site in index.sites.ids() {
|
|
tick_site_economy(index, site, dt);
|
|
}
|
|
|
|
index.time += dt;
|
|
}
|
|
|
|
/// Simulate a site's economy. This simulation is roughly equivalent to the
|
|
/// Lange-Lerner model's solution to the socialist calculation problem. The
|
|
/// simulation begins by assigning arbitrary values to each commodity and then
|
|
/// incrementally updates them according to the final scarcity of the commodity
|
|
/// at the end of the tick. This results in the formulation of values that are
|
|
/// roughly analgous to prices for each commodity. The workforce is then
|
|
/// reassigned according to the respective commodity values. The simulation also
|
|
/// includes damping terms that prevent cyclical inconsistencies in value
|
|
/// rationalisation magnifying enough to crash the economy. We also ensure that
|
|
/// a small number of workers are allocated to every industry (even inactive
|
|
/// ones) each tick. This is not an accident: a small amount of productive
|
|
/// capacity in one industry allows the economy to quickly pivot to a different
|
|
/// prodution configuration should an additional commodity that acts as
|
|
/// production input become available. This means that the economy will
|
|
/// dynamically react to environmental changes. If a product becomes available
|
|
/// through a mechanism such as trade, an entire arm of the economy may
|
|
/// materialise to take advantage of this.
|
|
pub fn tick_site_economy(index: &mut Index, site: Id<Site>, dt: f32) {
|
|
let site = &mut index.sites[site];
|
|
|
|
let orders = site.economy.get_orders();
|
|
let productivity = site.economy.get_productivity();
|
|
|
|
let mut demand = MapVec::from_default(0.0);
|
|
for (labor, orders) in &orders {
|
|
let scale = if let Some(labor) = labor {
|
|
site.economy.labors[*labor]
|
|
} else {
|
|
1.0
|
|
} * site.economy.pop;
|
|
for (good, amount) in orders {
|
|
demand[*good] += *amount * scale;
|
|
}
|
|
}
|
|
|
|
let mut supply = site.economy.stocks.clone(); //MapVec::from_default(0.0);
|
|
for (labor, (output_good, _)) in productivity.iter() {
|
|
supply[*output_good] +=
|
|
site.economy.yields[labor] * site.economy.labors[labor] * site.economy.pop;
|
|
}
|
|
|
|
let stocks = &site.economy.stocks;
|
|
site.economy.surplus = demand
|
|
.clone()
|
|
.map(|g, demand| supply[g] + stocks[g] - demand);
|
|
site.economy.marginal_surplus = demand.clone().map(|g, demand| supply[g] - demand);
|
|
|
|
// Update values according to the surplus of each stock
|
|
// Note that values are used for workforce allocation and are not the same thing
|
|
// as price
|
|
let values = &mut site.economy.values;
|
|
site.economy.surplus.iter().for_each(|(good, surplus)| {
|
|
// Value rationalisation
|
|
let val = 2.0f32.powf(1.0 - *surplus / demand[good]);
|
|
let smooth = 0.8;
|
|
values[good] = if val > 0.001 && val < 1000.0 {
|
|
Some(smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val)
|
|
} else {
|
|
None
|
|
};
|
|
});
|
|
|
|
// Update export targets based on relative values
|
|
// let value_avg = values
|
|
// .iter()
|
|
// .map(|(_, v)| (*v).unwrap_or(0.0))
|
|
// .sum::<f32>()
|
|
// .max(0.01)
|
|
// / values.iter().filter(|(_, v)| v.is_some()).count() as f32;
|
|
//let export_targets = &mut site.economy.export_targets;
|
|
//let last_exports = &self.last_exports;
|
|
// site.economy.values.iter().for_each(|(stock, value)| {
|
|
// let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0);
|
|
// //let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else {
|
|
// rvalue }; //export_targets[stock] = last_exports[stock] - rvalue *
|
|
// 0.1; // + (trade_states[stock].sell_belief.price -
|
|
// trade_states[stock].buy_belief.price) * 0.025; });
|
|
|
|
//let pop = site.economy.pop;
|
|
|
|
// Redistribute workforce according to relative good values
|
|
let labor_ratios = productivity.clone().map(|labor, (output_good, _)| {
|
|
site.economy.values[output_good].unwrap_or(0.0)
|
|
* site.economy.productivity[labor]
|
|
//(site.economy.prices[output_good] - site.economy.material_costs[output_good]) * site.economy.yields[labor]
|
|
//* demand[output_good] / supply[output_good].max(0.001)
|
|
});
|
|
let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
|
|
productivity.iter().for_each(|(labor, _)| {
|
|
let smooth = 0.8;
|
|
site.economy.labors[labor] = smooth * site.economy.labors[labor]
|
|
+ (1.0 - smooth)
|
|
* (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
|
|
});
|
|
|
|
// Production
|
|
let stocks_before = site.economy.stocks.clone();
|
|
let mut total_labor_values = MapVec::<_, f32>::default();
|
|
let mut total_outputs = MapVec::<_, f32>::default();
|
|
for (labor, orders) in orders.iter() {
|
|
let scale = if let Some(labor) = labor {
|
|
site.economy.labors[*labor]
|
|
} else {
|
|
1.0
|
|
} * site.economy.pop;
|
|
|
|
// For each order, we try to find the minimum satisfaction rate - this limits
|
|
// how much we can produce! For example, if we need 0.25 fish and
|
|
// 0.75 oats to make 1 unit of food, but only 0.5 units of oats are
|
|
// available then we only need to consume 2/3rds
|
|
// of other ingredients and leave the rest in stock
|
|
// In effect, this is the productivity
|
|
let labor_productivity = orders
|
|
.iter()
|
|
.map(|(good, amount)| {
|
|
// What quantity is this order requesting?
|
|
let _quantity = *amount * scale;
|
|
// What proportion of this order is the economy able to satisfy?
|
|
(stocks_before[*good] / demand[*good]).min(1.0)
|
|
})
|
|
.min_by(|a, b| a.partial_cmp(b).unwrap())
|
|
.unwrap_or_else(|| panic!("Industry {:?} requires at least one input order", labor));
|
|
|
|
let mut total_materials_cost = 0.0;
|
|
for (good, amount) in orders {
|
|
// What quantity is this order requesting?
|
|
let quantity = *amount * scale;
|
|
// What amount gets actually used in production?
|
|
let used = quantity * labor_productivity;
|
|
|
|
// Material cost of each factor of production
|
|
total_materials_cost += used * site.economy.labor_values[*good].unwrap_or(0.0);
|
|
|
|
// Deplete stocks accordingly
|
|
site.economy.stocks[*good] = (site.economy.stocks[*good] - used).max(0.0);
|
|
}
|
|
|
|
// Industries produce things
|
|
if let Some(labor) = labor {
|
|
let (stock, rate) = productivity[*labor];
|
|
let workers = site.economy.labors[*labor] * site.economy.pop;
|
|
let final_rate = rate;
|
|
let yield_per_worker =
|
|
labor_productivity * final_rate * (1.0 + workers / 100.0).min(3.0);
|
|
site.economy.yields[*labor] = yield_per_worker;
|
|
site.economy.productivity[*labor] = labor_productivity;
|
|
let total_output = yield_per_worker * workers;
|
|
site.economy.stocks[stock] += total_output;
|
|
|
|
// Materials cost per unit
|
|
site.economy.material_costs[stock] = total_materials_cost / total_output.max(0.001);
|
|
// Labor costs
|
|
let wages = 1.0;
|
|
let total_labor_cost = workers * wages;
|
|
|
|
total_labor_values[stock] += total_materials_cost + total_labor_cost;
|
|
total_outputs[stock] += total_output;
|
|
}
|
|
}
|
|
|
|
// Update labour values per unit
|
|
site.economy.labor_values = total_labor_values.map(|stock, tlv| {
|
|
let total_output = total_outputs[stock];
|
|
if total_output > 0.01 {
|
|
Some(tlv / total_outputs[stock])
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
// Decay stocks
|
|
site.economy
|
|
.stocks
|
|
.iter_mut()
|
|
.for_each(|(c, v)| *v *= 1.0 - c.decay_rate());
|
|
|
|
// Decay stocks
|
|
site.economy.replenish(index.time);
|
|
|
|
// Births/deaths
|
|
const NATURAL_BIRTH_RATE: f32 = 0.05;
|
|
const DEATH_RATE: f32 = 0.005;
|
|
let birth_rate = if site.economy.surplus[Good::Food] > 0.0 {
|
|
NATURAL_BIRTH_RATE
|
|
} else {
|
|
0.0
|
|
};
|
|
site.economy.pop += dt / YEAR * site.economy.pop * (birth_rate - DEATH_RATE);
|
|
}
|