veloren_world/site/economy/
mod.rs

1/// This file contains a single economy
2/// and functions to simulate it
3use 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>, // positive for orders, negative for exchange
36}
37
38#[derive(Debug)]
39pub struct TradeDelivery {
40    supplier: Id<Site>,
41    amount: GoodMap<f32>, // positive for orders, negative for exchange
42    prices: GoodMap<f32>, // at the time of interaction
43    supply: GoodMap<f32>, // maximum amount available, at the time of interaction
44}
45
46#[derive(Debug, Default)]
47pub struct TradeInformation {
48    orders: DHashMap<Id<Site>, Vec<TradeOrder>>, // per provider
49    deliveries: DHashMap<Id<Site>, Vec<TradeDelivery>>, // per receiver
50}
51
52#[derive(Debug)]
53pub struct NeighborInformation {
54    id: Id<Site>,
55    //travel_distance: usize,
56
57    // remembered from last interaction
58    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    /// Population
71    pop: f32,
72    population_limited_by: GoodIndex,
73
74    /// Total available amount of each good
75    stocks: GoodMap<f32>,
76    /// Surplus stock compared to demand orders
77    surplus: GoodMap<f32>,
78    /// change rate (derivative) of stock in the current situation
79    marginal_surplus: GoodMap<f32>,
80    /// amount of wares not needed by the economy (helps with trade planning)
81    unconsumed_stock: GoodMap<f32>,
82    /// Local availability of a good, 4.0 = starved, 2.0 = balanced, 0.1 =
83    /// extra, NULL = way too much
84    // For some goods, such a goods without any supply, it doesn't make sense to talk about value
85    values: GoodMap<Option<f32>>,
86    /// amount of goods exported/imported during the last cycle
87    last_exports: GoodMap<f32>,
88    active_exports: GoodMap<f32>, // unfinished trade (amount unconfirmed)
89    //pub export_targets: GoodMap<f32>,
90    /// amount of labor that went into a good, [1 man cycle=1.0]
91    labor_values: GoodMap<Option<f32>>,
92    // this assumes a single source, replace with LaborMap?
93    material_costs: GoodMap<f32>,
94
95    /// Proportion of individuals dedicated to an industry (sums to roughly 1.0)
96    labors: LaborMap<f32>,
97    // Per worker, per year, of their output good
98    yields: LaborMap<f32>,
99    /// [0.0..1.0]
100    productivity: LaborMap<f32>,
101    /// Missing raw material which limits production
102    limited_by: LaborMap<GoodIndex>,
103
104    natural_resources: NaturalResources,
105    /// Neighboring sites to trade with
106    neighbors: Vec<NeighborInformation>,
107
108    /// outgoing trade, per provider
109    orders: DHashMap<Id<Site>, Vec<TradeOrder>>,
110    /// incoming trade - only towards this site
111    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    // FIXME?: this is hit for (almost) every Good
148    //
149    // Which means that all goods in all cities have the same price.
150    //
151    // We could try to change that fact, but:
152    // a) This would mean we need need to rebalance prices again, which
153    // could probably be done quite easily with good_scaling, but still.
154    // b) Making prices vary from town to town would lead to (expected)
155    // scenarios where price of buying good in one town is less than
156    // the price of selling good in another town.
157    // Which we probably want, but the question is to what extent.
158    // c) Traveling merchants fuck this anyway from a gameplay perspective,
159    // since they have randomized origins anyway and don't conform to local
160    // prices the way they are coded right now.
161    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    /// orders per profession (excluding everyone)
235    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    /// resources consumed by everyone (no matter which profession)
248    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        // cache the site independent part of production
254        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            //dbg!((l,vec));
262            let labor_ratio = self.labors[l];
263            let total_workers = labor_ratio * self.pop;
264            // apply economy of scale (workers get more productive in numbers)
265            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        // info!("resources {:?}", self.stocks);
278    }
279
280    pub fn add_chunk(&mut self, ch: &SimChunk, distance_squared: i64) {
281        // let biome = ch.get_biome();
282        // we don't scale by pi, although that would be correct
283        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            //travel_distance: distance,
318            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                // Use labor values as prices. Not correct (doesn't care about exchange value)
337                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    /// plan the trading according to missing goods and prices at neighboring
349    /// sites (1st step of trading)
350    // returns wares spent (-) and procured (+)
351    // potential_trade: positive = buy, (negative = sell, unused)
352    fn plan_trade_for_site(
353        // site: &mut Site,
354        &mut self,
355        site_id: &Id<Site>,
356        transportation_capacity: f32,
357        // external_orders: &mut DHashMap<Id<Site>, Vec<TradeOrder>>,
358        potential_trade: &mut GoodMap<f32>,
359    ) -> GoodMap<f32> {
360        // TODO: Do we have some latency of information here (using last years
361        // capacity?)
362        //let total_transport_capacity = self.stocks[Transportation];
363        // TODO: We don't count the capacity per site, but globally (so there might be
364        // some imbalance in dispatch vs collection across sites (e.g. more dispatch
365        // than collection at one while more collection than dispatch at another))
366        // transport capacity works both ways (going there and returning)
367        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        // value+amount per good
374        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        // ratio+price per good and site
390        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        // price+stock per site and good
415        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        // TODO: we need to introduce priority (according to available transportation
432        // capacity)
433        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            // cut down number of lines printed
440            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        // === the actual planning is here ===
450        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                    // how much to buy, limit by supply and transport budget
455                    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; // (buy_target - missing_trade).max(0.0); // avoid negative buy target caused by numeric inaccuracies
465                        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); // you won't be able to transport it from elsewhere either, so don't count multiple times
471                    }
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                        // find suitable goods in exchange
482                        let mut acute_missing_dispatch: f32 = 0.0; // only count the highest priority (not multiple times)
483                        for (g2, (_, price2)) in good_payment[s].iter() {
484                            let mut amount2 = extra_goods[*g2];
485                            // good available for trading?
486                            if amount2 > 0.0 {
487                                amount2 = amount2.min(balance / price2); // pay until balance is even
488                                let effort2 = transportation_effort(*g2);
489                                let mut dispatch = amount2 * effort2;
490                                // limit by separate transport budget (on way back)
491                                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                        // adjust order if we are unable to pay for it
517                        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        // if site_id.id() == 1 {
531        //     // cut down number of lines printed
532        //     info!("orders {:#?}", neighbor_orders,);
533        // }
534        // TODO: Use planned orders and calculate value, stock etc. accordingly
535        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                    // this is just to catch unbound growth (happened in development)
546                    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        // return missing transport capacity
557        //missing_collect.max(missing_dispatch)
558        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    /// perform trade using neighboring orders (2nd step of trading)
576    pub fn trade_at_site(
577        &mut self,
578        site_id: Id<Site>,
579        orders: &mut Vec<TradeOrder>,
580        // economy: &mut Economy,
581        deliveries: &mut DHashMap<Id<Site>, Vec<TradeDelivery>>,
582    ) {
583        // make sure that at least this amount of stock remains available
584        // TODO: rework using economy.unconsumed_stock
585
586        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        //info!("Trade {} {}", site.id(), orders.len());
600        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            // amount, local value (sell low value, buy high value goods first (trading
623            // town's interest))
624            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; // reduce (negative) brought stock
655                        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    /// 3rd step of trading
723    fn collect_deliveries(
724        // site: &mut Site,
725        &mut self,
726        // deliveries: &mut Vec<TradeDelivery>,
727        // ctx: &mut vergleich::Context,
728    ) {
729        // collect all the goods we shipped
730        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        // TODO: properly rate benefits created by merchants (done below?)
738        for mut d in self.deliveries.drain(..) {
739            // let mut ictx = ctx.context(&format!("suppl {}", d.supplier.id()));
740            for i in d.amount.iter() {
741                last_exports[i.0] -= *i.1;
742            }
743            // remember price
744            if let Some(n) = self.neighbors.iter_mut().find(|n| n.id == d.supplier) {
745                // remember (and consume) last values
746                std::mem::swap(&mut n.last_values, &mut d.prices);
747                std::mem::swap(&mut n.last_supplies, &mut d.supply);
748                // add items to stock
749                for (g, a) in d.amount.iter() {
750                    if *a < 0.0 {
751                        // likely rounding error, ignore
752                        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        //self.active_exports.clear();
765    }
766
767    /// Simulate one step of economic interaction:
768    /// - collect returned goods from trade
769    /// - calculate demand, production and their ratio
770    /// - reassign workers based on missing goods
771    /// - change stock due to raw material use and production
772    /// - send out traders with goods and orders
773    /// - calculate good decay and population change
774    ///
775    /// Simulate a site's economy. This simulation is roughly equivalent to the
776    /// Lange-Lerner model's solution to the socialist calculation problem. The
777    /// simulation begins by assigning arbitrary values to each commodity and
778    /// then incrementally updates them according to the final scarcity of
779    /// the commodity at the end of the tick. This results in the
780    /// formulation of values that are roughly analogous to prices for each
781    /// commodity. The workforce is then reassigned according to the
782    /// respective commodity values. The simulation also includes damping
783    /// terms that prevent cyclical inconsistencies in value rationalisation
784    /// magnifying enough to crash the economy. We also ensure that
785    /// a small number of workers are allocated to every industry (even inactive
786    /// ones) each tick. This is not an accident: a small amount of productive
787    /// capacity in one industry allows the economy to quickly pivot to a
788    /// different production configuration should an additional commodity
789    /// that acts as production input become available. This means that the
790    /// economy will dynamically react to environmental changes. If a
791    /// product becomes available through a mechanism such as trade, an
792    /// entire arm of the economy may materialise to take advantage of this.
793    pub fn tick(&mut self, site_id: Id<Site>, dt: f32) {
794        // collect goods from trading
795        if INTER_SITE_TRADE {
796            self.collect_deliveries();
797        }
798
799        let orders = self.get_orders();
800        let production = self.get_production();
801
802        // for i in production.iter() {
803        //     vc.context("production")
804        //         .value(&std::format!("{:?}{:?}", i.0, Good::from(i.1.0)), i.1.1);
805        // }
806
807        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; // if we spend coin value increases
819        }
820
821        // which labor is the merchant
822        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; //GoodMap::from_default(0.0);
829        for (labor, goodvec) in production.iter() {
830            //for (output_good, _) in goodvec.iter() {
831            //info!("{} supply{:?}+={}", site_id.id(), Good::from(goodvec.0),
832            // self.yields[labor] * self.labors[labor] * self.pop);
833            supply[goodvec.0] += self.yields[labor] * self.labors[labor] * self.pop;
834            // vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0),
835            // labor))     .value("yields", self.yields[labor]);
836            // vc.context(&std::format!("{:?}-{:?}", Good::from(goodvec.0),
837            // labor))     .value("labors", self.labors[labor]);
838            //}
839        }
840
841        // for i in supply.iter() {
842        //     vc.context("supply")
843        //         .value(&std::format!("{:?}", Good::from(i.0)), *i.1);
844        // }
845
846        let stocks = &self.stocks;
847        // for i in stocks.iter() {
848        //     vc.context("stocks")
849        //         .value(&std::format!("{:?}", Good::from(i.0)), *i.1);
850        // }
851        self.surplus = demand.map(|g, demand| supply[g] + stocks[g] - demand);
852        self.marginal_surplus = demand.map(|g, demand| supply[g] - demand);
853
854        // plan trading with other sites
855        // let external_orders = &mut index.trade.orders;
856        let mut potential_trade = GoodMap::from_default(0.0);
857        // use last year's generated transportation for merchants (could we do better?
858        // this is in line with the other professions)
859        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); // TODO: check for availability?
864
865            // add the wares to sell to demand and the goods to buy to supply
866            for (g, a) in trade.iter() {
867                // vc.context("trade")
868                //     .value(&std::format!("{:?}", Good::from(g)), *a);
869                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        // Update values according to the surplus of each stock
883        // Note that values are used for workforce allocation and are not the same thing
884        // as price
885        // fall back to old (less wrong than other goods) coin logic
886        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            // Value rationalisation
896            // let goodname = std::format!("{:?}", Good::from(good));
897            // vc.context("old_surplus").value(&goodname, old_surplus);
898            // vc.context("demand").value(&goodname, demand[good]);
899            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                    // vc.context("values").value(
904                    // &goodname,
905                    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 empty_goods: DHashSet<GoodIndex> = DHashSet::default();
919        // TODO: Does avg/max/sum make most sense for labors creating more than one good
920        // summing favors merchants too much (as they will provide multiple
921        // goods, so we use max instead)
922        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        //let mut labor_context = vc.context("labor");
945        production.iter().for_each(|(labor, _)| {
946            let smooth = 0.8;
947            self.labors[labor] =
948            // labor_context.value(
949            //     &format!("{:?}", labor),
950                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        // Production
957        let stocks_before = self.stocks;
958        // TODO: Should we recalculate demand after labor reassignment?
959
960        let direct_use = direct_use_goods();
961        // Handle the stocks you can't pile (decay)
962        for g in direct_use {
963            self.stocks[*g] = 0.0;
964        }
965
966        let mut total_labor_values = GoodMap::<f32>::default();
967        // TODO: trade
968        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            // For each order, we try to find the minimum satisfaction rate - this limits
975            // how much we can produce! For example, if we need 0.25 fish and
976            // 0.75 oats to make 1 unit of food, but only 0.5 units of oats are
977            // available then we only need to consume 2/3rds
978            // of other ingredients and leave the rest in stock
979            // In effect, this is the productivity
980            let (labor_productivity, limited_by) = orders
981                .iter()
982                .map(|(good, amount)| {
983                    // What quantity is this order requesting?
984                    let _quantity = *amount * workers;
985                    assert!(stocks_before[*good] >= 0.0);
986                    assert!(demand[*good] >= 0.0);
987                    // What proportion of this order is the economy able to satisfy?
988                    ((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                // What quantity is this order requesting?
1004                let quantity = *amount * workers;
1005                // What amount gets actually used in production?
1006                let used = quantity * labor_productivity;
1007
1008                // Material cost of each factor of production
1009                total_materials_cost += used * self.labor_values[*good].unwrap_or(0.0);
1010
1011                // Deplete stocks accordingly
1012                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                // TODO: replan for missing merchant productivity???
1019                for (g, a) in trade.iter() {
1020                    if !direct_use.contains(&g) {
1021                        if *a < 0.0 {
1022                            // take these goods to the road
1023                            if self.stocks[g] + *a < 0.0 {
1024                                // we have a problem: Probably due to a shift in productivity we
1025                                // have less goods available than
1026                                // planned, so we would need to
1027                                // reduce the amount shipped
1028                                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                            //                    assert!(self.stocks[g] + *a >= 0.0);
1050                            else {
1051                                self.stocks[g] += *a;
1052                            }
1053                        }
1054                        total_materials_cost += (-*a) * self.labor_values[g].unwrap_or(0.0);
1055                    } else {
1056                        // count on receiving these
1057                        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            // Industries produce things
1071            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                // Materials cost per unit
1084                // TODO: How to handle this reasonably for multiple producers (collect upper and
1085                // lower term separately)
1086                self.material_costs[stock] = total_materials_cost / amount.max(0.001) * cost_weight;
1087                // Labor costs
1088                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        // consume goods needed by everyone
1097        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            //info!("Ev {:.1} {:?} {} - {:.1} {:.1}", self.pop, good,
1102            // self.stocks[good], needed, available);
1103        }
1104
1105        // Update labour values per unit
1106        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        // Decay stocks (the ones which totally decay are handled later)
1116        self.stocks
1117            .iter_mut()
1118            .map(|(c, v)| (v, 1.0 - decay_rate(c)))
1119            .for_each(|(v, factor)| *v *= factor);
1120
1121        // Decay stocks
1122        self.replenish(dt);
1123
1124        // Births/deaths
1125        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 += //vc.value(
1134            //"pop",
1135            dt / DAYS_PER_YEAR * self.pop * (birth_rate - DEATH_RATE);
1136        //);
1137        self.population_limited_by = if population_growth {
1138            GoodIndex::default()
1139        } else {
1140            *FOOD_INDEX
1141        };
1142
1143        // calculate the new unclaimed stock
1144        //let next_orders = self.get_orders();
1145        // orders are static
1146        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        //let mut us = vc.context("unconsumed");
1159        self.unconsumed_stock = GoodMap::from_iter(
1160            self.stocks.iter().map(|(g, a)| {
1161                (
1162                    g,
1163                    //us.value(&format!("{:?}", Good::from(g)),
1164                    *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
1390/** you can't accumulate or save these options/resources for later */
1391fn 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        // avoid economy of scale
1400        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}