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