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;
148 const STARTING_COIN: f32 = 1000.0;
149 const _NATURAL_RESOURCE_SCALE: f32 = 1.0 / 9.0;
150
151 pub fn population(&self) -> f32 { self.pop }
152
153 pub fn get_available_stock(&self) -> HashMap<Good, f32> {
154 self.unconsumed_stock
155 .iter()
156 .map(|(g, a)| (g.into(), *a))
157 .collect()
158 }
159
160 pub fn get_information(&self, id: Id<Site>) -> EconomyInfo {
161 EconomyInfo {
162 id: id.id(),
163 population: self.pop.floor() as u32,
164 stock: self
165 .stocks
166 .iter()
167 .map(|(g, a)| (Good::from(g), *a))
168 .collect(),
169 labor_values: self
170 .labor_values
171 .iter()
172 .filter_map(|(g, a)| a.map(|a| (Good::from(g), a)))
173 .collect(),
174 values: self
175 .values
176 .iter()
177 .filter_map(|(g, a)| a.map(|a| (Good::from(g), a)))
178 .collect(),
179 labors: self.labors.iter().map(|(_, a)| (*a)).collect(),
180 last_exports: self
181 .last_exports
182 .iter()
183 .map(|(g, a)| (Good::from(g), *a))
184 .collect(),
185 resources: self
186 .natural_resources
187 .chunks_per_resource
188 .iter()
189 .map(|(g, a)| {
190 (
191 Good::from(g),
192 (*a) * self.natural_resources.average_yield_per_chunk[g],
193 )
194 })
195 .collect(),
196 }
197 }
198
199 pub fn cache_economy(&mut self) {
200 for g in good_list() {
201 let amount: f32 = self
202 .natural_resources
203 .per_area
204 .iter()
205 .map(|a| a.resource_sum[g])
206 .sum();
207 let chunks = self
208 .natural_resources
209 .per_area
210 .iter()
211 .map(|a| a.resource_chunks[g])
212 .sum();
213 if chunks > 0.001 {
214 self.natural_resources.chunks_per_resource[g] = chunks;
215 self.natural_resources.average_yield_per_chunk[g] = amount / chunks;
216 }
217 }
218 }
219
220 fn get_orders(&self) -> &'static LaborMap<Vec<(GoodIndex, f32)>> {
222 lazy_static! {
223 static ref ORDERS: LaborMap<Vec<(GoodIndex, f32)>> = {
224 let mut res: LaborMap<Vec<(GoodIndex, f32)>> = LaborMap::default();
225 res.iter_mut()
226 .for_each(|(i, e)| e.extend(i.orders().copied()));
227 res
228 };
229 }
230 &ORDERS
231 }
232
233 fn get_orders_everyone(&self) -> impl Iterator<Item = &'static (GoodIndex, f32)> {
235 Labor::orders_everyone()
236 }
237
238 fn get_production(&self) -> LaborMap<(GoodIndex, f32)> {
239 lazy_static! {
241 static ref PRODUCTS: LaborMap<(GoodIndex, f32)> = LaborMap::from_iter(
242 Labor::list().map(|p| { (p, p.products(),) }),
243 (GoodIndex::default(), 0.0),
244 );
245 }
246 PRODUCTS.map(|l, vec| {
247 let labor_ratio = self.labors[l];
249 let total_workers = labor_ratio * self.pop;
250 let relative_scale = 1.0 + labor_ratio;
252 let absolute_scale = (1.0 + total_workers / 100.0).min(3.0);
253 let scale = relative_scale * absolute_scale;
254 (vec.0, vec.1 * scale)
255 })
256 }
257
258 fn replenish(&mut self, _time: f32) {
259 for (good, &ch) in self.natural_resources.chunks_per_resource.iter() {
260 let per_year = self.natural_resources.average_yield_per_chunk[good] * ch;
261 self.stocks[good] = self.stocks[good].max(per_year);
262 }
263 }
265
266 pub fn add_chunk(&mut self, ch: &SimChunk, distance_squared: i64) {
267 let distance_bin = (distance_squared >> 16).min(64) as usize;
270 if self.natural_resources.per_area.len() <= distance_bin {
271 self.natural_resources
272 .per_area
273 .resize_with(distance_bin + 1, Default::default);
274 }
275 self.natural_resources.per_area[distance_bin].chunks += 1;
276
277 let mut add_biome = |biome, amount| {
278 if let Ok(idx) = GoodIndex::try_from(Terrain(biome)) {
279 self.natural_resources.per_area[distance_bin].resource_sum[idx] += amount;
280 self.natural_resources.per_area[distance_bin].resource_chunks[idx] += amount;
281 }
282 };
283 if ch.river.is_ocean() {
284 add_biome(BiomeKind::Ocean, 1.0);
285 } else if ch.river.is_lake() {
286 add_biome(BiomeKind::Lake, 1.0);
287 } else {
288 add_biome(BiomeKind::Forest, 0.5 + ch.tree_density);
289 add_biome(BiomeKind::Grassland, 0.5 + ch.humidity);
290 add_biome(BiomeKind::Jungle, 0.5 + ch.humidity * ch.temp.max(0.0));
291 add_biome(BiomeKind::Mountain, 0.5 + (ch.alt / 4000.0).max(0.0));
292 add_biome(
293 BiomeKind::Desert,
294 0.5 + (1.0 - ch.humidity) * ch.temp.max(0.0),
295 );
296 add_biome(BiomeKind::Snowland, 0.5 + (-ch.temp).max(0.0));
297 }
298 }
299
300 pub fn add_neighbor(&mut self, id: Id<Site>, _distance: usize) {
301 self.neighbors.push(NeighborInformation {
302 id,
303 last_values: GoodMap::from_default(Economy::MINIMUM_PRICE),
305 last_supplies: Default::default(),
306 });
307 }
308
309 pub fn get_site_prices(&self) -> SitePrices {
310 let normalize = |xs: GoodMap<Option<f32>>| {
311 let sum = xs
312 .iter()
313 .map(|(_, x)| (*x).unwrap_or(0.0))
314 .sum::<f32>()
315 .max(0.001);
316 xs.map(|_, x| Some(x? / sum))
317 };
318
319 SitePrices {
320 values: {
321 let labor_values = normalize(self.labor_values);
322 let prices = normalize(self.values).map(|good, value| {
324 ((labor_values[good].unwrap_or(Economy::MINIMUM_PRICE)
325 + value.unwrap_or(Economy::MINIMUM_PRICE))
326 * 0.5)
327 .max(Economy::MINIMUM_PRICE)
328 });
329 prices.iter().map(|(g, v)| (Good::from(g), *v)).collect()
330 },
331 }
332 }
333
334 fn plan_trade_for_site(
339 &mut self,
341 site_id: &Id<Site>,
342 transportation_capacity: f32,
343 potential_trade: &mut GoodMap<f32>,
345 ) -> GoodMap<f32> {
346 let mut dispatch_capacity = transportation_capacity;
354 let mut collect_capacity = transportation_capacity;
355 let mut missing_dispatch: f32 = 0.0;
356 let mut missing_collect: f32 = 0.0;
357 let mut result = GoodMap::default();
358 const MIN_SELL_PRICE: f32 = 1.0;
359 let mut missing_goods: Vec<(GoodIndex, (f32, f32))> = self
361 .surplus
362 .iter()
363 .filter(|(g, a)| (**a < 0.0 && *g != *TRANSPORTATION_INDEX))
364 .map(|(g, a)| (g, (self.values[g].unwrap_or(Economy::MINIMUM_PRICE), -*a)))
365 .collect();
366 missing_goods.sort_by(|a, b| b.1.0.partial_cmp(&a.1.0).unwrap_or(Less));
367 let mut extra_goods: GoodMap<f32> = GoodMap::from_iter(
368 self.surplus
369 .iter()
370 .chain(core::iter::once((*COIN_INDEX, &self.stocks[*COIN_INDEX])))
371 .filter(|(g, a)| (**a > 0.0 && *g != *TRANSPORTATION_INDEX))
372 .map(|(g, a)| (g, *a)),
373 0.0,
374 );
375 type GoodRatioPrice = Vec<(GoodIndex, (f32, f32))>;
377 let good_payment: DHashMap<Id<Site>, GoodRatioPrice> = self
378 .neighbors
379 .iter()
380 .map(|n| {
381 let mut rel_value = extra_goods
382 .iter()
383 .map(|(g, _)| (g, n.last_values[g]))
384 .filter(|(_, last_val)| *last_val >= MIN_SELL_PRICE)
385 .map(|(g, last_val)| {
386 (
387 g,
388 (
389 last_val
390 / self.values[g].unwrap_or(-1.0).max(Economy::MINIMUM_PRICE),
391 last_val,
392 ),
393 )
394 })
395 .collect::<Vec<_>>();
396 rel_value.sort_by(|a, b| b.1.0.partial_cmp(&a.1.0).unwrap_or(Less));
397 (n.id, rel_value)
398 })
399 .collect();
400 type SitePriceStock = Vec<(Id<Site>, (f32, f32))>;
402 let mut good_price: DHashMap<GoodIndex, SitePriceStock> = missing_goods
403 .iter()
404 .map(|(g, _)| {
405 (*g, {
406 let mut neighbor_prices: Vec<(Id<Site>, (f32, f32))> = self
407 .neighbors
408 .iter()
409 .filter(|n| n.last_supplies[*g] > 0.0)
410 .map(|n| (n.id, (n.last_values[*g], n.last_supplies[*g])))
411 .collect();
412 neighbor_prices.sort_by(|a, b| a.1.0.partial_cmp(&b.1.0).unwrap_or(Less));
413 neighbor_prices
414 })
415 })
416 .collect();
417 let mut neighbor_orders: DHashMap<Id<Site>, GoodMap<f32>> = self
420 .neighbors
421 .iter()
422 .map(|n| (n.id, GoodMap::default()))
423 .collect();
424 if site_id.id() == 1 {
425 trace!(
427 "Site {} #neighbors {} Transport capacity {}",
428 site_id.id(),
429 self.neighbors.len(),
430 transportation_capacity,
431 );
432 trace!("missing {:#?} extra {:#?}", missing_goods, extra_goods,);
433 trace!("buy {:#?} pay {:#?}", good_price, good_payment);
434 }
435 for (g, (_, a)) in missing_goods.iter() {
437 let mut amount = *a;
438 if let Some(site_price_stock) = good_price.get_mut(g) {
439 for (s, (price, supply)) in site_price_stock.iter_mut() {
440 let mut buy_target = amount.min(*supply);
442 let effort = transportation_effort(*g);
443 let collect = buy_target * effort;
444 let mut potential_balance: f32 = 0.0;
445 if collect > collect_capacity && effort > 0.0 {
446 let transportable_amount = collect_capacity / effort;
447 let missing_trade = buy_target - transportable_amount;
448 potential_trade[*g] += missing_trade;
449 potential_balance += missing_trade * *price;
450 buy_target = transportable_amount; missing_collect += collect - collect_capacity;
452 trace!(
453 "missing capacity {:?}/{:?} {:?}",
454 missing_trade, amount, potential_balance,
455 );
456 amount = (amount - missing_trade).max(0.0); }
458 let mut balance: f32 = *price * buy_target;
459 trace!(
460 "buy {:?} at {:?} amount {:?} balance {:?}",
461 *g,
462 s.id(),
463 buy_target,
464 balance,
465 );
466 if let Some(neighbor_orders) = neighbor_orders.get_mut(s) {
467 let mut acute_missing_dispatch: f32 = 0.0; for (g2, (_, price2)) in good_payment[s].iter() {
470 let mut amount2 = extra_goods[*g2];
471 if amount2 > 0.0 {
473 amount2 = amount2.min(balance / price2); let effort2 = transportation_effort(*g2);
475 let mut dispatch = amount2 * effort2;
476 if dispatch > dispatch_capacity && effort2 > 0.0 {
478 let transportable_amount = dispatch_capacity / effort2;
479 let missing_trade = amount2 - transportable_amount;
480 amount2 = transportable_amount;
481 if acute_missing_dispatch == 0.0 {
482 acute_missing_dispatch = missing_trade * effort2;
483 }
484 trace!(
485 "can't carry payment {:?} {:?} {:?}",
486 g2, dispatch, dispatch_capacity
487 );
488 dispatch = dispatch_capacity;
489 }
490
491 extra_goods[*g2] -= amount2;
492 trace!("pay {:?} {:?} = {:?}", g2, amount2, balance);
493 balance -= amount2 * price2;
494 neighbor_orders[*g2] -= amount2;
495 dispatch_capacity = (dispatch_capacity - dispatch).max(0.0);
496 if balance == 0.0 {
497 break;
498 }
499 }
500 }
501 missing_dispatch += acute_missing_dispatch;
502 buy_target -= balance / *price;
504 buy_target = buy_target.min(amount);
505 collect_capacity = (collect_capacity - buy_target * effort).max(0.0);
506 neighbor_orders[*g] += buy_target;
507 amount -= buy_target;
508 trace!(
509 "deal amount {:?} end_balance {:?} price {:?} left {:?}",
510 buy_target, balance, *price, amount
511 );
512 }
513 }
514 }
515 }
516 for n in &self.neighbors {
522 if let Some(orders) = neighbor_orders.get(&n.id) {
523 for (g, a) in orders.iter() {
524 result[g] += *a;
525 }
526 let to = TradeOrder {
527 customer: *site_id,
528 amount: *orders,
529 };
530 if let Some(o) = self.orders.get_mut(&n.id) {
531 if o.len() < 100 {
533 o.push(to);
534 } else {
535 warn!("overflow {:?}", o);
536 }
537 } else {
538 self.orders.insert(n.id, vec![to]);
539 }
540 }
541 }
542 trace!(
545 "Tranportation {:?} {:?} {:?} {:?} {:?}",
546 transportation_capacity,
547 collect_capacity,
548 dispatch_capacity,
549 missing_collect,
550 missing_dispatch,
551 );
552 result[*TRANSPORTATION_INDEX] = -(transportation_capacity
553 - collect_capacity.min(dispatch_capacity)
554 + missing_collect.max(missing_dispatch));
555 if site_id.id() == 1 {
556 trace!("Trade {:?}", result);
557 }
558 result
559 }
560
561 pub fn trade_at_site(
563 &mut self,
564 site_id: Id<Site>,
565 orders: &mut Vec<TradeOrder>,
566 deliveries: &mut DHashMap<Id<Site>, Vec<TradeDelivery>>,
568 ) {
569 let internal_orders = self.get_orders();
573 let mut next_demand = GoodMap::from_default(0.0);
574 for (labor, orders) in internal_orders.iter() {
575 let workers = self.labors[labor] * self.pop;
576 for (good, amount) in orders {
577 next_demand[*good] += *amount * workers;
578 assert!(next_demand[*good] >= 0.0);
579 }
580 }
581 for (good, amount) in self.get_orders_everyone() {
582 next_demand[*good] += *amount * self.pop;
583 assert!(next_demand[*good] >= 0.0);
584 }
585 let mut total_orders: GoodMap<f32> = GoodMap::from_default(0.0);
587 for i in orders.iter() {
588 for (g, &a) in i.amount.iter().filter(|(_, a)| **a > 0.0) {
589 total_orders[g] += a;
590 }
591 }
592 let order_stock_ratio: GoodMap<Option<f32>> = GoodMap::from_iter(
593 self.stocks
594 .iter()
595 .map(|(g, a)| (g, *a, next_demand[g]))
596 .filter(|(_, a, s)| *a > *s)
597 .map(|(g, a, s)| (g, Some(total_orders[g] / (a - s)))),
598 None,
599 );
600 trace!("trade {} {:?}", site_id.id(), order_stock_ratio);
601 let prices = GoodMap::from_iter(
602 self.values
603 .iter()
604 .map(|(g, o)| (g, o.unwrap_or(0.0).max(Economy::MINIMUM_PRICE))),
605 0.0,
606 );
607 for o in orders.drain(..) {
608 let mut sorted_sell: Vec<(GoodIndex, f32, f32)> = o
611 .amount
612 .iter()
613 .filter(|(_, &a)| a > 0.0)
614 .map(|(g, a)| (g, *a, prices[g]))
615 .collect();
616 sorted_sell.sort_by(|a, b| (a.2.partial_cmp(&b.2).unwrap_or(Less)));
617 let mut sorted_buy: Vec<(GoodIndex, f32, f32)> = o
618 .amount
619 .iter()
620 .filter(|(_, &a)| a < 0.0)
621 .map(|(g, a)| (g, *a, prices[g]))
622 .collect();
623 sorted_buy.sort_by(|a, b| (b.2.partial_cmp(&a.2).unwrap_or(Less)));
624 trace!(
625 "with {} {:?} buy {:?}",
626 o.customer.id(),
627 sorted_sell,
628 sorted_buy
629 );
630 let mut good_delivery = GoodMap::from_default(0.0);
631 for (g, amount, price) in sorted_sell.iter() {
632 if let Some(order_stock_ratio) = order_stock_ratio[*g] {
633 let allocated_amount = *amount / order_stock_ratio.max(1.0);
634 let mut balance = allocated_amount * *price;
635 for (g2, avail, price2) in sorted_buy.iter_mut() {
636 let amount2 = (-*avail).min(balance / *price2);
637 assert!(amount2 >= 0.0);
638 self.stocks[*g2] += amount2;
639 balance = (balance - amount2 * *price2).max(0.0);
640 *avail += amount2; trace!("paid with {:?} {} {}", *g2, amount2, *price2);
642 if balance == 0.0 {
643 break;
644 }
645 }
646 let mut paid_amount =
647 (allocated_amount - balance / *price).min(self.stocks[*g]);
648 if paid_amount / allocated_amount < 0.95 {
649 trace!(
650 "Client {} is broke on {:?} : {} {} severity {}",
651 o.customer.id(),
652 *g,
653 paid_amount,
654 allocated_amount,
655 order_stock_ratio,
656 );
657 } else {
658 trace!("bought {:?} {} {}", *g, paid_amount, *price);
659 }
660 if self.stocks[*g] - paid_amount < 0.0 {
661 info!(
662 "BUG {:?} {:?} {} TO {:?} OSR {:?} ND {:?}",
663 self.stocks[*g],
664 *g,
665 paid_amount,
666 total_orders[*g],
667 order_stock_ratio,
668 next_demand[*g]
669 );
670 paid_amount = self.stocks[*g];
671 }
672 good_delivery[*g] += paid_amount;
673 self.stocks[*g] -= paid_amount;
674 }
675 }
676 for (g, amount, _) in sorted_buy.drain(..) {
677 if amount < 0.0 {
678 trace!("shipping back unsold {} of {:?}", amount, g);
679 good_delivery[g] += -amount;
680 }
681 }
682 let delivery = TradeDelivery {
683 supplier: site_id,
684 prices,
685 supply: GoodMap::from_iter(
686 self.stocks.iter().map(|(g, a)| {
687 (g, {
688 (a - next_demand[g] - total_orders[g]).max(0.0) + good_delivery[g]
689 })
690 }),
691 0.0,
692 ),
693 amount: good_delivery,
694 };
695 trace!(?delivery);
696 if let Some(deliveries) = deliveries.get_mut(&o.customer) {
697 deliveries.push(delivery);
698 } else {
699 deliveries.insert(o.customer, vec![delivery]);
700 }
701 }
702 if !orders.is_empty() {
703 info!("non empty orders {:?}", orders);
704 orders.clear();
705 }
706 }
707
708 fn collect_deliveries(
710 &mut self,
712 ) {
715 let mut last_exports = GoodMap::from_iter(
717 self.active_exports
718 .iter()
719 .filter(|(_g, a)| **a > 0.0)
720 .map(|(g, a)| (g, *a)),
721 0.0,
722 );
723 for mut d in self.deliveries.drain(..) {
725 for i in d.amount.iter() {
727 last_exports[i.0] -= *i.1;
728 }
729 if let Some(n) = self.neighbors.iter_mut().find(|n| n.id == d.supplier) {
731 std::mem::swap(&mut n.last_values, &mut d.prices);
733 std::mem::swap(&mut n.last_supplies, &mut d.supply);
734 for (g, a) in d.amount.iter() {
736 if *a < 0.0 {
737 trace!("Unexpected delivery for {:?} {}", g, *a);
739 } else {
740 self.stocks[g] += *a;
741 }
742 }
743 }
744 }
745 if !self.deliveries.is_empty() {
746 info!("non empty deliveries {:?}", self.deliveries);
747 self.deliveries.clear();
748 }
749 std::mem::swap(&mut last_exports, &mut self.last_exports);
750 }
752
753 pub fn tick(&mut self, site_id: Id<Site>, dt: f32) {
780 if INTER_SITE_TRADE {
782 self.collect_deliveries();
783 }
784
785 let orders = self.get_orders();
786 let production = self.get_production();
787
788 let mut demand = GoodMap::from_default(0.0);
794 for (labor, orders) in orders.iter() {
795 let workers = self.labors[labor] * self.pop;
796 for (good, amount) in orders {
797 demand[*good] += *amount * workers;
798 }
799 }
800 for (good, amount) in self.get_orders_everyone() {
801 demand[*good] += *amount * self.pop;
802 }
803 if INTER_SITE_TRADE {
804 demand[*COIN_INDEX] += Economy::STARTING_COIN; }
806
807 let merchant_labor = production
809 .iter()
810 .find(|(_, v)| v.0 == *TRANSPORTATION_INDEX)
811 .map(|(l, _)| l)
812 .unwrap_or_default();
813
814 let mut supply = self.stocks; for (labor, goodvec) in production.iter() {
816 supply[goodvec.0] += self.yields[labor] * self.labors[labor] * self.pop;
820 }
826
827 let stocks = &self.stocks;
833 self.surplus = demand.map(|g, demand| supply[g] + stocks[g] - demand);
838 self.marginal_surplus = demand.map(|g, demand| supply[g] - demand);
839
840 let mut potential_trade = GoodMap::from_default(0.0);
843 let transportation_capacity = self.stocks[*TRANSPORTATION_INDEX];
846 let trade = if INTER_SITE_TRADE {
847 let trade =
848 self.plan_trade_for_site(&site_id, transportation_capacity, &mut potential_trade);
849 self.active_exports = GoodMap::from_iter(trade.iter().map(|(g, a)| (g, -*a)), 0.0); for (g, a) in trade.iter() {
853 if *a > 0.0 {
856 supply[g] += *a;
857 assert!(supply[g] >= 0.0);
858 } else {
859 demand[g] -= *a;
860 assert!(demand[g] >= 0.0);
861 }
862 }
863 trade
864 } else {
865 GoodMap::default()
866 };
867
868 let old_coin_surplus = self.stocks[*COIN_INDEX] - demand[*COIN_INDEX];
873 let values = &mut self.values;
874
875 self.surplus.iter().for_each(|(good, surplus)| {
876 let old_surplus = if good == *COIN_INDEX {
877 old_coin_surplus
878 } else {
879 *surplus
880 };
881 let val = 2.0f32.powf(1.0 - old_surplus / demand[good]);
886 let smooth = 0.8;
887 values[good] = if val > 0.001 && val < 1000.0 {
888 Some(
889 smooth * values[good].unwrap_or(val) + (1.0 - smooth) * val,
892 )
893 } else {
894 None
895 };
896 });
897
898 let all_trade_goods: DHashSet<GoodIndex> = trade
899 .iter()
900 .chain(potential_trade.iter())
901 .filter(|(_, a)| **a > 0.0)
902 .map(|(g, _)| g)
903 .collect();
904 let labor_ratios: LaborMap<f32> = LaborMap::from_iter(
909 production.iter().map(|(labor, goodvec)| {
910 (
911 labor,
912 if labor == merchant_labor {
913 all_trade_goods
914 .iter()
915 .chain(std::iter::once(&goodvec.0))
916 .map(|&output_good| self.values[output_good].unwrap_or(0.0))
917 .max_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap_or(Less))
918 } else {
919 self.values[goodvec.0]
920 }
921 .unwrap_or(0.0)
922 * self.productivity[labor],
923 )
924 }),
925 0.0,
926 );
927 trace!(?labor_ratios);
928
929 let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::<f32>().max(0.01);
930 production.iter().for_each(|(labor, _)| {
932 let smooth = 0.8;
933 self.labors[labor] =
934 smooth * self.labors[labor]
937 + (1.0 - smooth)
938 * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum);
939 assert!(self.labors[labor] >= 0.0);
940 });
941
942 let stocks_before = self.stocks;
944 let direct_use = direct_use_goods();
947 for g in direct_use {
949 self.stocks[*g] = 0.0;
950 }
951
952 let mut total_labor_values = GoodMap::<f32>::default();
953 let mut total_outputs = GoodMap::<f32>::default();
955 for (labor, orders) in orders.iter() {
956 let workers = self.labors[labor] * self.pop;
957 assert!(workers >= 0.0);
958 let is_merchant = merchant_labor == labor;
959
960 let (labor_productivity, limited_by) = orders
967 .iter()
968 .map(|(good, amount)| {
969 let _quantity = *amount * workers;
971 assert!(stocks_before[*good] >= 0.0);
972 assert!(demand[*good] >= 0.0);
973 ((stocks_before[*good] / demand[*good]).min(1.0), *good)
975 })
976 .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Less))
977 .unwrap_or_else(|| {
978 panic!("Industry {:?} requires at least one input order", labor)
979 });
980 assert!(labor_productivity >= 0.0);
981 self.limited_by[labor] = if labor_productivity >= 1.0 {
982 GoodIndex::default()
983 } else {
984 limited_by
985 };
986
987 let mut total_materials_cost = 0.0;
988 for (good, amount) in orders {
989 let quantity = *amount * workers;
991 let used = quantity * labor_productivity;
993
994 total_materials_cost += used * self.labor_values[*good].unwrap_or(0.0);
996
997 if !direct_use.contains(good) {
999 self.stocks[*good] = (self.stocks[*good] - used).max(0.0);
1000 }
1001 }
1002 let mut produced_goods: GoodMap<f32> = GoodMap::from_default(0.0);
1003 if INTER_SITE_TRADE && is_merchant {
1004 for (g, a) in trade.iter() {
1006 if !direct_use.contains(&g) {
1007 if *a < 0.0 {
1008 if self.stocks[g] + *a < 0.0 {
1010 debug!("NEG STOCK {:?} {} {}", g, self.stocks[g], *a);
1015 let reduced_amount = self.stocks[g];
1016 let planned_amount: f32 = self
1017 .orders
1018 .iter()
1019 .map(|i| {
1020 i.1.iter()
1021 .filter(|o| o.customer == site_id)
1022 .map(|j| j.amount[g])
1023 .sum::<f32>()
1024 })
1025 .sum();
1026 let scale = reduced_amount / planned_amount.abs();
1027 trace!("re-plan {} {} {}", reduced_amount, planned_amount, scale);
1028 for k in self.orders.iter_mut() {
1029 for l in k.1.iter_mut().filter(|o| o.customer == site_id) {
1030 l.amount[g] *= scale;
1031 }
1032 }
1033 self.stocks[g] = 0.0;
1034 }
1035 else {
1037 self.stocks[g] += *a;
1038 }
1039 }
1040 total_materials_cost += (-*a) * self.labor_values[g].unwrap_or(0.0);
1041 } else {
1042 produced_goods[g] += *a;
1044 }
1045 }
1046 trace!(
1047 "merchant {} {}: {:?} {} {:?}",
1048 site_id.id(),
1049 self.pop,
1050 produced_goods,
1051 total_materials_cost,
1052 trade
1053 );
1054 }
1055
1056 let work_products = &production[labor];
1058 self.yields[labor] = labor_productivity * work_products.1;
1059 self.productivity[labor] = labor_productivity;
1060 let (stock, rate) = work_products;
1061 let total_output = labor_productivity * *rate * workers;
1062 assert!(total_output >= 0.0);
1063 self.stocks[*stock] += total_output;
1064 produced_goods[*stock] += total_output;
1065
1066 let produced_amount: f32 = produced_goods.iter().map(|(_, a)| *a).sum();
1067 for (stock, amount) in produced_goods.iter() {
1068 let cost_weight = amount / produced_amount.max(0.001);
1069 self.material_costs[stock] = total_materials_cost / amount.max(0.001) * cost_weight;
1073 let wages = 1.0;
1075 let total_labor_cost = workers * wages;
1076
1077 total_labor_values[stock] +=
1078 (total_materials_cost + total_labor_cost) * cost_weight;
1079 total_outputs[stock] += amount;
1080 }
1081 }
1082 for &(good, amount) in self.get_orders_everyone() {
1084 let needed = amount * self.pop;
1085 let available = stocks_before[good];
1086 self.stocks[good] = (self.stocks[good] - needed.min(available)).max(0.0);
1087 }
1090
1091 self.labor_values = total_labor_values.map(|stock, tlv| {
1093 let total_output = total_outputs[stock];
1094 if total_output > 0.01 {
1095 Some(tlv / total_output)
1096 } else {
1097 None
1098 }
1099 });
1100
1101 self.stocks
1103 .iter_mut()
1104 .map(|(c, v)| (v, 1.0 - decay_rate(c)))
1105 .for_each(|(v, factor)| *v *= factor);
1106
1107 self.replenish(dt);
1109
1110 const NATURAL_BIRTH_RATE: f32 = 0.05;
1112 const DEATH_RATE: f32 = 0.005;
1113 let population_growth = self.surplus[*FOOD_INDEX] > 0.0;
1114 let birth_rate = if population_growth {
1115 NATURAL_BIRTH_RATE
1116 } else {
1117 0.0
1118 };
1119 self.pop += dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE);
1122 self.population_limited_by = if population_growth {
1124 GoodIndex::default()
1125 } else {
1126 *FOOD_INDEX
1127 };
1128
1129 let mut next_demand = GoodMap::from_default(0.0);
1133 for (labor, orders) in orders.iter() {
1134 let workers = self.labors[labor] * self.pop;
1135 for (good, amount) in orders {
1136 next_demand[*good] += *amount * workers;
1137 assert!(next_demand[*good] >= 0.0);
1138 }
1139 }
1140 for (good, amount) in self.get_orders_everyone() {
1141 next_demand[*good] += *amount * self.pop;
1142 assert!(next_demand[*good] >= 0.0);
1143 }
1144 self.unconsumed_stock = GoodMap::from_iter(
1146 self.stocks.iter().map(|(g, a)| {
1147 (
1148 g,
1149 *a - next_demand[g],
1151 )
1152 }),
1153 0.0,
1154 );
1155 }
1156
1157 pub fn csv_entry(f: &mut std::fs::File, site: &Site) -> Result<(), std::io::Error> {
1158 use std::io::Write;
1159 write!(
1160 *f,
1161 "{}, {}, {}, {:.1}, {},,",
1162 site.name(),
1163 site.get_origin().x,
1164 site.get_origin().y,
1165 site.economy.pop,
1166 site.economy.neighbors.len(),
1167 )?;
1168 for g in good_list() {
1169 if let Some(value) = site.economy.values[g] {
1170 write!(*f, "{:.2},", value)?;
1171 } else {
1172 f.write_all(b",")?;
1173 }
1174 }
1175 f.write_all(b",")?;
1176 for g in good_list() {
1177 if let Some(labor_value) = site.economy.labor_values[g] {
1178 write!(f, "{:.2},", labor_value)?;
1179 } else {
1180 f.write_all(b",")?;
1181 }
1182 }
1183 f.write_all(b",")?;
1184 for g in good_list() {
1185 write!(f, "{:.1},", site.economy.stocks[g])?;
1186 }
1187 f.write_all(b",")?;
1188 for g in good_list() {
1189 write!(f, "{:.1},", site.economy.marginal_surplus[g])?;
1190 }
1191 f.write_all(b",")?;
1192 for l in LaborIndex::list() {
1193 write!(f, "{:.1},", site.economy.labors[l] * site.economy.pop)?;
1194 }
1195 f.write_all(b",")?;
1196 for l in LaborIndex::list() {
1197 write!(f, "{:.2},", site.economy.productivity[l])?;
1198 }
1199 f.write_all(b",")?;
1200 for l in LaborIndex::list() {
1201 write!(f, "{:.1},", site.economy.yields[l])?;
1202 }
1203 f.write_all(b",")?;
1204 for l in LaborIndex::list() {
1205 let limit = site.economy.limited_by[l];
1206 if limit == GoodIndex::default() {
1207 f.write_all(b",")?;
1208 } else {
1209 write!(f, "{:?},", limit)?;
1210 }
1211 }
1212 f.write_all(b",")?;
1213 for g in good_list() {
1214 if site.economy.last_exports[g] >= 0.1 || site.economy.last_exports[g] <= -0.1 {
1215 write!(f, "{:.1},", site.economy.last_exports[g])?;
1216 } else {
1217 f.write_all(b",")?;
1218 }
1219 }
1220 writeln!(f)
1221 }
1222
1223 fn csv_header(f: &mut std::fs::File) -> Result<(), std::io::Error> {
1224 use std::io::Write;
1225 write!(f, "Site,PosX,PosY,Population,Neighbors,,")?;
1226 for g in good_list() {
1227 write!(f, "{:?} Value,", g)?;
1228 }
1229 f.write_all(b",")?;
1230 for g in good_list() {
1231 write!(f, "{:?} LaborVal,", g)?;
1232 }
1233 f.write_all(b",")?;
1234 for g in good_list() {
1235 write!(f, "{:?} Stock,", g)?;
1236 }
1237 f.write_all(b",")?;
1238 for g in good_list() {
1239 write!(f, "{:?} Surplus,", g)?;
1240 }
1241 f.write_all(b",")?;
1242 for l in LaborIndex::list() {
1243 write!(f, "{:?} Labor,", l)?;
1244 }
1245 f.write_all(b",")?;
1246 for l in LaborIndex::list() {
1247 write!(f, "{:?} Productivity,", l)?;
1248 }
1249 f.write_all(b",")?;
1250 for l in LaborIndex::list() {
1251 write!(f, "{:?} Yields,", l)?;
1252 }
1253 f.write_all(b",")?;
1254 for l in LaborIndex::list() {
1255 write!(f, "{:?} limit,", l)?;
1256 }
1257 f.write_all(b",")?;
1258 for g in good_list() {
1259 write!(f, "{:?} trade,", g)?;
1260 }
1261 writeln!(f)
1262 }
1263
1264 pub fn csv_open() -> Option<std::fs::File> {
1265 if GENERATE_CSV {
1266 let mut f = std::fs::File::create("economy.csv").ok()?;
1267 if Self::csv_header(&mut f).is_err() {
1268 None
1269 } else {
1270 Some(f)
1271 }
1272 } else {
1273 None
1274 }
1275 }
1276
1277 #[cfg(test)]
1278 fn print_details(&self) {
1279 fn print_sorted(
1280 prefix: &str,
1281 mut list: Vec<(String, f32)>,
1282 threshold: f32,
1283 decimals: usize,
1284 ) {
1285 print!("{}", prefix);
1286 list.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Less));
1287 for i in list.iter() {
1288 if i.1 >= threshold {
1289 print!("{}={:.*} ", i.0, decimals, i.1);
1290 }
1291 }
1292 println!();
1293 }
1294
1295 print!(" Resources: ");
1296 for i in good_list() {
1297 let amount = self.natural_resources.chunks_per_resource[i];
1298 if amount > 0.0 {
1299 print!("{:?}={} ", i, amount);
1300 }
1301 }
1302 println!();
1303 println!(
1304 " Population {:.1}, limited by {:?}",
1305 self.pop, self.population_limited_by
1306 );
1307 let idle: f32 = self.pop * (1.0 - self.labors.iter().map(|(_, a)| *a).sum::<f32>());
1308 print_sorted(
1309 &format!(" Professions: idle={:.1} ", idle),
1310 self.labors
1311 .iter()
1312 .map(|(l, a)| (format!("{:?}", l), *a * self.pop))
1313 .collect(),
1314 self.pop * 0.05,
1315 1,
1316 );
1317 print_sorted(
1318 " Stock: ",
1319 self.stocks
1320 .iter()
1321 .map(|(l, a)| (format!("{:?}", l), *a))
1322 .collect(),
1323 1.0,
1324 0,
1325 );
1326 print_sorted(
1327 " Values: ",
1328 self.values
1329 .iter()
1330 .map(|(l, a)| {
1331 (
1332 format!("{:?}", l),
1333 a.map(|v| if v > 3.9 { 0.0 } else { v }).unwrap_or(0.0),
1334 )
1335 })
1336 .collect(),
1337 0.1,
1338 1,
1339 );
1340 print_sorted(
1341 " Labor Values: ",
1342 self.labor_values
1343 .iter()
1344 .map(|(l, a)| (format!("{:?}", l), a.unwrap_or(0.0)))
1345 .collect(),
1346 0.1,
1347 1,
1348 );
1349 print!(" Limited: ");
1350 for (limit, prod) in self.limited_by.iter().zip(self.productivity.iter()) {
1351 if (0.01..=0.99).contains(prod.1) {
1352 print!("{:?}:{:?}={:.2} ", limit.0, limit.1, *prod.1);
1353 }
1354 }
1355 println!();
1356 print!(" Trade({}): ", self.neighbors.len());
1357 for (g, &amt) in self.active_exports.iter() {
1358 if !(-0.1..=0.1).contains(&amt) {
1359 print!("{:?}={:.2} ", g, amt);
1360 }
1361 }
1362 println!();
1363 }
1364}
1365
1366fn good_list() -> impl Iterator<Item = GoodIndex> {
1367 (0..GoodIndex::LENGTH).map(GoodIndex::from_usize)
1368}
1369
1370fn transportation_effort(g: GoodIndex) -> f32 { cache::cache().transport_effort[g] }
1371
1372fn decay_rate(g: GoodIndex) -> f32 { cache::cache().decay_rate[g] }
1373
1374fn direct_use_goods() -> &'static [GoodIndex] { &cache::cache().direct_use_goods }
1376
1377pub struct GraphInfo {
1378 dummy: Economy,
1379}
1380
1381impl Default for GraphInfo {
1382 fn default() -> Self {
1383 Self {
1385 dummy: Economy {
1386 pop: 0.0,
1387 labors: LaborMap::from_default(0.0),
1388 ..Default::default()
1389 },
1390 }
1391 }
1392}
1393
1394impl GraphInfo {
1395 pub fn get_orders(&self) -> &'static LaborMap<Vec<(GoodIndex, f32)>> { self.dummy.get_orders() }
1396
1397 pub fn get_orders_everyone(&self) -> impl Iterator<Item = &'static (GoodIndex, f32)> {
1398 self.dummy.get_orders_everyone()
1399 }
1400
1401 pub fn get_production(&self) -> LaborMap<(GoodIndex, f32)> { self.dummy.get_production() }
1402
1403 pub fn good_list(&self) -> impl Iterator<Item = GoodIndex> { good_list() }
1404
1405 pub fn labor_list(&self) -> impl Iterator<Item = Labor> { Labor::list() }
1406
1407 pub fn can_store(&self, g: &GoodIndex) -> bool { direct_use_goods().contains(g) }
1408}