1use crate::world_msg::EconomyInfo;
4use crate::{
5 sim::SimChunk,
6 site::Site,
7 util::{DHashMap, DHashSet, map_array::GenericIndex},
8};
9use common::{
10 store::Id,
11 terrain::BiomeKind,
12 trade::{Good, SitePrices},
13};
14use hashbrown::HashMap;
15use lazy_static::lazy_static;
16use std::{cmp::Ordering::Less, convert::TryFrom};
17use tracing::{debug, info, trace, warn};
18
19use Good::*;
20mod map_types;
21pub use map_types::Labor;
22use map_types::{GoodIndex, GoodMap, LaborIndex, LaborMap, NaturalResources};
23mod context;
24pub use context::simulate_economy;
25mod cache;
26
27const INTER_SITE_TRADE: bool = true;
28const DAYS_PER_MONTH: f32 = 30.0;
29const DAYS_PER_YEAR: f32 = 12.0 * DAYS_PER_MONTH;
30const GENERATE_CSV: bool = false;
31
32#[derive(Debug)]
33pub struct TradeOrder {
34 customer: Id<Site>,
35 amount: GoodMap<f32>, }
37
38#[derive(Debug)]
39pub struct TradeDelivery {
40 supplier: Id<Site>,
41 amount: GoodMap<f32>, prices: GoodMap<f32>, supply: GoodMap<f32>, }
45
46#[derive(Debug, Default)]
47pub struct TradeInformation {
48 orders: DHashMap<Id<Site>, Vec<TradeOrder>>, deliveries: DHashMap<Id<Site>, Vec<TradeDelivery>>, }
51
52#[derive(Debug)]
53pub struct NeighborInformation {
54 id: Id<Site>,
55 last_values: GoodMap<f32>,
59 last_supplies: GoodMap<f32>,
60}
61
62lazy_static! {
63 static ref COIN_INDEX: GoodIndex = Coin.try_into().unwrap_or_default();
64 static ref FOOD_INDEX: GoodIndex = Good::Food.try_into().unwrap_or_default();
65 static ref TRANSPORTATION_INDEX: GoodIndex = Transportation.try_into().unwrap_or_default();
66}
67
68#[derive(Debug)]
69pub struct Economy {
70 pop: f32,
72 population_limited_by: GoodIndex,
73
74 stocks: GoodMap<f32>,
76 surplus: GoodMap<f32>,
78 marginal_surplus: GoodMap<f32>,
80 unconsumed_stock: GoodMap<f32>,
82 values: GoodMap<Option<f32>>,
86 last_exports: GoodMap<f32>,
88 active_exports: GoodMap<f32>, labor_values: GoodMap<Option<f32>>,
92 material_costs: GoodMap<f32>,
94
95 labors: LaborMap<f32>,
97 yields: LaborMap<f32>,
99 productivity: LaborMap<f32>,
101 limited_by: LaborMap<GoodIndex>,
103
104 natural_resources: NaturalResources,
105 neighbors: Vec<NeighborInformation>,
107
108 orders: DHashMap<Id<Site>, Vec<TradeOrder>>,
110 deliveries: Vec<TradeDelivery>,
112}
113
114impl Default for Economy {
115 fn default() -> Self {
116 let coin_index: GoodIndex = GoodIndex::try_from(Coin).unwrap_or_default();
117 Self {
118 pop: 32.0,
119 population_limited_by: GoodIndex::default(),
120
121 stocks: GoodMap::from_list(&[(coin_index, Economy::STARTING_COIN)], 100.0),
122 surplus: Default::default(),
123 marginal_surplus: Default::default(),
124 values: GoodMap::from_list(&[(coin_index, Some(2.0))], None),
125 last_exports: Default::default(),
126 active_exports: Default::default(),
127
128 labor_values: Default::default(),
129 material_costs: Default::default(),
130
131 labors: LaborMap::from_default(0.01),
132 yields: LaborMap::from_default(1.0),
133 productivity: LaborMap::from_default(1.0),
134 limited_by: LaborMap::from_default(GoodIndex::default()),
135
136 natural_resources: Default::default(),
137 neighbors: Default::default(),
138 unconsumed_stock: Default::default(),
139
140 orders: Default::default(),
141 deliveries: Default::default(),
142 }
143 }
144}
145
146impl Economy {
147 const MINIMUM_PRICE: f32 = 0.1;
162 const STARTING_COIN: f32 = 1000.0;
163 const _NATURAL_RESOURCE_SCALE: f32 = 1.0 / 9.0;
164
165 pub fn population(&self) -> f32 { self.pop }
166
167 pub fn get_available_stock(&self) -> HashMap<Good, f32> {
168 self.unconsumed_stock
169 .iter()
170 .map(|(g, a)| (g.into(), *a))
171 .collect()
172 }
173
174 pub fn get_information(&self, id: Id<Site>) -> EconomyInfo {
175 EconomyInfo {
176 id: id.id(),
177 population: self.pop.floor() as u32,
178 stock: self
179 .stocks
180 .iter()
181 .map(|(g, a)| (Good::from(g), *a))
182 .collect(),
183 labor_values: self
184 .labor_values
185 .iter()
186 .filter_map(|(g, a)| a.map(|a| (Good::from(g), a)))
187 .collect(),
188 values: self
189 .values
190 .iter()
191 .filter_map(|(g, a)| a.map(|a| (Good::from(g), a)))
192 .collect(),
193 labors: self.labors.iter().map(|(_, a)| *a).collect(),
194 last_exports: self
195 .last_exports
196 .iter()
197 .map(|(g, a)| (Good::from(g), *a))
198 .collect(),
199 resources: self
200 .natural_resources
201 .chunks_per_resource
202 .iter()
203 .map(|(g, a)| {
204 (
205 Good::from(g),
206 (*a) * self.natural_resources.average_yield_per_chunk[g],
207 )
208 })
209 .collect(),
210 }
211 }
212
213 pub fn cache_economy(&mut self) {
214 for g in good_list() {
215 let amount: f32 = self
216 .natural_resources
217 .per_area
218 .iter()
219 .map(|a| a.resource_sum[g])
220 .sum();
221 let chunks = self
222 .natural_resources
223 .per_area
224 .iter()
225 .map(|a| a.resource_chunks[g])
226 .sum();
227 if chunks > 0.001 {
228 self.natural_resources.chunks_per_resource[g] = chunks;
229 self.natural_resources.average_yield_per_chunk[g] = amount / chunks;
230 }
231 }
232 }
233
234 fn get_orders(&self) -> &'static LaborMap<Vec<(GoodIndex, f32)>> {
236 lazy_static! {
237 static ref ORDERS: LaborMap<Vec<(GoodIndex, f32)>> = {
238 let mut res: LaborMap<Vec<(GoodIndex, f32)>> = LaborMap::default();
239 res.iter_mut()
240 .for_each(|(i, e)| e.extend(i.orders().copied()));
241 res
242 };
243 }
244 &ORDERS
245 }
246
247 fn get_orders_everyone(&self) -> impl Iterator<Item = &'static (GoodIndex, f32)> + use<> {
249 Labor::orders_everyone()
250 }
251
252 fn get_production(&self) -> LaborMap<(GoodIndex, f32)> {
253 lazy_static! {
255 static ref PRODUCTS: LaborMap<(GoodIndex, f32)> = LaborMap::from_iter(
256 Labor::list().map(|p| { (p, p.products(),) }),
257 (GoodIndex::default(), 0.0),
258 );
259 }
260 PRODUCTS.map(|l, vec| {
261 let labor_ratio = self.labors[l];
263 let total_workers = labor_ratio * self.pop;
264 let relative_scale = 1.0 + labor_ratio;
266 let absolute_scale = (1.0 + total_workers / 100.0).min(3.0);
267 let scale = relative_scale * absolute_scale;
268 (vec.0, vec.1 * scale)
269 })
270 }
271
272 fn replenish(&mut self, _time: f32) {
273 for (good, &ch) in self.natural_resources.chunks_per_resource.iter() {
274 let per_year = self.natural_resources.average_yield_per_chunk[good] * ch;
275 self.stocks[good] = self.stocks[good].max(per_year);
276 }
277 }
279
280 pub fn add_chunk(&mut self, ch: &SimChunk, distance_squared: i64) {
281 let distance_bin = (distance_squared >> 16).min(64) as usize;
284 if self.natural_resources.per_area.len() <= distance_bin {
285 self.natural_resources
286 .per_area
287 .resize_with(distance_bin + 1, Default::default);
288 }
289 self.natural_resources.per_area[distance_bin].chunks += 1;
290
291 let mut add_biome = |biome, amount| {
292 if let Ok(idx) = GoodIndex::try_from(Terrain(biome)) {
293 self.natural_resources.per_area[distance_bin].resource_sum[idx] += amount;
294 self.natural_resources.per_area[distance_bin].resource_chunks[idx] += amount;
295 }
296 };
297 if ch.river.is_ocean() {
298 add_biome(BiomeKind::Ocean, 1.0);
299 } else if ch.river.is_lake() {
300 add_biome(BiomeKind::Lake, 1.0);
301 } else {
302 add_biome(BiomeKind::Forest, 0.5 + ch.tree_density);
303 add_biome(BiomeKind::Grassland, 0.5 + ch.humidity);
304 add_biome(BiomeKind::Jungle, 0.5 + ch.humidity * ch.temp.max(0.0));
305 add_biome(BiomeKind::Mountain, 0.5 + (ch.alt / 4000.0).max(0.0));
306 add_biome(
307 BiomeKind::Desert,
308 0.5 + (1.0 - ch.humidity) * ch.temp.max(0.0),
309 );
310 add_biome(BiomeKind::Snowland, 0.5 + (-ch.temp).max(0.0));
311 }
312 }
313
314 pub fn add_neighbor(&mut self, id: Id<Site>, _distance: usize) {
315 self.neighbors.push(NeighborInformation {
316 id,
317 last_values: GoodMap::from_default(Economy::MINIMUM_PRICE),
319 last_supplies: Default::default(),
320 });
321 }
322
323 pub fn get_site_prices(&self) -> SitePrices {
324 let normalize = |xs: GoodMap<Option<f32>>| {
325 let sum = xs
326 .iter()
327 .map(|(_, x)| (*x).unwrap_or(0.0))
328 .sum::<f32>()
329 .max(0.001);
330 xs.map(|_, x| Some(x? / sum))
331 };
332
333 SitePrices {
334 values: {
335 let labor_values = normalize(self.labor_values);
336 let prices = normalize(self.values).map(|good, value| {
338 ((labor_values[good].unwrap_or(Economy::MINIMUM_PRICE)
339 + value.unwrap_or(Economy::MINIMUM_PRICE))
340 * 0.5)
341 .max(Economy::MINIMUM_PRICE)
342 });
343 prices.iter().map(|(g, v)| (Good::from(g), *v)).collect()
344 },
345 }
346 }
347
348 fn plan_trade_for_site(
353 &mut self,
355 site_id: &Id<Site>,
356 transportation_capacity: f32,
357 potential_trade: &mut GoodMap<f32>,
359 ) -> GoodMap<f32> {
360 let mut dispatch_capacity = transportation_capacity;
368 let mut collect_capacity = transportation_capacity;
369 let mut missing_dispatch: f32 = 0.0;
370 let mut missing_collect: f32 = 0.0;
371 let mut result = GoodMap::default();
372 const MIN_SELL_PRICE: f32 = 1.0;
373 let mut missing_goods: Vec<(GoodIndex, (f32, f32))> = self
375 .surplus
376 .iter()
377 .filter(|(g, a)| **a < 0.0 && *g != *TRANSPORTATION_INDEX)
378 .map(|(g, a)| (g, (self.values[g].unwrap_or(Economy::MINIMUM_PRICE), -*a)))
379 .collect();
380 missing_goods.sort_by(|a, b| b.1.0.partial_cmp(&a.1.0).unwrap_or(Less));
381 let mut extra_goods: GoodMap<f32> = GoodMap::from_iter(
382 self.surplus
383 .iter()
384 .chain(core::iter::once((*COIN_INDEX, &self.stocks[*COIN_INDEX])))
385 .filter(|(g, a)| **a > 0.0 && *g != *TRANSPORTATION_INDEX)
386 .map(|(g, a)| (g, *a)),
387 0.0,
388 );
389 type GoodRatioPrice = Vec<(GoodIndex, (f32, f32))>;
391 let good_payment: DHashMap<Id<Site>, GoodRatioPrice> = self
392 .neighbors
393 .iter()
394 .map(|n| {
395 let mut rel_value = extra_goods
396 .iter()
397 .map(|(g, _)| (g, n.last_values[g]))
398 .filter(|(_, last_val)| *last_val >= MIN_SELL_PRICE)
399 .map(|(g, last_val)| {
400 (
401 g,
402 (
403 last_val
404 / self.values[g].unwrap_or(-1.0).max(Economy::MINIMUM_PRICE),
405 last_val,
406 ),
407 )
408 })
409 .collect::<Vec<_>>();
410 rel_value.sort_by(|a, b| b.1.0.partial_cmp(&a.1.0).unwrap_or(Less));
411 (n.id, rel_value)
412 })
413 .collect();
414 type SitePriceStock = Vec<(Id<Site>, (f32, f32))>;
416 let mut good_price: DHashMap<GoodIndex, SitePriceStock> = missing_goods
417 .iter()
418 .map(|(g, _)| {
419 (*g, {
420 let mut neighbor_prices: Vec<(Id<Site>, (f32, f32))> = self
421 .neighbors
422 .iter()
423 .filter(|n| n.last_supplies[*g] > 0.0)
424 .map(|n| (n.id, (n.last_values[*g], n.last_supplies[*g])))
425 .collect();
426 neighbor_prices.sort_by(|a, b| a.1.0.partial_cmp(&b.1.0).unwrap_or(Less));
427 neighbor_prices
428 })
429 })
430 .collect();
431 let mut neighbor_orders: DHashMap<Id<Site>, GoodMap<f32>> = self
434 .neighbors
435 .iter()
436 .map(|n| (n.id, GoodMap::default()))
437 .collect();
438 if site_id.id() == 1 {
439 trace!(
441 "Site {} #neighbors {} Transport capacity {}",
442 site_id.id(),
443 self.neighbors.len(),
444 transportation_capacity,
445 );
446 trace!("missing {:#?} extra {:#?}", missing_goods, extra_goods,);
447 trace!("buy {:#?} pay {:#?}", good_price, good_payment);
448 }
449 for (g, (_, a)) in missing_goods.iter() {
451 let mut amount = *a;
452 if let Some(site_price_stock) = good_price.get_mut(g) {
453 for (s, (price, supply)) in site_price_stock.iter_mut() {
454 let mut buy_target = amount.min(*supply);
456 let effort = transportation_effort(*g);
457 let collect = buy_target * effort;
458 let mut potential_balance: f32 = 0.0;
459 if collect > collect_capacity && effort > 0.0 {
460 let transportable_amount = collect_capacity / effort;
461 let missing_trade = buy_target - transportable_amount;
462 potential_trade[*g] += missing_trade;
463 potential_balance += missing_trade * *price;
464 buy_target = transportable_amount; missing_collect += collect - collect_capacity;
466 trace!(
467 "missing capacity {:?}/{:?} {:?}",
468 missing_trade, amount, potential_balance,
469 );
470 amount = (amount - missing_trade).max(0.0); }
472 let mut balance: f32 = *price * buy_target;
473 trace!(
474 "buy {:?} at {:?} amount {:?} balance {:?}",
475 *g,
476 s.id(),
477 buy_target,
478 balance,
479 );
480 if let Some(neighbor_orders) = neighbor_orders.get_mut(s) {
481 let mut acute_missing_dispatch: f32 = 0.0; for (g2, (_, price2)) in good_payment[s].iter() {
484 let mut amount2 = extra_goods[*g2];
485 if amount2 > 0.0 {
487 amount2 = amount2.min(balance / price2); let effort2 = transportation_effort(*g2);
489 let mut dispatch = amount2 * effort2;
490 if dispatch > dispatch_capacity && effort2 > 0.0 {
492 let transportable_amount = dispatch_capacity / effort2;
493 let missing_trade = amount2 - transportable_amount;
494 amount2 = transportable_amount;
495 if acute_missing_dispatch == 0.0 {
496 acute_missing_dispatch = missing_trade * effort2;
497 }
498 trace!(
499 "can't carry payment {:?} {:?} {:?}",
500 g2, dispatch, dispatch_capacity
501 );
502 dispatch = dispatch_capacity;
503 }
504
505 extra_goods[*g2] -= amount2;
506 trace!("pay {:?} {:?} = {:?}", g2, amount2, balance);
507 balance -= amount2 * price2;
508 neighbor_orders[*g2] -= amount2;
509 dispatch_capacity = (dispatch_capacity - dispatch).max(0.0);
510 if balance == 0.0 {
511 break;
512 }
513 }
514 }
515 missing_dispatch += acute_missing_dispatch;
516 buy_target -= balance / *price;
518 buy_target = buy_target.min(amount);
519 collect_capacity = (collect_capacity - buy_target * effort).max(0.0);
520 neighbor_orders[*g] += buy_target;
521 amount -= buy_target;
522 trace!(
523 "deal amount {:?} end_balance {:?} price {:?} left {:?}",
524 buy_target, balance, *price, amount
525 );
526 }
527 }
528 }
529 }
530 for n in &self.neighbors {
536 if let Some(orders) = neighbor_orders.get(&n.id) {
537 for (g, a) in orders.iter() {
538 result[g] += *a;
539 }
540 let to = TradeOrder {
541 customer: *site_id,
542 amount: *orders,
543 };
544 if let Some(o) = self.orders.get_mut(&n.id) {
545 if o.len() < 100 {
547 o.push(to);
548 } else {
549 warn!("overflow {:?}", o);
550 }
551 } else {
552 self.orders.insert(n.id, vec![to]);
553 }
554 }
555 }
556 trace!(
559 "Tranportation {:?} {:?} {:?} {:?} {:?}",
560 transportation_capacity,
561 collect_capacity,
562 dispatch_capacity,
563 missing_collect,
564 missing_dispatch,
565 );
566 result[*TRANSPORTATION_INDEX] = -(transportation_capacity
567 - collect_capacity.min(dispatch_capacity)
568 + missing_collect.max(missing_dispatch));
569 if site_id.id() == 1 {
570 trace!("Trade {:?}", result);
571 }
572 result
573 }
574
575 pub fn trade_at_site(
577 &mut self,
578 site_id: Id<Site>,
579 orders: &mut Vec<TradeOrder>,
580 deliveries: &mut DHashMap<Id<Site>, Vec<TradeDelivery>>,
582 ) {
583 let internal_orders = self.get_orders();
587 let mut next_demand = GoodMap::from_default(0.0);
588 for (labor, orders) in internal_orders.iter() {
589 let workers = self.labors[labor] * self.pop;
590 for (good, amount) in orders {
591 next_demand[*good] += *amount * workers;
592 assert!(next_demand[*good] >= 0.0);
593 }
594 }
595 for (good, amount) in self.get_orders_everyone() {
596 next_demand[*good] += *amount * self.pop;
597 assert!(next_demand[*good] >= 0.0);
598 }
599 let mut total_orders: GoodMap<f32> = GoodMap::from_default(0.0);
601 for i in orders.iter() {
602 for (g, &a) in i.amount.iter().filter(|(_, a)| **a > 0.0) {
603 total_orders[g] += a;
604 }
605 }
606 let order_stock_ratio: GoodMap<Option<f32>> = GoodMap::from_iter(
607 self.stocks
608 .iter()
609 .map(|(g, a)| (g, *a, next_demand[g]))
610 .filter(|(_, a, s)| *a > *s)
611 .map(|(g, a, s)| (g, Some(total_orders[g] / (a - s)))),
612 None,
613 );
614 trace!("trade {} {:?}", site_id.id(), order_stock_ratio);
615 let prices = GoodMap::from_iter(
616 self.values
617 .iter()
618 .map(|(g, o)| (g, o.unwrap_or(0.0).max(Economy::MINIMUM_PRICE))),
619 0.0,
620 );
621 for o in orders.drain(..) {
622 let mut sorted_sell: Vec<(GoodIndex, f32, f32)> = o
625 .amount
626 .iter()
627 .filter(|&(_, &a)| a > 0.0)
628 .map(|(g, a)| (g, *a, prices[g]))
629 .collect();
630 sorted_sell.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(Less));
631 let mut sorted_buy: Vec<(GoodIndex, f32, f32)> = o
632 .amount
633 .iter()
634 .filter(|&(_, &a)| a < 0.0)
635 .map(|(g, a)| (g, *a, prices[g]))
636 .collect();
637 sorted_buy.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(Less));
638 trace!(
639 "with {} {:?} buy {:?}",
640 o.customer.id(),
641 sorted_sell,
642 sorted_buy
643 );
644 let mut good_delivery = GoodMap::from_default(0.0);
645 for (g, amount, price) in sorted_sell.iter() {
646 if let Some(order_stock_ratio) = order_stock_ratio[*g] {
647 let allocated_amount = *amount / order_stock_ratio.max(1.0);
648 let mut balance = allocated_amount * *price;
649 for (g2, avail, price2) in sorted_buy.iter_mut() {
650 let amount2 = (-*avail).min(balance / *price2);
651 assert!(amount2 >= 0.0);
652 self.stocks[*g2] += amount2;
653 balance = (balance - amount2 * *price2).max(0.0);
654 *avail += amount2; trace!("paid with {:?} {} {}", *g2, amount2, *price2);
656 if balance == 0.0 {
657 break;
658 }
659 }
660 let mut paid_amount =
661 (allocated_amount - balance / *price).min(self.stocks[*g]);
662 if paid_amount / allocated_amount < 0.95 {
663 trace!(
664 "Client {} is broke on {:?} : {} {} severity {}",
665 o.customer.id(),
666 *g,
667 paid_amount,
668 allocated_amount,
669 order_stock_ratio,
670 );
671 } else {
672 trace!("bought {:?} {} {}", *g, paid_amount, *price);
673 }
674 if self.stocks[*g] - paid_amount < 0.0 {
675 info!(
676 "BUG {:?} {:?} {} TO {:?} OSR {:?} ND {:?}",
677 self.stocks[*g],
678 *g,
679 paid_amount,
680 total_orders[*g],
681 order_stock_ratio,
682 next_demand[*g]
683 );
684 paid_amount = self.stocks[*g];
685 }
686 good_delivery[*g] += paid_amount;
687 self.stocks[*g] -= paid_amount;
688 }
689 }
690 for (g, amount, _) in sorted_buy.drain(..) {
691 if amount < 0.0 {
692 trace!("shipping back unsold {} of {:?}", amount, g);
693 good_delivery[g] += -amount;
694 }
695 }
696 let delivery = TradeDelivery {
697 supplier: site_id,
698 prices,
699 supply: GoodMap::from_iter(
700 self.stocks.iter().map(|(g, a)| {
701 (g, {
702 (a - next_demand[g] - total_orders[g]).max(0.0) + good_delivery[g]
703 })
704 }),
705 0.0,
706 ),
707 amount: good_delivery,
708 };
709 trace!(?delivery);
710 if let Some(deliveries) = deliveries.get_mut(&o.customer) {
711 deliveries.push(delivery);
712 } else {
713 deliveries.insert(o.customer, vec![delivery]);
714 }
715 }
716 if !orders.is_empty() {
717 info!("non empty orders {:?}", orders);
718 orders.clear();
719 }
720 }
721
722 fn collect_deliveries(
724 &mut self,
726 ) {
729 let mut last_exports = GoodMap::from_iter(
731 self.active_exports
732 .iter()
733 .filter(|(_g, a)| **a > 0.0)
734 .map(|(g, a)| (g, *a)),
735 0.0,
736 );
737 for mut d in self.deliveries.drain(..) {
739 for i in d.amount.iter() {
741 last_exports[i.0] -= *i.1;
742 }
743 if let Some(n) = self.neighbors.iter_mut().find(|n| n.id == d.supplier) {
745 std::mem::swap(&mut n.last_values, &mut d.prices);
747 std::mem::swap(&mut n.last_supplies, &mut d.supply);
748 for (g, a) in d.amount.iter() {
750 if *a < 0.0 {
751 trace!("Unexpected delivery for {:?} {}", g, *a);
753 } else {
754 self.stocks[g] += *a;
755 }
756 }
757 }
758 }
759 if !self.deliveries.is_empty() {
760 info!("non empty deliveries {:?}", self.deliveries);
761 self.deliveries.clear();
762 }
763 std::mem::swap(&mut last_exports, &mut self.last_exports);
764 }
766
767 pub fn tick(&mut self, site_id: Id<Site>, dt: f32) {
794 if INTER_SITE_TRADE {
796 self.collect_deliveries();
797 }
798
799 let orders = self.get_orders();
800 let production = self.get_production();
801
802 let mut demand = GoodMap::from_default(0.0);
808 for (labor, orders) in orders.iter() {
809 let workers = self.labors[labor] * self.pop;
810 for (good, amount) in orders {
811 demand[*good] += *amount * workers;
812 }
813 }
814 for (good, amount) in self.get_orders_everyone() {
815 demand[*good] += *amount * self.pop;
816 }
817 if INTER_SITE_TRADE {
818 demand[*COIN_INDEX] += Economy::STARTING_COIN; }
820
821 let merchant_labor = production
823 .iter()
824 .find(|(_, v)| v.0 == *TRANSPORTATION_INDEX)
825 .map(|(l, _)| l)
826 .unwrap_or_default();
827
828 let mut supply = self.stocks; for (labor, goodvec) in production.iter() {
830 supply[goodvec.0] += self.yields[labor] * self.labors[labor] * self.pop;
834 }
840
841 let stocks = &self.stocks;
847 self.surplus = demand.map(|g, demand| supply[g] + stocks[g] - demand);
852 self.marginal_surplus = demand.map(|g, demand| supply[g] - demand);
853
854 let mut potential_trade = GoodMap::from_default(0.0);
857 let transportation_capacity = self.stocks[*TRANSPORTATION_INDEX];
860 let trade = if INTER_SITE_TRADE {
861 let trade =
862 self.plan_trade_for_site(&site_id, transportation_capacity, &mut potential_trade);
863 self.active_exports = GoodMap::from_iter(trade.iter().map(|(g, a)| (g, -*a)), 0.0); for (g, a) in trade.iter() {
867 if *a > 0.0 {
870 supply[g] += *a;
871 assert!(supply[g] >= 0.0);
872 } else {
873 demand[g] -= *a;
874 assert!(demand[g] >= 0.0);
875 }
876 }
877 trade
878 } else {
879 GoodMap::default()
880 };
881
882 let old_coin_surplus = self.stocks[*COIN_INDEX] - demand[*COIN_INDEX];
887 let values = &mut self.values;
888
889 self.surplus.iter().for_each(|(good, surplus)| {
890 let old_surplus = if good == *COIN_INDEX {
891 old_coin_surplus
892 } else {
893 *surplus
894 };
895 let val = 2.0f32.powf(1.0 - old_surplus / demand[good]);
900 let smooth = 0.8;
901 values[good] = if val > 0.001 && val < 1000.0 {
902 Some(
903 smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val,
906 )
907 } else {
908 None
909 };
910 });
911
912 let all_trade_goods: DHashSet<GoodIndex> = trade
913 .iter()
914 .chain(potential_trade.iter())
915 .filter(|(_, a)| **a > 0.0)
916 .map(|(g, _)| g)
917 .collect();
918 let labor_ratios: LaborMap<f32> = LaborMap::from_iter(
923 production.iter().map(|(labor, goodvec)| {
924 (
925 labor,
926 if labor == merchant_labor {
927 all_trade_goods
928 .iter()
929 .chain(std::iter::once(&goodvec.0))
930 .map(|&output_good| self.values[output_good].unwrap_or(0.0))
931 .max_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap_or(Less))
932 } else {
933 self.values[goodvec.0]
934 }
935 .unwrap_or(0.0)
936 * self.productivity[labor],
937 )
938 }),
939 0.0,
940 );
941 trace!(?labor_ratios);
942
943 let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
944 production.iter().for_each(|(labor, _)| {
946 let smooth = 0.8;
947 self.labors[labor] =
948 smooth * self.labors[labor]
951 + (1.0 - smooth)
952 * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
953 assert!(self.labors[labor] >= 0.0);
954 });
955
956 let stocks_before = self.stocks;
958 let direct_use = direct_use_goods();
961 for g in direct_use {
963 self.stocks[*g] = 0.0;
964 }
965
966 let mut total_labor_values = GoodMap::<f32>::default();
967 let mut total_outputs = GoodMap::<f32>::default();
969 for (labor, orders) in orders.iter() {
970 let workers = self.labors[labor] * self.pop;
971 assert!(workers >= 0.0);
972 let is_merchant = merchant_labor == labor;
973
974 let (labor_productivity, limited_by) = orders
981 .iter()
982 .map(|(good, amount)| {
983 let _quantity = *amount * workers;
985 assert!(stocks_before[*good] >= 0.0);
986 assert!(demand[*good] >= 0.0);
987 ((stocks_before[*good] / demand[*good]).min(1.0), *good)
989 })
990 .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Less))
991 .unwrap_or_else(|| {
992 panic!("Industry {:?} requires at least one input order", labor)
993 });
994 assert!(labor_productivity >= 0.0);
995 self.limited_by[labor] = if labor_productivity >= 1.0 {
996 GoodIndex::default()
997 } else {
998 limited_by
999 };
1000
1001 let mut total_materials_cost = 0.0;
1002 for (good, amount) in orders {
1003 let quantity = *amount * workers;
1005 let used = quantity * labor_productivity;
1007
1008 total_materials_cost += used * self.labor_values[*good].unwrap_or(0.0);
1010
1011 if !direct_use.contains(good) {
1013 self.stocks[*good] = (self.stocks[*good] - used).max(0.0);
1014 }
1015 }
1016 let mut produced_goods: GoodMap<f32> = GoodMap::from_default(0.0);
1017 if INTER_SITE_TRADE && is_merchant {
1018 for (g, a) in trade.iter() {
1020 if !direct_use.contains(&g) {
1021 if *a < 0.0 {
1022 if self.stocks[g] + *a < 0.0 {
1024 debug!("NEG STOCK {:?} {} {}", g, self.stocks[g], *a);
1029 let reduced_amount = self.stocks[g];
1030 let planned_amount: f32 = self
1031 .orders
1032 .iter()
1033 .map(|i| {
1034 i.1.iter()
1035 .filter(|o| o.customer == site_id)
1036 .map(|j| j.amount[g])
1037 .sum::<f32>()
1038 })
1039 .sum();
1040 let scale = reduced_amount / planned_amount.abs();
1041 trace!("re-plan {} {} {}", reduced_amount, planned_amount, scale);
1042 for k in self.orders.iter_mut() {
1043 for l in k.1.iter_mut().filter(|o| o.customer == site_id) {
1044 l.amount[g] *= scale;
1045 }
1046 }
1047 self.stocks[g] = 0.0;
1048 }
1049 else {
1051 self.stocks[g] += *a;
1052 }
1053 }
1054 total_materials_cost += (-*a) * self.labor_values[g].unwrap_or(0.0);
1055 } else {
1056 produced_goods[g] += *a;
1058 }
1059 }
1060 trace!(
1061 "merchant {} {}: {:?} {} {:?}",
1062 site_id.id(),
1063 self.pop,
1064 produced_goods,
1065 total_materials_cost,
1066 trade
1067 );
1068 }
1069
1070 let work_products = &production[labor];
1072 self.yields[labor] = labor_productivity * work_products.1;
1073 self.productivity[labor] = labor_productivity;
1074 let (stock, rate) = work_products;
1075 let total_output = labor_productivity * *rate * workers;
1076 assert!(total_output >= 0.0);
1077 self.stocks[*stock] += total_output;
1078 produced_goods[*stock] += total_output;
1079
1080 let produced_amount: f32 = produced_goods.iter().map(|(_, a)| *a).sum();
1081 for (stock, amount) in produced_goods.iter() {
1082 let cost_weight = amount / produced_amount.max(0.001);
1083 self.material_costs[stock] = total_materials_cost / amount.max(0.001) * cost_weight;
1087 let wages = 1.0;
1089 let total_labor_cost = workers * wages;
1090
1091 total_labor_values[stock] +=
1092 (total_materials_cost + total_labor_cost) * cost_weight;
1093 total_outputs[stock] += amount;
1094 }
1095 }
1096 for &(good, amount) in self.get_orders_everyone() {
1098 let needed = amount * self.pop;
1099 let available = stocks_before[good];
1100 self.stocks[good] = (self.stocks[good] - needed.min(available)).max(0.0);
1101 }
1104
1105 self.labor_values = total_labor_values.map(|stock, tlv| {
1107 let total_output = total_outputs[stock];
1108 if total_output > 0.01 {
1109 Some(tlv / total_output)
1110 } else {
1111 None
1112 }
1113 });
1114
1115 self.stocks
1117 .iter_mut()
1118 .map(|(c, v)| (v, 1.0 - decay_rate(c)))
1119 .for_each(|(v, factor)| *v *= factor);
1120
1121 self.replenish(dt);
1123
1124 const NATURAL_BIRTH_RATE: f32 = 0.05;
1126 const DEATH_RATE: f32 = 0.005;
1127 let population_growth = self.surplus[*FOOD_INDEX] > 0.0;
1128 let birth_rate = if population_growth {
1129 NATURAL_BIRTH_RATE
1130 } else {
1131 0.0
1132 };
1133 self.pop += dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE);
1136 self.population_limited_by = if population_growth {
1138 GoodIndex::default()
1139 } else {
1140 *FOOD_INDEX
1141 };
1142
1143 let mut next_demand = GoodMap::from_default(0.0);
1147 for (labor, orders) in orders.iter() {
1148 let workers = self.labors[labor] * self.pop;
1149 for (good, amount) in orders {
1150 next_demand[*good] += *amount * workers;
1151 assert!(next_demand[*good] >= 0.0);
1152 }
1153 }
1154 for (good, amount) in self.get_orders_everyone() {
1155 next_demand[*good] += *amount * self.pop;
1156 assert!(next_demand[*good] >= 0.0);
1157 }
1158 self.unconsumed_stock = GoodMap::from_iter(
1160 self.stocks.iter().map(|(g, a)| {
1161 (
1162 g,
1163 *a - next_demand[g],
1165 )
1166 }),
1167 0.0,
1168 );
1169 }
1170
1171 pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> {
1172 use std::io::Write;
1173 let d = Economy::default();
1174 let economy = site.economy.as_deref().unwrap_or(&d);
1175 write!(
1176 *f,
1177 "{}, {}, {}, {:.1}, {},,",
1178 site.name().unwrap_or("<None>"),
1179 site.origin.x,
1180 site.origin.y,
1181 economy.pop,
1182 economy.neighbors.len(),
1183 )?;
1184 for g in good_list() {
1185 if let Some(value) = economy.values[g] {
1186 write!(*f, "{:.2},", value)?;
1187 } else {
1188 f.write_all(b",")?;
1189 }
1190 }
1191 f.write_all(b",")?;
1192 for g in good_list() {
1193 if let Some(labor_value) = economy.labor_values[g] {
1194 write!(f, "{:.2},", labor_value)?;
1195 } else {
1196 f.write_all(b",")?;
1197 }
1198 }
1199 f.write_all(b",")?;
1200 for g in good_list() {
1201 write!(f, "{:.1},", economy.stocks[g])?;
1202 }
1203 f.write_all(b",")?;
1204 for g in good_list() {
1205 write!(f, "{:.1},", economy.marginal_surplus[g])?;
1206 }
1207 f.write_all(b",")?;
1208 for l in LaborIndex::list() {
1209 write!(f, "{:.1},", economy.labors[l] * economy.pop)?;
1210 }
1211 f.write_all(b",")?;
1212 for l in LaborIndex::list() {
1213 write!(f, "{:.2},", economy.productivity[l])?;
1214 }
1215 f.write_all(b",")?;
1216 for l in LaborIndex::list() {
1217 write!(f, "{:.1},", economy.yields[l])?;
1218 }
1219 f.write_all(b",")?;
1220 for l in LaborIndex::list() {
1221 let limit = economy.limited_by[l];
1222 if limit == GoodIndex::default() {
1223 f.write_all(b",")?;
1224 } else {
1225 write!(f, "{:?},", limit)?;
1226 }
1227 }
1228 f.write_all(b",")?;
1229 for g in good_list() {
1230 if economy.last_exports[g] >= 0.1 || economy.last_exports[g] <= -0.1 {
1231 write!(f, "{:.1},", economy.last_exports[g])?;
1232 } else {
1233 f.write_all(b",")?;
1234 }
1235 }
1236 writeln!(f)
1237 }
1238
1239 fn csv_header(f: &mut std::fs::File) -> Result<(), std::io::Error> {
1240 use std::io::Write;
1241 write!(f, "Site,PosX,PosY,Population,Neighbors,,")?;
1242 for g in good_list() {
1243 write!(f, "{:?} Value,", g)?;
1244 }
1245 f.write_all(b",")?;
1246 for g in good_list() {
1247 write!(f, "{:?} LaborVal,", g)?;
1248 }
1249 f.write_all(b",")?;
1250 for g in good_list() {
1251 write!(f, "{:?} Stock,", g)?;
1252 }
1253 f.write_all(b",")?;
1254 for g in good_list() {
1255 write!(f, "{:?} Surplus,", g)?;
1256 }
1257 f.write_all(b",")?;
1258 for l in LaborIndex::list() {
1259 write!(f, "{:?} Labor,", l)?;
1260 }
1261 f.write_all(b",")?;
1262 for l in LaborIndex::list() {
1263 write!(f, "{:?} Productivity,", l)?;
1264 }
1265 f.write_all(b",")?;
1266 for l in LaborIndex::list() {
1267 write!(f, "{:?} Yields,", l)?;
1268 }
1269 f.write_all(b",")?;
1270 for l in LaborIndex::list() {
1271 write!(f, "{:?} limit,", l)?;
1272 }
1273 f.write_all(b",")?;
1274 for g in good_list() {
1275 write!(f, "{:?} trade,", g)?;
1276 }
1277 writeln!(f)
1278 }
1279
1280 pub fn csv_open() -> Option<std::fs::File> {
1281 if GENERATE_CSV {
1282 let mut f = std::fs::File::create("economy.csv").ok()?;
1283 if Self::csv_header(&mut f).is_err() {
1284 None
1285 } else {
1286 Some(f)
1287 }
1288 } else {
1289 None
1290 }
1291 }
1292
1293 #[cfg(test)]
1294 fn print_details(&self) {
1295 fn print_sorted(
1296 prefix: &str,
1297 mut list: Vec<(String, f32)>,
1298 threshold: f32,
1299 decimals: usize,
1300 ) {
1301 print!("{}", prefix);
1302 list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Less));
1303 for i in list.iter() {
1304 if i.1 >= threshold {
1305 print!("{}={:.*} ", i.0, decimals, i.1);
1306 }
1307 }
1308 println!();
1309 }
1310
1311 print!(" Resources: ");
1312 for i in good_list() {
1313 let amount = self.natural_resources.chunks_per_resource[i];
1314 if amount > 0.0 {
1315 print!("{:?}={} ", i, amount);
1316 }
1317 }
1318 println!();
1319 println!(
1320 " Population {:.1}, limited by {:?}",
1321 self.pop, self.population_limited_by
1322 );
1323 let idle: f32 = self.pop * (1.0 - self.labors.iter().map(|(_, a)| *a).sum::<f32>());
1324 print_sorted(
1325 &format!(" Professions: idle={:.1} ", idle),
1326 self.labors
1327 .iter()
1328 .map(|(l, a)| (format!("{:?}", l), *a * self.pop))
1329 .collect(),
1330 self.pop * 0.05,
1331 1,
1332 );
1333 print_sorted(
1334 " Stock: ",
1335 self.stocks
1336 .iter()
1337 .map(|(l, a)| (format!("{:?}", l), *a))
1338 .collect(),
1339 1.0,
1340 0,
1341 );
1342 print_sorted(
1343 " Values: ",
1344 self.values
1345 .iter()
1346 .map(|(l, a)| {
1347 (
1348 format!("{:?}", l),
1349 a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0),
1350 )
1351 })
1352 .collect(),
1353 0.1,
1354 1,
1355 );
1356 print_sorted(
1357 " Labor Values: ",
1358 self.labor_values
1359 .iter()
1360 .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0)))
1361 .collect(),
1362 0.1,
1363 1,
1364 );
1365 print!(" Limited: ");
1366 for (limit, prod) in self.limited_by.iter().zip(self.productivity.iter()) {
1367 if (0.01..=0.99).contains(prod.1) {
1368 print!("{:?}:{:?}={:.2} ", limit.0, limit.1, *prod.1);
1369 }
1370 }
1371 println!();
1372 print!(" Trade({}): ", self.neighbors.len());
1373 for (g, &amt) in self.active_exports.iter() {
1374 if !(-0.1..=0.1).contains(&amt) {
1375 print!("{:?}={:.2} ", g, amt);
1376 }
1377 }
1378 println!();
1379 }
1380}
1381
1382fn good_list() -> impl Iterator<Item = GoodIndex> {
1383 (0..GoodIndex::LENGTH).map(GoodIndex::from_usize)
1384}
1385
1386fn transportation_effort(g: GoodIndex) -> f32 { cache::cache().transport_effort[g] }
1387
1388fn decay_rate(g: GoodIndex) -> f32 { cache::cache().decay_rate[g] }
1389
1390fn direct_use_goods() -> &'static [GoodIndex] { &cache::cache().direct_use_goods }
1392
1393pub struct GraphInfo {
1394 dummy: Economy,
1395}
1396
1397impl Default for GraphInfo {
1398 fn default() -> Self {
1399 Self {
1401 dummy: Economy {
1402 pop: 0.0,
1403 labors: LaborMap::from_default(0.0),
1404 ..Default::default()
1405 },
1406 }
1407 }
1408}
1409
1410impl GraphInfo {
1411 pub fn get_orders(&self) -> &'static LaborMap<Vec<(GoodIndex, f32)>> { self.dummy.get_orders() }
1412
1413 pub fn get_orders_everyone(&self) -> impl Iterator<Item = &'static (GoodIndex, f32)> + use<> {
1414 self.dummy.get_orders_everyone()
1415 }
1416
1417 pub fn get_production(&self) -> LaborMap<(GoodIndex, f32)> { self.dummy.get_production() }
1418
1419 pub fn good_list(&self) -> impl Iterator<Item = GoodIndex> + use<> { good_list() }
1420
1421 pub fn labor_list(&self) -> impl Iterator<Item = Labor> + use<> { Labor::list() }
1422
1423 pub fn can_store(&self, g: &GoodIndex) -> bool { direct_use_goods().contains(g) }
1424}