xerus
a general purpose tensor library
contractionHeuristic.cpp
Go to the documentation of this file.
1 // Xerus - A General Purpose Tensor Library
2 // Copyright (C) 2014-2017 Benjamin Huber and Sebastian Wolf.
3 //
4 // Xerus is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as published
6 // by the Free Software Foundation, either version 3 of the License,
7 // or (at your option) any later version.
8 //
9 // Xerus is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
13 //
14 // You should have received a copy of the GNU Affero General Public License
15 // along with Xerus. If not, see <http://www.gnu.org/licenses/>.
16 //
17 // For further information on Xerus visit https://libXerus.org
18 // or contact us at contact@libXerus.org.
19 
25 #include <xerus/misc/check.h>
26 
28 #include <xerus/tensorNetwork.h>
29 #include <xerus/misc/internal.h>
30 
31 namespace xerus {
32  namespace internal {
33 
34  template<double (*scoreFct)(double, double, double, double, double)>
35  void greedy_heuristic(double &_bestCost, std::vector<std::pair<size_t,size_t>> &_contractions, TensorNetwork _network) {
36  // estimated cost to calculate this heuristic is
37  // numNodes * numNodes * 2*avgEdgesPerNode = 2 * numNodes * numEdges
38  double numNodes = 0, numEdges = 0;
39  for (const auto &node : _network.nodes) {
40  if (!node.erased) {
41  numNodes += 1;
42  numEdges += static_cast<double>(node.degree());
43  }
44  }
45  // if the best solution is only about twice as costly as the calculation of this heuristic, then don't bother
46  if (_bestCost < 2 * 2 * numNodes * numEdges) { return; }
47 
48  double bestScore, ourCost=0;
49  double ourFinalCost=0;
50  std::vector<std::pair<size_t,size_t>> ourContractions;
51  size_t bestId1, bestId2;
52  do {
53  bestScore = std::numeric_limits<double>::max();
54  for (size_t i = 0; i < _network.nodes.size(); ++i) {
55  if (_network.nodes[i].erased) { continue; }
56  TensorNetwork::TensorNode &ni = _network.nodes[i];
57  for (size_t j = i+1; j < _network.nodes.size(); ++j) {
58  if (_network.nodes[j].erased) { continue; }
59  TensorNetwork::TensorNode &nj = _network.nodes[j];
60  /* possible candidate (i.e. link to a later node) */
61  /* calculate n,m,r */
62  double m=1,n=1,r=1;
63  for (size_t d = 0; d < ni.degree(); ++d) {
64  if (ni.neighbors[d].other == j) {
65  r *= static_cast<double>(ni.neighbors[d].dimension);
66  } else {
67  m *= static_cast<double>(ni.neighbors[d].dimension);
68  }
69  }
70  for (size_t d = 0; d < nj.degree(); ++d) {
71  if (nj.neighbors[d].other != i) {
72  n *= static_cast<double>(nj.neighbors[d].dimension);
73  }
74  }
75  double tmpscore = scoreFct(m,n,r,0.0,0.0);
76  if (tmpscore < bestScore) {
77  bestScore = tmpscore;
78  ourCost = contraction_cost(m,n,r,0.0,0.0);
79  bestId1 = i;
80  bestId2 = j;
81  }
82  }
83  }
84  if (bestScore < std::numeric_limits<double>::max()) {
85  ourFinalCost += ourCost;
86  if (ourFinalCost > _bestCost) {
87  return;
88  }
89  ourContractions.emplace_back(bestId1,bestId2);
90  _network.contract(bestId1,bestId2);
91  }
92  } while (bestScore < std::numeric_limits<double>::max());
93  if (ourFinalCost < _bestCost) {
94  _bestCost = ourFinalCost;
95  _contractions = std::move(ourContractions);
96  }
97  }
98 
99 
100 
101 
102  double contraction_cost(double _m, double _n, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
103  return _m*_n*_r; // TODO sparse
104  }
105 
106 
107 
108 
109  double score_size(double _m, double _n, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
110  return _n*_m-(_n+_m)*_r;
111  }
112  double score_mn(double _m, double _n, double /*_r*/, double /*_sparsity1*/, double /*_sparsity2*/) {
113  return _m*_n;
114  }
115  double score_speed(double _m, double _n, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
116  return (_n*_m-(_n+_m)*_r)/(_n*_m*_r);
117  }
118  double score_r(double /*_m*/, double /*_n*/, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
119  return -_r;
120  }
121  double score_big_tensor(double _m, double _n, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
122  if (_n*_m<(_n+_m)*_r) {
123  return -1e10 + _n*_m*_r;
124  }
125  return _n*_m-(_n+_m)*_r;
126 
127  }
128  double score_littlestep(double _m, double _n, double _r, double /*_sparsity1*/, double /*_sparsity2*/) {
129  if (_n*_m<(_n+_m)*_r) {
130  return -std::max(_n,_m)*_r;
131  }
132  return _n*_m-(_n+_m)*_r;
133 
134  }
135 
136 
137 
138  std::tuple<size_t, size_t, size_t, double> best_of_three(const TensorNetwork &_network, size_t _id1, size_t _id2, size_t _id3) {
139  const TensorNetwork::TensorNode &na = _network.nodes[_id1];
140  const TensorNetwork::TensorNode &nb = _network.nodes[_id2];
141  const TensorNetwork::TensorNode &nc = _network.nodes[_id3];
142  double sa=1, sb=1, sc=1; // sizes devided by the link dimensions between a,b,c
143  double sab=1, sbc=1, sac=1; // link dimensions
144  for (size_t d = 0; d < na.degree(); ++d) {
145  if (na.neighbors[d].links(_id2)) {
146  sab *= static_cast<double>(na.neighbors[d].dimension);
147  } else if (na.neighbors[d].links(_id3)) {
148  sac *= static_cast<double>(na.neighbors[d].dimension);
149  } else {
150  sa *= static_cast<double>(na.neighbors[d].dimension);
151  }
152  }
153  for (size_t d = 0; d < nb.degree(); ++d) {
154  if (nb.neighbors[d].links(_id3)) {
155  sbc *= static_cast<double>(nb.neighbors[d].dimension);
156  } else if (!nb.neighbors[d].links(_id1)) {
157  sb *= static_cast<double>(nb.neighbors[d].dimension);
158  }
159  }
160  for (size_t d = 0; d < nc.degree(); ++d) {
161 // size_t other = nc.neighbors[d].other;
162  if (!nc.neighbors[d].links(_id1) && !nc.neighbors[d].links(_id2)) {
163  sc *= static_cast<double>(nc.neighbors[d].dimension);
164  }
165  }
166  // cost of contraction a-b first etc.
167  double costAB = sa*sb*sac*sbc*(sab+sc); // (sa*sac)*sab*(sb*sbc) + sa*sb*sac*sbc*sc;
168  double costAC = sa*sc*sab*sbc*(sac+sb);
169  double costBC = sb*sc*sab*sac*(sbc+sa);
170  if (costAB < costAC && costAB < costBC) {
171  return std::tuple<size_t, size_t, size_t, double>(_id1, _id2, _id3, sa*sb*sac*sbc*sab);
172  }
173  if (costAC < costBC) {
174  return std::tuple<size_t, size_t, size_t, double>(_id1, _id3, _id2, sa*sc*sab*sbc*sac);
175  }
176  return std::tuple<size_t, size_t, size_t, double>(_id2, _id3, _id1, sb*sc*sab*sac*sbc);
177  }
178 
179 
180  void greedy_best_of_three_heuristic(double &_bestCost, std::vector<std::pair<size_t,size_t>> &_contractions, TensorNetwork _network) {
181  // estimated cost to calculate this heuristic is
182  // numNodes * numNodes * 3*avgEdgesPerNode = 3 * numNodes * numEdges
183  size_t numNodes=0, numEdges=0;
184  for (const auto &node : _network.nodes) {
185  if (!node.erased) {
186  numNodes += 1;
187  numEdges += node.degree();
188  }
189  }
190  // if the best solution is only about twice as costly as the calculation of this heuristic, then don't bother
191  if (_bestCost < double(2 * 3 * numNodes * numEdges)) { return; }
192 
193  double ourFinalCost=0;
194  std::vector<std::pair<size_t,size_t>> ourContractions;
195  while (numNodes >= 3) {
196  // find a node with lowest degree
197  size_t id1 = 0;
198  size_t currDegree=~0ul;
199  for (size_t i=0; i<_network.nodes.size(); ++i) {
200  if (!_network.nodes[i].erased) {
201  if (_network.nodes[i].degree() < currDegree) {
202  id1 = i;
203  currDegree = _network.nodes[i].degree();
204  }
205  }
206  }
207 
208  // find its neighbor with lowest degree
209  size_t id2 = 0;
210  currDegree=~0ul;
211  for (const TensorNetwork::Link &l : _network.nodes[id1].neighbors) {
212  if (!l.external) {
213  if (_network.nodes[l.other].degree() < currDegree) {
214  id2 = l.other;
215  currDegree = _network.nodes[l.other].degree();
216  }
217  }
218  }
219 
220  size_t id3=0;
221  // find the next available node
222  while (id3 == id1 || id3 == id2 || _network.nodes[id3].erased) {
223  id3 += 1;
224  }
225  size_t numConnections = 0;
226  for (const TensorNetwork::Link &l : _network.nodes[id3].neighbors) {
227  if (l.links(id1) || l.links(id2)) {
228  numConnections += 1;
229  }
230  }
231  // find the next most connected node
232  for (size_t i=id3+1; i<_network.nodes.size(); ++i) {
233  if (i == id1 || i == id2) { continue; }
234  size_t newConnections=0;
235  for (const TensorNetwork::Link &l : _network.nodes[i].neighbors) {
236  if (l.links(id1) || l.links(id2)) {
237  newConnections += 1;
238  }
239  }
240  if (newConnections > numConnections) {
241  numConnections = newConnections;
242  id3 = i;
243  }
244  }
245 
246  // find the next best contraction within id1,id2,id3
247  std::tuple<size_t, size_t, size_t, double> contraction = best_of_three(_network, id1, id2, id3);
248  ourFinalCost += std::get<3>(contraction);
249  if (ourFinalCost > _bestCost) {
250  return;
251  }
252 // if (std::get<1>(contraction) == id1) {
253 // id1 = id3;
254 // } else if (std::get<1>(contraction) == id2) {
255 // id2 = id3;
256 // }
257  ourContractions.emplace_back(std::get<0>(contraction), std::get<1>(contraction));
258  _network.contract(std::get<0>(contraction), std::get<1>(contraction));
259  numNodes -= 1;
260  LOG(cont, std::get<0>(contraction) << " " << std::get<1>(contraction));
261 
262  if (numNodes == 2) {
263  ourFinalCost += _network.contraction_cost(id1, id2);
264  ourContractions.emplace_back(id1, id2);
265  LOG(cont, id1 << " " << id2);
266  numNodes -= 1;
267  }
268  }
269 
270  LOG(hahaha, ourFinalCost << " vs " << _bestCost);
271  if (ourFinalCost < _bestCost) {
272  _bestCost = ourFinalCost;
273  _contractions = std::move(ourContractions);
274  }
275  }
276 
277 
278  void exchange_heuristic(double &_bestCost, std::vector<std::pair<size_t,size_t>> &_contractions, TensorNetwork _network) {
279  // estimated cost to calculate this heuristic is
280  // numContractions * 3*avgEdgesPerNode ~= 3 * numEdges
281  TensorNetwork copyNet(_network);
282  double numEdges=0;
283  for (const auto &node : _network.nodes) {
284  if (!node.erased) {
285  numEdges += static_cast<double>(node.degree());
286  }
287  }
288  // if the best solution is only about twice as costly as the calculation of this heuristic, then don't bother
289  double cost_of_heuristic = 3*numEdges;
290 // LOG(vergleich, _bestCost << " vs " << cost_of_heuristic);
291  if (_bestCost < 2 * cost_of_heuristic) { return; }
292 
293 
294  std::vector<std::pair<size_t, size_t>> openPairs;
295  openPairs.push_back(_contractions.front());
296 
297  double ourFinalCost=0;
298  std::vector<std::pair<size_t,size_t>> ourContractions;
299  std::vector<size_t> idMap;
300  for (size_t i =0; i<_network.nodes.size(); ++i) {
301  idMap.emplace_back(i);
302  }
303 
304  for (size_t i=1; i<_contractions.size(); ++i) {
305  std::pair<size_t, size_t> next = _contractions[i];
306  while (next.first != idMap[next.first]) { next.first = idMap[next.first]; }
307  while (next.second != idMap[next.second]) { next.second = idMap[next.second]; }
308 
309  std::vector<std::pair<size_t, size_t>> newOpenPairs;
310  for (const std::pair<size_t, size_t> &p : openPairs) {
311  size_t id1 = p.first;
312  while (id1 != idMap[id1]) { id1 = idMap[id1]; }
313  size_t id2 = p.second;
314  while (id2 != idMap[id2]) { id2 = idMap[id2]; }
315  if (next.first != id1 && next.first != id2) {
316  if (next.second == id1 || next.second == id2) {
317  auto contr = best_of_three(_network, id1, id2, next.first);
318  size_t a = std::get<0>(contr);
319  size_t b = std::get<1>(contr);
320  size_t c = std::get<2>(contr);
321  idMap[b] = a;
322  ourFinalCost += std::get<3>(contr);
323  ourContractions.emplace_back(a,b);
324  _network.contract(a,b);
325  next.first = a;
326  next.second = c;
327  } else {
328  newOpenPairs.emplace_back(id1,id2);
329  }
330  } else {
331  if (next.second == id1 || next.second == id2) {
332  LOG(fatal, "ie");
333  } else {
334  auto contr = best_of_three(_network, id1, id2, next.second);
335  size_t a = std::get<0>(contr);
336  size_t b = std::get<1>(contr);
337  size_t c = std::get<2>(contr);
338  idMap[b] = a;
339  ourFinalCost += std::get<3>(contr);
340  ourContractions.emplace_back(a,b);
341  _network.contract(a,b);
342  next.first = a;
343  next.second = c;
344  }
345  }
346  }
347  newOpenPairs.emplace_back(next);
348  openPairs = std::move(newOpenPairs);
349  }
350 
351  INTERNAL_CHECK(openPairs.size() == 1, "ie");
352 
353  ourFinalCost += _network.contraction_cost(openPairs.front().first, openPairs.front().second);
354  ourContractions.emplace_back(openPairs.front());
355 
356 // LOG(hohohoEx, ourFinalCost << " vs " << _bestCost);
357  if (ourFinalCost < _bestCost) {
358  bool repeat = false;
359  if (_bestCost - ourFinalCost > cost_of_heuristic * 2) {
360  repeat = true;
361  }
362  _bestCost = ourFinalCost;
363  _contractions = std::move(ourContractions);
364 
365  if (repeat) {
366  exchange_heuristic(_bestCost, _contractions, copyNet);
367  }
368  }
369  }
370 
371 
372  const std::vector<ContractionHeuristic> contractionHeuristics {
373  &greedy_heuristic<&score_size>,
374  &greedy_heuristic<&score_mn>,
375  &greedy_heuristic<&score_speed>,
376 // &greedy_heuristic<&score_r>,
377  &greedy_heuristic<&score_big_tensor>,
378  &greedy_heuristic<&score_littlestep>
379 // ,&greedy_best_of_three_heuristic
381  };
382  } // namespace internal
383 
384 } // namespace xerus
Header file for CHECK and REQUIRE macros.
void contract(const size_t _nodeId1, const size_t _nodeId2)
Contracts the nodes with indices _nodeId1 and _nodeId2.
double score_littlestep(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
const std::vector< ContractionHeuristic > contractionHeuristics
Very general class used to represent arbitary tensor networks.
Definition: tensorNetwork.h:42
#define LOG
Definition: internal.h:38
void greedy_best_of_three_heuristic(double &_bestCost, std::vector< std::pair< size_t, size_t >> &_contractions, TensorNetwork _network)
The TensorNode class is used by the class TensorNetwork to store the componentent tensors defining th...
Definition: tensorNetwork.h:85
double contraction_cost(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
The main namespace of xerus.
Definition: basic.h:37
double score_mn(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
double contraction_cost(const size_t _nodeId1, const size_t _nodeId2) const
Approximates the cost of contraction two given nodes.
void exchange_heuristic(double &_bestCost, std::vector< std::pair< size_t, size_t >> &_contractions, TensorNetwork _network)
std::tuple< size_t, size_t, size_t, double > best_of_three(const TensorNetwork &_network, size_t _id1, size_t _id2, size_t _id3)
Header file for comfort functions and macros that should not be exported in the library.
size_t degree() const noexcept
Definition: tensorNode.cpp:74
Header file for the class managing the contraction heuristics.
void greedy_heuristic(double &_bestCost, std::vector< std::pair< size_t, size_t >> &_contractions, TensorNetwork _network)
#define INTERNAL_CHECK
Definition: internal.h:37
double score_big_tensor(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
std::vector< Link > neighbors
Vector of links defining the connection of this node to the network.
Definition: tensorNetwork.h:91
Header file for the TensorNetwork class.
double score_r(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
double score_size(double _m, double _n, double _r, double _sparsity1, double _sparsity2)
std::vector< TensorNode > nodes
The nodes constituting the network. The order determines the ids of the nodes.
double score_speed(double _m, double _n, double _r, double _sparsity1, double _sparsity2)