chiark / gitweb /
routetrade: Do not instantiate useless island pairs
[ypp-sc-tools.main.git] / yarrg / rsvalue.c
1 /**/
2
3 #include <glpk.h>
4
5 #include "rscommon.h"
6
7 DEBUG_DEFINE_DEBUGF(value);
8
9 typedef struct { int mass, volu; } CommodInfo;
10 static int commodstabsz;
11 static CommodInfo *commodstab;
12
13 static sqlite3_stmt *ss_ipair_dist;
14 static sqlite3_stmt *ss_ite_buy, *ss_ite_sell;
15
16 #define MAX_LEGS (MAX_ROUTELEN-1)
17
18 typedef struct {
19   int commodid, src_price, dst_price;
20 } Trade;
21
22 #define TRADES_PER_BLOCK 10
23
24 typedef struct TradesBlock {
25   struct TradesBlock *next;
26   int ntrades;
27   Trade t[TRADES_PER_BLOCK];
28 } TradesBlock;
29
30 static IslandPair ***ipairs; /* ipairs[sislandid][dislandid] */
31
32 typedef struct IslandTradeEnd {
33   struct IslandTradeEnd *next;
34   /* key: */
35   int commodid, price;
36   /* values: */
37   int qty;
38   unsigned long generation;
39   int rownum;
40 } IslandTradeEnd;
41
42 typedef struct {
43   IslandTradeEnd *src, *dst;
44 } IslandTradeEndHeads;
45
46 IslandTradeEndHeads *itradeends;
47   /* itradeends[islandid].{src,dst}->commodid etc. */
48
49 static LPX *lp;
50 static unsigned long generation;
51
52 static int nconstraint_rows;
53 static int constraint_rows[1+2+3*MAX_LEGS];
54 static double constraint_coeffs[1+2+3*MAX_LEGS];
55       /* dummy0, src, dst, for_each_leg( [mass], [volume], [capital] ) */
56
57 static void add_constraint(int row, double coefficient) {
58   nconstraint_rows++; /* glpk indices start from 1 !!! */
59   constraint_rows  [nconstraint_rows]= row;
60   constraint_coeffs[nconstraint_rows]= coefficient;
61 }
62
63 static void avail_c(const Trade *t, IslandTradeEnd **trades,
64                     int price, const char *srcdst,
65                     int islandid, sqlite3_stmt *ss_ite) {
66   /* find row number of trade availability constraint */
67   IslandTradeEnd *search;
68
69   for (search= *trades; search; search=search->next)
70     if (search->commodid==t->commodid && search->price==price)
71       goto found;
72   abort();
73   
74  found:;
75   if (search->generation != generation) {
76     search->rownum= lpx_add_rows(lp, 1);
77     lpx_set_row_bnds(lp, search->rownum, LPX_UP, 0, search->qty);
78
79     if (DEBUGP(value) || DEBUGP(check)) {
80       char *name= masprintf("%s_i%d_c%d_%d_all",
81                             srcdst, islandid, t->commodid, price);
82       lpx_set_row_name(lp,search->rownum,name);
83
84       if (DEBUGP(check)) {
85         int nrows= lpx_get_num_rows(lp);
86         assert(search->rownum == nrows);
87         int i;
88         for (i=1; i<nrows; i++)
89           assert(strcmp(name, lpx_get_row_name(lp,i)));
90       }
91       free(name);
92     }
93     search->generation= generation;
94   }
95
96   add_constraint(search->rownum, 1.0);
97 }
98
99 static int setup_leg_constraints(double max_thing, int legs, const char *wh) {
100   int leg, startrow;
101   if (max_thing < 0 || !legs) return -1;
102   startrow= lpx_add_rows(lp, legs);
103   for (leg=0; leg<legs; leg++) {
104     int row= leg+startrow;
105     lpx_set_row_bnds(lp, row, LPX_UP, 0, max_thing);
106     if (DEBUGP(value)) {
107       char *name= masprintf("%s_%d",wh,leg);
108       lpx_set_row_name(lp,row,name);
109       free(name);
110     }
111   }
112   return startrow;
113 }
114
115 static void add_leg_c(int startrow, int leg, double value) {
116   if (startrow<=0) return;
117   assert(value > 0);
118   add_constraint(startrow+leg, value);
119 }
120
121 IslandPair *ipair_get_maybe(int si, int di) {
122   IslandPair **ipa;
123
124   assert(si < islandtablesz);
125   assert(di < islandtablesz);
126
127   if (!(ipa= ipairs[si])) return 0;
128   return ipa[di];
129 }
130
131 static IslandPair *ipair_get_create(int si, int di) {
132   IslandPair *ip, **ipa;
133
134   assert(si < islandtablesz);
135   assert(di < islandtablesz);
136
137   if (!(ipa= ipairs[si])) {
138     ipa= ipairs[si]= mcalloc(sizeof(*ipa) * islandtablesz);
139   }
140   if ((ip= ipa[di]))
141     return ip;
142
143   ipa[di]= ip= mmalloc(sizeof(*ip));
144   ip->trades= 0;
145   ip->route_tail_value= -1;
146
147   debugf("VALUE ipair_get(i%d,i%d) running...\n", si,di);
148   SQL_MUST( sqlite3_bind_int(ss_ipair_dist, 1, si) );
149   SQL_MUST( sqlite3_bind_int(ss_ipair_dist, 2, di) );
150   assert(SQL_STEP(ss_ipair_dist));
151   int dist= sqlite3_column_int(ss_ipair_dist, 0);
152   ip->distance_loss_factor= pow(distance_loss_factor_per_league, dist);
153   sqlite3_reset(ss_ipair_dist);
154   
155   return ip;
156 }
157
158 double value_route(int nislands, const int *islands, int exclude_arbitrage) {
159   int s,d;
160
161   /* We need to construct the LP problem.  GLPK talks
162    * about rows and columns, which are numbered from 1.
163    *
164    * Each column is a `structural variable' ie one of the entries in
165    * the objective function.  In our case the set of structural
166    * variable is, for each port, the set of Trades which collect at
167    * that island.  (We use `port' to mean `specific visit to an
168    * island' so if an island appears more than once so do its trades.)
169    * We don't need to worry about crossing with all the possible
170    * delivery locations as we always deliver on the first port.
171    * We will call such a structural variable a Flow, for brevity.
172    *
173    * We iterate over the possible Flows adding them as columns as we
174    * go, and also adding their entries to the various constraints.
175    *
176    * Each row is an `auxiliary variable' ie one of the constraints.
177    * We have two kinds of constraint:
178    *   - mass/volume/capital: one constraint for each sailed leg
179    *       (unless relevant constraint is not satisfied)
180    *   - quantity of commodity available for collection
181    *       or delivery at particular price and island
182    * The former are numbered predictably: we have first all the mass
183    * limits, then all the volume limits, then all the capital limits
184    * (as applicable) - one for each leg, ie one for each entry
185    * in islands except the first.
186    *
187    * The latter are added as needed and the row numbers are stored in
188    * a data structure for later reuse.
189    */
190
191   assert(nislands >= 1);
192   assert(++generation);
193
194   assert(!lp);
195   lp= lpx_create_prob();
196   lpx_set_obj_dir(lp, LPX_MAX);
197   lpx_set_int_parm(lp, LPX_K_MSGLEV, DEBUGP(lp) ? 3 : 1);
198
199   if (DEBUGP(value)) {
200     lpx_set_prob_name(lp,(char*)"value_route");
201     lpx_set_obj_name(lp,(char*)"profit");
202   }
203
204   int legs= nislands-1;
205   int mass_constraints= setup_leg_constraints(max_mass, legs, "mass");
206   int volu_constraints= setup_leg_constraints(max_volu, legs, "volu");
207   int capi_constraints= setup_leg_constraints(max_capi, legs, "capi");
208
209   double delay_slot_loss_factor= 1.0;
210   for (s=0;
211        s<nislands;
212        s++, delay_slot_loss_factor *= LOSS_FACTOR_PER_DELAY_SLOT) {
213     int si= islands[s];
214     
215     for (d= s + exclude_arbitrage;
216          d < nislands;
217          d++) {
218       int di= islands[d];
219       int already_d;
220       for (already_d=s+1; already_d<d; already_d++)
221         if (islands[already_d] == di)
222           /* visited this island already since we left s, uninteresting */
223           goto next_d;
224
225       if (d>s && di==si)
226         /* route has returned to si, no need to think more about s */
227         goto next_s;
228
229       /*----- actually add these trades to the LP problem -----*/
230       
231       IslandPair *ip= ipair_get_maybe(islands[s], islands[d]);
232
233       if (!ip || !ip->trades)
234         goto next_d;
235
236       double loss_factor= delay_slot_loss_factor * ip->distance_loss_factor;
237       debugf(" SOME   i%d#%d..i%d#%d  dslf=%g dlf=%g  lf=%g\n",
238              si,s, di,d,
239              delay_slot_loss_factor, ip->distance_loss_factor, loss_factor);
240
241       TradesBlock *block;
242       for (block=ip->trades; block; block=block->next) {
243         int inblock;
244         for (inblock=0; inblock<block->ntrades; inblock++) {
245           Trade *t= &block->t[inblock];
246
247           debugf("  TRADE i%d#%d..i%d#%d c%d %d-%d  ",
248                  si,s, di,d, t->commodid, t->src_price, t->dst_price);
249
250           nconstraint_rows=0;
251
252           avail_c(t, &itradeends[si].src, t->src_price, "src", si,ss_ite_sell);
253           avail_c(t, &itradeends[di].dst, t->dst_price, "dst", di,ss_ite_buy);
254
255           int leg;
256           for (leg=s; leg<d; leg++) {
257             add_leg_c(mass_constraints,leg, commodstab[t->commodid].mass*1e-3);
258             add_leg_c(volu_constraints,leg, commodstab[t->commodid].volu*1e-3);
259             add_leg_c(capi_constraints,leg, t->src_price);
260           }
261
262           double unit_profit= t->dst_price * loss_factor - t->src_price;
263           debugf("    unit profit %f\n", unit_profit);
264           if (unit_profit <= 0) continue;
265
266           int col= lpx_add_cols(lp,1);
267           lpx_set_col_bnds(lp, col, LPX_LO, 0, 0);
268           lpx_set_obj_coef(lp, col, unit_profit);
269           lpx_set_mat_col(lp, col, nconstraint_rows,
270                           constraint_rows, constraint_coeffs);
271
272           if (DEBUGP(value)) {
273             char *name= masprintf("c%d_p%d_%d_p%d_%d", t->commodid,
274                                   s, t->src_price, d, t->dst_price);
275             lpx_set_col_name(lp, col, name);
276             free(name);
277           }
278         } /* inblock */
279       } /* block */
280       
281       /*----- that's done adding these trades to the LP problem -----*/
282       
283     next_d:;
284     } /* for (d) */
285   next_s:;
286   } /* for (s) */
287
288   double profit= 0;
289
290   if (lpx_get_num_cols(lp)) {
291     if (DEBUGP(lp))
292       lpx_write_cpxlp(lp, (char*)DEBUG_DEV);
293
294     int ipr= lpx_simplex(lp);
295     assert(ipr==LPX_E_OK);
296
297     if (DEBUGP(lp))
298       lpx_print_sol(lp, (char*)DEBUG_DEV);
299
300     int lpst= lpx_get_status(lp);
301     assert(lpst == LPX_OPT);
302     profit= lpx_get_obj_val(lp);
303   }
304
305   lpx_delete_prob(lp);
306   lp= 0;
307
308   return profit;
309 }
310
311 #define TRADE_FROM                                                      \
312     "  FROM sell, buy\n"                                                \
313     "  WHERE sell.commodid=buy.commodid AND sell.price < buy.price\n"
314               
315 static void read_trades(void) {
316   /* We would like to use DISTINCT but sqlite3 is too stupid
317    * to notice that it could use the index to do the DISTINCT
318    * which makes it rather slow. */
319   sqlite3_stmt *ss_trades;
320
321 #define TRADE_COLS \
322     "sell.commodid, sell.islandid, sell.price, buy.islandid, buy.price"
323   SQL_PREPARE(ss_trades,
324               " SELECT " TRADE_COLS "\n"
325               TRADE_FROM
326               "  ORDER BY " TRADE_COLS);
327
328   SQL_DISTINCT_DECL(cols,5);
329   while (SQL_DISTINCT_STEP(ss_trades,cols,5)) {    
330     IslandPair *ip= ipair_get_create(cols[1], cols[3]);
331     TradesBlock *block= ip->trades;
332     if (!block || ip->trades->ntrades >= TRADES_PER_BLOCK) {
333       block= mmalloc(sizeof(*block));
334       block->next= ip->trades;
335       ip->trades= block;
336       block->ntrades= 0;
337     }
338     Trade *trade= &block->t[block->ntrades];
339     trade->commodid=  cols[0];
340     trade->src_price= cols[2];
341     trade->dst_price= cols[4];
342     block->ntrades++;
343   }
344   sqlite3_finalize(ss_trades);
345 }
346
347 static void read_islandtradeends(const char *bs, int srcdstoff) {
348
349 #define TRADEEND_KEYCOLS "%s.commodid, %s.islandid, %s.stallid"
350   char *stmt= masprintf(" SELECT " TRADEEND_KEYCOLS ", %s.price, %s.qty\n"
351                         TRADE_FROM
352                         "  ORDER BY "  TRADEEND_KEYCOLS,
353                         bs,bs,bs,bs,bs, bs,bs,bs);
354   char *stmt_id= masprintf("qtys (%s)",bs);
355   sqlite3_stmt *ss= sql_prepare(stmt, stmt_id);
356   free(stmt); free(stmt_id);
357
358   SQL_DISTINCT_DECL(cols,5);
359   while (SQL_DISTINCT_STEP(ss,cols,3)) {
360     IslandTradeEnd *search;
361
362     int commodid= cols[0];
363     int islandid= cols[1];
364     int price= cols[3];
365     int qty= cols[4];
366
367     IslandTradeEnd **trades= (void*)((char*)&itradeends[islandid] + srcdstoff);
368
369     for (search= *trades; search; search=search->next)
370       if (search->commodid==commodid && search->price==price)
371         goto found;
372     /* not found, add new end */
373
374     search= mmalloc(sizeof(*search));
375     search->commodid= commodid;
376     search->price= price;
377     search->next= *trades;
378     search->generation= 0;
379     search->qty= 0;
380     *trades= search;
381
382   found:
383     search->qty += qty;
384   }
385   sqlite3_finalize(ss);
386 }
387
388 void setup_value(void) {
389   sqlite3_stmt *sst;
390   int i;
391
392   commodstabsz= sql_single_int("SELECT max(commodid) FROM commods") + 1;
393   commodstab= mmalloc(sizeof(*commodstab)*commodstabsz);
394   for (i=0; i<commodstabsz; i++)
395     commodstab[i].mass= commodstab[i].volu= -1;
396
397   SQL_PREPARE(sst,
398               "SELECT commodid,unitmass,unitvolume FROM commods");
399   while (SQL_STEP(sst)) {
400     int id= sqlite3_column_int(sst,0);
401     assert(id>=0 && id<commodstabsz);
402     commodstab[id].mass= sqlite3_column_int(sst,1);
403     commodstab[id].volu= sqlite3_column_int(sst,2);
404   }
405   sqlite3_finalize(sst);
406
407   ipairs= mcalloc(sizeof(*ipairs) * islandtablesz);
408   itradeends= mcalloc(sizeof(*itradeends) * islandtablesz);
409
410   SQL_PREPARE(ss_ipair_dist,
411               " SELECT dist FROM dists\n"
412               "  WHERE aiid=? and biid=?");
413
414   read_trades();
415   read_islandtradeends("sell", offsetof(IslandTradeEndHeads, src));
416   read_islandtradeends("buy",  offsetof(IslandTradeEndHeads, dst));
417 }