The ROme OpTimistic Simulator  2.0.0
A General-Purpose Multithreaded Parallel/Distributed Simulation Platform
costs.c
1 /*
2  * topology_costs.c
3  *
4  * Created on: 02 ago 2018
5  * Author: andrea
6  */
7 
8 #include <ROOT-Sim.h>
9 #include <lib/topology.h>
10 
11 #include <math.h>
12 #include <stdint.h>
13 
14 #include <scheduler/scheduler.h>
15 #include <lib/jsmn_helper.h>
16 #include <lib/numerical.h>
17 #include <datatypes/bitmap.h>
18 #include <datatypes/array.h>
19 #include <datatypes/heap.h>
20 #include <scheduler/process.h>
21 
22 typedef struct _topology_t {
23  bool dirty;
24  unsigned *prev_next_cache;
25  double data[];
26 } topology_t;
27 
28 unsigned size_checkpoint_costs(void){
29  return sizeof(topology_t) + // the basic struct size
30  sizeof(double) * topology_global.directions * topology_global.lp_cnt + // the whole cost matrix
31  sizeof(double) * topology_global.lp_cnt + // the cache of total path costs to speed up queries
32  sizeof(unsigned) * 2 * topology_global.lp_cnt; // the cache of previous and next hops to speed up queries
33 }
34 
35 void *load_topology_file_costs(c_jsmntok_t *root_token, const char *json_base){
36  unsigned i;
37  c_jsmntok_t *aux_tok;
38  const unsigned lp_cnt = topology_global.lp_cnt;
39  const unsigned neighbours_cnt = topology_global.directions;
40 
41  // retrieve the values array
42  c_jsmntok_t *values_tok = get_value_token_by_key(root_token, json_base, root_token, "values");
43  if(!values_tok|| values_tok->type != JSMN_ARRAY || children_count_token(root_token, values_tok) != lp_cnt)
44  rootsim_error(false, "Invalid or missing json value with key \"values\"");
45 
46  // instantiates the array
47  double *ret_data = rsalloc(sizeof(double) * neighbours_cnt * lp_cnt);
48 
49  // we iterate and store the costs of going into a neighbour
50  for (i = 0; i < lp_cnt; ++i) {
51  // get the token of the lp we are scanning
52  aux_tok = get_at_token(root_token, values_tok, i);
53 
54  // we parse the array
55  if(!aux_tok || parse_double_array(root_token, json_base, aux_tok, neighbours_cnt, &ret_data[i * neighbours_cnt]) < 0)
56  rootsim_error(false, "Invalid or missing value in the current array of costs");
57  }
58 
59  // sanity check on the values of the costs
60  // XXX could negative costs be useful to someone?
61  // they would require non-minimal work to implement
62  // Bellman-Ford would be required
63  for (i = 0; i < neighbours_cnt * lp_cnt; ++i) {
64  if(ret_data[i] < 0) {
65  // xxx if negative costs need to be implemented we can't do this
66  if(ret_data[i] > -1.0 || ret_data[i] < -1.0)
67  rootsim_error(true, "Negative costs are still not supported");
68 
69  ret_data[i] = INFINITY; // this way we can mark an edge as non crossable
70  }
71  }
72  return ret_data;
73 }
74 
75 
76 topology_t *topology_costs_init(unsigned this_region_id, void *topology_data){
77  (void) this_region_id;
78  unsigned i;
79  const unsigned lp_cnt = topology_global.lp_cnt;
80  const unsigned neighbours = topology_global.directions;
81 
82  // instantiate the topology struct
83  topology_t *topology = rsalloc(topology_global.chkp_size);
84 
85  // from now on we expect a topology based on cost to reside in a unique memory block and to have this layout in memory:
86  // BASE_STRUCT | COST MATRIX | CACHE MINIMUM COSTS | CACHE PREVIOUS HOP | CACHE NEXT HOP
87  topology->prev_next_cache = UNION_CAST(&topology->data[(neighbours + 1) * lp_cnt], unsigned *);
88 
89  if(topology_data)
90  memcpy(topology->data, topology_data, sizeof(double) * neighbours * lp_cnt);
91  else{
92  for(i = 0; i < neighbours * lp_cnt; ++i)
93  topology->data[i] = 1.0;
94  }
95  topology->dirty = true;
96 
97  return topology;
98 }
99 
100 // this is used in order to sort the elements of the heap
101 #define __cmp_dijkstra_h(a, b) CmpSumHelpers(a.cost, b.cost)
102 // this is costly: we try as much as possible to cache the results of this function
103 static void dijkstra_costs(const topology_t *topology, unsigned int source_cell, double min_costs[RegionsCount()], unsigned int previous[RegionsCount()]) {
104  // helper structure, we use this as heap elements to keep track of vertexes status during dijkstra execution
105  struct _dijkstra_h_t{
106  struct _sum_helper_t cost;
107  unsigned cell;
108  };
109 
110  const unsigned neighbours = topology_global.directions;
111  const unsigned lp_cnt = topology_global.lp_cnt;
112  unsigned i, receiver;
113  struct _sum_helper_t min_costs_h[lp_cnt];
114  rootsim_heap(struct _dijkstra_h_t) heap;
115  const double *costs = topology->data;
116 
117  struct _dijkstra_h_t current_scan = {{0, 0}, source_cell}, partial_scan = {0};
118 
119  i = lp_cnt;
120  while(i--){
121  min_costs_h[i].crt = 0;
122  min_costs_h[i].sum = INFINITY;
123  previous[i] = UINT_MAX;
124  }
125 
126  heap_init(heap);
127 
128  min_costs_h[source_cell] = current_scan.cost;
129  // textbook dijkstra (keep in mind i'm not passing pointers, this stuff gets copied)
130  heap_insert(heap, current_scan, __cmp_dijkstra_h);
131  // while we have vertexes to process
132  while(!heap_empty(heap)) {
133  // extract the lowest one
134  current_scan = heap_extract(heap, __cmp_dijkstra_h);
135  // since we are not supporting decrease key on the heap we have to filter spurious duplicates
136  if(CmpSumHelpers(current_scan.cost, min_costs_h[current_scan.cell]) > 0)
137  continue;
138  // we cycle through the neighbours of the current cell
139  for(i = 0; i < neighbours; ++i){
140  // we get the receiver cell
141  receiver = get_raw_receiver(current_scan.cell, i);
142  if(receiver == DIRECTION_INVALID || isinf(costs[current_scan.cell * neighbours + i]))
143  continue;
144  // we compute the sum of the current distance plus one hop to the receiver
145  partial_scan.cost = PartialNeumaierSum(current_scan.cost, costs[current_scan.cell * neighbours + i]);
146  // if lower we have a candidate optimum for the cell
147  if(CmpSumHelpers(partial_scan.cost, min_costs_h[receiver]) < 0){
148  // set the previous cell to retrieve the path later on
149  previous[receiver] = current_scan.cell;
150  // we set the cell field on our struct
151  partial_scan.cell = receiver;
152  // refresh lowest cost found for the cell
153  min_costs_h[receiver] = partial_scan.cost;
154  // we insert this into the heap
155  heap_insert(heap, partial_scan, __cmp_dijkstra_h);
156  }
157  }
158  }
159  // we transform the sum helpers into single double value
160  i = lp_cnt;
161  while(i--){
162  min_costs[i] = ValueSumHelper(min_costs_h[i]);
163  }
164 }
165 #undef __cmp_dijsktra_h
166 
167 static void refresh_cache_costs(topology_t *topology){
168  if(topology->dirty){
169  const unsigned lp_cnt = topology_global.lp_cnt;
170  // computes minimum cost spanning tree rooted in the current region
171  dijkstra_costs(topology, current->gid.to_int, &topology->data[topology_global.directions*lp_cnt], topology->prev_next_cache);
172  // this sets to an uninitialized value the buffer which holds the next hop for
173  // each possible destination (we compute those on demand when asked by the user and we cache those here)
174  memset(&topology->prev_next_cache[lp_cnt], UCHAR_MAX, sizeof(unsigned) * lp_cnt);
175  topology->dirty = false;
176  }
177 }
178 
180  uint64_t val_bits;
181  double val;
182 };
183 
185  long unsigned loc_i;
187 };
188 
189 void set_value_topology_costs(unsigned from, unsigned to, double value){
190  topology_t *topology = current->topology;
191  struct update_topology_t to_send = {from * topology_global.directions + to, .upd = {value}};
192  // this makes sure our bitwise tricks work properly
193  static_assert(sizeof(double) == sizeof(uint64_t), "the bit operation trick on TOPOLOGY_COST updates is wrong");
194  union _double_bits_trick old = {topology->data[to_send.loc_i]};
195  // the XOR is needed in order to have a consistent state after contemporaneous update events
196  to_send.upd.val_bits ^= old.val_bits;
197 
198  if(unlikely(to_send.upd.val_bits == 0))
199  // the update is unnecessary
200  return;
201  // save the value locally
202  topology->data[to_send.loc_i] = value;
203  // send the update message to the other LPs
204  unsigned i = topology_global.lp_cnt;
205  while(i--){
206  if(i == current->gid.to_int)
207  continue;
208  UncheckedScheduleNewEvent(i, current_evt->timestamp, TOPOLOGY_UPDATE, &to_send, sizeof(to_send));
209  }
210  // mark the topology as dirty
211  topology->dirty = true;
212 }
213 
214 void update_topology_costs(void){
215  topology_t *topology = current->topology;
216  struct update_topology_t *upd_p = (struct update_topology_t *)current_evt->event_content;
217  // same ol' trick as set_value_topology_costs()
218  union _double_bits_trick *old_p = (union _double_bits_trick *)&topology->data[upd_p->loc_i];
219  // update our value through the XOR
220  old_p->val_bits ^= upd_p->upd.val_bits;
221  // mark the topology as dirty
222  topology->dirty = true;
223 }
224 
225 double get_value_topology_costs(unsigned from, unsigned to){
226  return current->topology->data[from * topology_global.directions + to];
227 }
228 
229 bool is_reachable_costs(unsigned to){
230  // XXX do we want deep reachability or dumb reachability?
231  return find_receiver_toward_costs(to) != UINT_MAX;
232 }
233 
234 unsigned int find_receiver_toward_costs(unsigned int to){
235  topology_t *topology = current->topology;
236  const unsigned lp_cnt = topology_global.lp_cnt;
237  const unsigned this_lp = current->gid.to_int;
238  // refresh the cache
239  refresh_cache_costs(topology);
240  // this is the location where we expect to find the cached next hop
241  unsigned *ret_p = &topology->prev_next_cache[lp_cnt + to];
242 
243  if(*ret_p == UINT_MAX){
244  // this value hasn't been requested yet
245  unsigned path[lp_cnt];
246  // we compute the complete path
247  if(!build_path(lp_cnt, path, topology->prev_next_cache, this_lp, to)){
248  // there's no path so we put in a sentinel value
249  *ret_p = this_lp;
250  }else{
251  // we got a feasible path, we save the first hop
252  *ret_p = path[0];
253  }
254  }
255  // we mark the unreachable regions with next hop = current LP id
256  if(*ret_p == this_lp){
257  // the requested cell is unreachable
258  return UINT_MAX;
259  }
260 
261  return *ret_p;
262 }
263 
264 double compute_min_tour_costs(unsigned int source, unsigned int dest, unsigned int result[RegionsCount()]) {
265  topology_t *topology = current->topology;
266  const unsigned lp_cnt = topology_global.lp_cnt;
267 
268  // I suppose (I HOPE!!!) the requests will be more frequent for paths starting from the region where we are staying
269  if(source == current->gid.to_int){
270  refresh_cache_costs(topology); // so we cache that stuff
271  // the path is built on the fly (but this is almost as costly as copying it directly)
272  if(!build_path(lp_cnt, result, topology->prev_next_cache, source, dest)){
273  // the destination is unreachable
274  // we mark the cache a sentinel value indicating impossibility
275  topology->prev_next_cache[lp_cnt + dest] = source;
276  return -1.0;
277  }
278  topology->prev_next_cache[lp_cnt + dest] = result[0]; // we cache the next hop value;
279  // this is the cached value for the minimum cost incurred in the path
280  return topology->data[topology_global.directions * lp_cnt + dest];
281  }
282 
283  unsigned int previous[lp_cnt];
284  double min_costs[lp_cnt];
285  // this is not cahced, we need to execute the whole algorithm, again only for this request
286  dijkstra_costs(topology, source, min_costs, previous);
287  // we build the path
288  if(!build_path(lp_cnt, result, previous, source, dest))
289  return -1.0;
290 
291  return min_costs[dest];
292 }
293 
Definition: jsmn.h:43
A generic invalid direction.
Definition: ROOT-Sim.h:122
ROOT-Sim header for model development.
#define UNION_CAST(x, destType)
Macro to "legitimately" pun a type.
Definition: core.h:121
unsigned chkp_size
Definition: topology.h:18
struct _sum_helper_t PartialNeumaierSum(struct _sum_helper_t sh, double addendum)
Definition: numerical.c:463
unsigned int to_int
The GID numerical value.
Definition: core.h:133
unsigned lp_cnt
Definition: topology.h:21
The ROOT-Sim scheduler main module header.
This is the implementation of a dynamic array, used for managing various data structures.
__thread struct lp_struct * current
This is a per-thread variable pointing to the block state of the LP currently scheduled.
Definition: scheduler.c:72
this represents a partial Neumaier sum
Definition: numerical.h:55
double data[]
a pointer to the cache used to speedup queries on paths (unused in probabilities topology type) ...
Definition: costs.c:25
Numerical Library.
double val
needed by the bitwise XOR
Definition: costs.c:181
__thread msg_t * current_evt
Definition: scheduler.c:83
topology_t * topology
pointer to the topology struct
Definition: process.h:152
unsigned directions
Definition: topology.h:19
LP control blocks.
union _double_bits_trick upd
where to put the new value
Definition: costs.c:186
GID_t gid
Global ID of the LP.
Definition: process.h:82
bool dirty
Definition: costs.c:23
Bitmap data type.
unsigned * prev_next_cache
Definition: costs.c:24
#define unlikely(exp)
Optimize the branch as likely not taken.
Definition: core.h:74
the customised struct for TOPOLOGY_OBSTACLES representation
Definition: costs.c:22