doc/gjk.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 import pdb
3 import sys
4 
5 # ABC = AB^AC
6 # (ABC^AJ).a = (j.c - j.b) a.a + (j.a - j.c) b.a + (j.b - j.a) c.a, for j = b or c
7 
8 segment_fmt = "{j}a_aa"
9 plane_fmt = ""
10 edge_fmt = "{j}a * {b}a_{c}a + {j}{b} * {c}a_aa - {j}{c} * {b}a_aa"
11 
12 # These checks must be negative and not positive, as in the cheat sheet.
13 # They are the same as in the cheat sheet, except that we consider (...).dot(A) instead of (...).dot(-A)
14 plane_tests = ["C.dot (a_cross_b)", "D.dot(a_cross_c)", "-D.dot(a_cross_b)"]
15 checks = (
16  plane_tests
17  + [edge_fmt.format(**{"j": j, "b": "b", "c": "c"}) for j in ["b", "c"]]
18  + [edge_fmt.format(**{"j": j, "b": "c", "c": "d"}) for j in ["c", "d"]]
19  + [edge_fmt.format(**{"j": j, "b": "d", "c": "b"}) for j in ["d", "b"]]
20  + [segment_fmt.format(**{"j": j}) for j in ["b", "c", "d"]]
21 )
22 checks_hr = (
23  ["ABC.AO >= 0", "ACD.AO >= 0", "ADB.AO >= 0"]
24  + ["(ABC ^ {}).AO >= 0".format(n) for n in ["AB", "AC"]]
25  + ["(ACD ^ {}).AO >= 0".format(n) for n in ["AC", "AD"]]
26  + ["(ADB ^ {}).AO >= 0".format(n) for n in ["AD", "AB"]]
27  + ["AB.AO >= 0", "AC.AO >= 0", "AD.AO >= 0"]
28 )
29 
30 # weights of the checks.
31 weights = (
32  [
33  2,
34  ]
35  * 3
36  + [
37  3,
38  ]
39  * 6
40  + [
41  1,
42  ]
43  * 3
44 )
45 
46 # Segment tests first, because they have lower weight.
47 # tests = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ]
48 tests = [
49  9,
50  10,
51  11,
52  0,
53  1,
54  2,
55  3,
56  4,
57  5,
58  6,
59  7,
60  8,
61 ]
62 assert len(tests) == len(checks)
63 assert sorted(tests) == list(range(len(tests)))
64 
65 regions = [
66  "ABC",
67  "ACD",
68  "ADB",
69  "AB",
70  "AC",
71  "AD",
72  "A",
73  "Inside",
74 ]
75 cases = list(range(len(regions)))
76 
77 # The following 3 lists refer to table doc/GJK_tetrahedra_boolean_table.ods
78 
79 # A check ID is (+/- (index+1)) where a minus sign encodes a NOT operation
80 # and index refers to an index in list checks.
81 
82 # definitions is a list of list of check IDs to be ANDed.
83 # For instance, a0.a3.!a4 -> [ 1, 4, -5]
84 definitions = [
85  [1, 4, -5],
86  [2, 6, -7],
87  [3, 8, -9],
88  [-4, 9, 10],
89  [-6, 5, 11],
90  [-8, 7, 12],
91  [-10, -11, -12],
92  [-1, -2, -3],
93 ]
94 # conditions is a list of (list of (list of check IDs to be ANDed) to be ORed).
95 conditions = [
96  [],
97  [],
98  [],
99  [],
100  [],
101  [],
102  [],
103  [], # [ [10, 11, 12], ], # I don't think this is always true...
104 ]
105 # rejections is a list of (list of (list of check IDs to be ANDed) to be ORed).
106 rejections = [
107  [
108  [2, 6, 7],
109  [3, -8, -9],
110  ],
111  [
112  [3, 8, 9],
113  [1, -4, -5],
114  ],
115  [
116  [1, 4, 5],
117  [2, -6, -7],
118  ],
119  [
120  [-1, -3],
121  ],
122  [
123  [-2, -1],
124  ],
125  [
126  [-3, -2],
127  ],
128  [
129  [4, -5],
130  [6, -7],
131  [8, -9],
132  ],
133  [],
134 ]
135 
136 implications = [
137  [
138  [
139  4,
140  5,
141  10,
142  ],
143  [11],
144  ],
145  [
146  [
147  6,
148  7,
149  11,
150  ],
151  [12],
152  ],
153  [
154  [
155  8,
156  9,
157  12,
158  ],
159  [10],
160  ],
161  [
162  [
163  -4,
164  -5,
165  11,
166  ],
167  [10],
168  ],
169  [
170  [
171  -6,
172  -7,
173  12,
174  ],
175  [11],
176  ],
177  [
178  [
179  -8,
180  -9,
181  10,
182  ],
183  [12],
184  ],
185  [[1, 4, 5, 6], [-7]],
186  [[2, 6, 9, 8], [-9]],
187  [[3, 8, 9, 4], [-5]],
188  [
189  [
190  -4,
191  5,
192  10,
193  ],
194  [-11],
195  ],
196  [
197  [
198  4,
199  -5,
200  -10,
201  ],
202  [11],
203  ],
204  [
205  [
206  -6,
207  7,
208  11,
209  ],
210  [-12],
211  ],
212  [
213  [
214  6,
215  -7,
216  -11,
217  ],
218  [12],
219  ],
220  [
221  [
222  -8,
223  9,
224  12,
225  ],
226  [-10],
227  ],
228  [
229  [
230  8,
231  -9,
232  -12,
233  ],
234  [10],
235  ],
236  [[10, 3, 9, -12, 4, -5], [1]],
237  [[10, -3, 1, -4], [9]],
238  [[10, -3, -1, 2, -6, 11], [5]],
239  [[-10, 11, 2, -12, -5, -1], [6]],
240  [[-10, 11, -2, 1, 5], [-6]],
241  [[-10, -11, 12, 1, -7, -2, 4], [-5]],
242  [[-10, -11, 12, -3, 2, 7], [-8]],
243  [[-10, -11, 12, -3, -2], [-1]],
244 ]
245 
246 
247 def set_test_values(current_tests, test_values, itest, value):
248  def satisfies(values, indices):
249  for k in indices:
250  if k > 0 and values[k - 1] != True:
251  return False
252  if k < 0 and values[-k - 1] != False:
253  return False
254  return True
255 
256  remaining_tests = list(current_tests)
257  next_test_values = list(test_values)
258 
259  remaining_tests.remove(itest)
260  next_test_values[itest] = value
261  rerun = True
262  while rerun:
263  rerun = False
264  for impl in implications:
265  if satisfies(next_test_values, impl[0]):
266  for id in impl[1]:
267  k = (id - 1) if id > 0 else (-id - 1)
268  if k in remaining_tests:
269  next_test_values[k] = id > 0
270  remaining_tests.remove(k)
271  rerun = True
272  else:
273  if next_test_values[k] != (id > 0):
274  raise ValueError("Absurd case")
275  return remaining_tests, next_test_values
276 
277 
278 def set_tests_values(current_tests, test_values, itests, values):
279  for itest, value in zip(itests, values):
280  current_tests, test_values = set_test_values(
281  current_tests, test_values, itest, value
282  )
283  return current_tests, test_values
284 
285 
286 def apply_test_values(cases, test_values):
287  def canSatisfy(values, indices):
288  for k in indices:
289  if k > 0 and values[k - 1] == False:
290  return False
291  if k < 0 and values[-k - 1] == True:
292  return False
293  return True
294 
295  def satisfies(values, indices):
296  for k in indices:
297  if k > 0 and values[k - 1] != True:
298  return False
299  if k < 0 and values[-k - 1] != False:
300  return False
301  return True
302 
303  # Check all cases.
304  left_cases = []
305  for case in cases:
306  defi = definitions[case]
307  conds = conditions[case]
308  rejs = rejections[case]
309  if satisfies(test_values, defi):
310  # A definition is True, stop recursion
311  return [case]
312  if not canSatisfy(test_values, defi):
313  continue
314  for cond in conds:
315  if satisfies(test_values, cond):
316  # A condition is True, stop recursion
317  return [case]
318  append = True
319  for rej in rejs:
320  if satisfies(test_values, rej):
321  # A rejection is True, discard this case
322  append = False
323  break
324  if append:
325  left_cases.append(case)
326  return left_cases
327 
328 
329 def max_number_of_tests(
330  current_tests,
331  cases,
332  test_values=[
333  None,
334  ]
335  * len(tests),
336  prevBestScore=float("inf"),
337  prevScore=0,
338 ):
339  for test in current_tests:
340  assert test_values[test] == None, "Test " + str(test) + " already performed"
341 
342  left_cases = apply_test_values(cases, test_values)
343 
344  if len(left_cases) == 1:
345  return prevScore, {
346  "case": left_cases[0],
347  }
348  elif len(left_cases) == 0:
349  return prevScore, {
350  "case": None,
351  "comments": [
352  "applied " + str(test_values),
353  "to " + ", ".join([regions[c] for c in cases]),
354  ],
355  }
356 
357  assert len(current_tests) > 0, "No more test but " + str(left_cases) + " remains"
358 
359  currentBestScore = prevBestScore
360  bestScore = float("inf")
361  bestOrder = [None, None]
362  for i, test in enumerate(current_tests):
363  assert bestScore >= currentBestScore
364 
365  currentScore = prevScore + len(left_cases) * weights[test]
366  # currentScore = prevScore + weights[test]
367  if currentScore > currentBestScore: # Cannot do better -> stop
368  continue
369 
370  try:
371  remaining_tests, next_test_values = set_test_values(
372  current_tests, test_values, test, True
373  )
374  except ValueError:
375  remaining_tests = None
376 
377  if remaining_tests is not None:
378  # Do not put this in try catch as I do not want other ValueError to be understood as an infeasible branch.
379  score_if_t, order_if_t = max_number_of_tests(
380  remaining_tests,
381  left_cases,
382  next_test_values,
383  currentBestScore,
384  currentScore,
385  )
386  if score_if_t >= currentBestScore: # True didn't do better -> stop
387  continue
388  else:
389  score_if_t, order_if_t = prevScore, None
390 
391  try:
392  remaining_tests, next_test_values = set_test_values(
393  current_tests, test_values, test, False
394  )
395  except ValueError:
396  remaining_tests = None
397 
398  if remaining_tests is not None:
399  # Do not put this in try catch as I do not want other ValueError to be understood as an infeasible branch.
400  score_if_f, order_if_f = max_number_of_tests(
401  remaining_tests,
402  left_cases,
403  next_test_values,
404  currentBestScore,
405  currentScore,
406  )
407  else:
408  score_if_f, order_if_f = prevScore, None
409 
410  currentScore = max(score_if_t, score_if_f)
411  if currentScore < bestScore:
412  if currentScore < currentBestScore:
413  bestScore = currentScore
414  bestOrder = {"test": test, "true": order_if_t, "false": order_if_f}
415  # pdb.set_trace()
416  currentBestScore = currentScore
417  if len(tests) == len(current_tests):
418  print("New best score: {}".format(currentBestScore))
419 
420  return bestScore, bestOrder
421 
422 
423 def printComments(order, indent, file):
424  if "comments" in order:
425  for comment in order["comments"]:
426  print(indent + "// " + comment, file=file)
427 
428 
429 def printOrder(order, indent="", start=True, file=sys.stdout, curTests=[]):
430  if start:
431  print(
432  "bool GJK::projectTetrahedraOrigin(const Simplex& current, Simplex& next)",
433  file=file,
434  )
435  print("{", file=file)
436  print(
437  indent + "// The code of this function was generated using doc/gjk.py",
438  file=file,
439  )
440  print(indent + "const vertex_id_t a = 3, b = 2, c = 1, d = 0;", file=file)
441  for l in "abcd":
442  print(
443  indent
444  + "const Vec3f& {} (current.vertex[{}]->w);".format(l.upper(), l),
445  file=file,
446  )
447  print(indent + "const FCL_REAL aa = A.squaredNorm();".format(l), file=file)
448  for l in "dcb":
449  for m in "abcd":
450  if m <= l:
451  print(
452  indent
453  + "const FCL_REAL {0}{1} = {2}.dot({3});".format(
454  l, m, l.upper(), m.upper()
455  ),
456  file=file,
457  )
458  else:
459  print(
460  indent + "const FCL_REAL& {0}{1} = {1}{0};".format(l, m),
461  file=file,
462  )
463  print(indent + "const FCL_REAL {0}a_aa = {0}a - aa;".format(l), file=file)
464  for l0, l1 in zip("bcd", "cdb"):
465  print(
466  indent + "const FCL_REAL {0}a_{1}a = {0}a - {1}a;".format(l0, l1),
467  file=file,
468  )
469  for l in "bc":
470  print(
471  indent + "const Vec3f a_cross_{0} = A.cross({1});".format(l, l.upper()),
472  file=file,
473  )
474  print("", file=file)
475  print("#define REGION_INSIDE() " + indent + "\\", file=file)
476  print(indent + " ray.setZero(); \\", file=file)
477  print(indent + " next.vertex[0] = current.vertex[d]; \\", file=file)
478  print(indent + " next.vertex[1] = current.vertex[c]; \\", file=file)
479  print(indent + " next.vertex[2] = current.vertex[b]; \\", file=file)
480  print(indent + " next.vertex[3] = current.vertex[a]; \\", file=file)
481  print(indent + " next.rank=4; \\", file=file)
482  print(indent + " return true;", file=file)
483  print("", file=file)
484 
485  if "case" in order:
486  case = order["case"]
487  if case is None:
488  print(
489  indent + "// There are no case corresponding to this set of tests.",
490  file=file,
491  )
492  printComments(order, indent, file)
493  print(indent + "assert(false);", file=file)
494  return
495  region = regions[case]
496  print(indent + "// Region " + region, file=file)
497  printComments(order, indent, file)
498  toFree = ["b", "c", "d"]
499  if region == "Inside":
500  print(indent + "REGION_INSIDE()", file=file)
501  toFree = []
502  elif region == "A":
503  print(indent + "originToPoint (current, a, A, next, ray);", file=file)
504  elif len(region) == 2:
505  a = region[0]
506  B = region[1]
507  print(
508  indent
509  + "originToSegment (current, a, {b}, A, {B}, {B}-A, -{b}a_aa, next, ray);".format(
510  **{
511  "b": B.lower(),
512  "B": B,
513  }
514  ),
515  file=file,
516  )
517  toFree.remove(B.lower())
518  elif len(region) == 3:
519  B = region[1]
520  C = region[2]
521  test = plane_tests[["ABC", "ACD", "ADB"].index(region)]
522  if test.startswith("-"):
523  test = test[1:]
524  else:
525  test = "-" + test
526  print(
527  indent
528  + "originToTriangle (current, a, {b}, {c}, ({B}-A).cross({C}-A), {t}, next, ray);".format(
529  **{"b": B.lower(), "c": C.lower(), "B": B, "C": C, "t": test}
530  ),
531  file=file,
532  )
533  toFree.remove(B.lower())
534  toFree.remove(C.lower())
535  else:
536  assert False, "Unknown region " + region
537  for pt in toFree:
538  print(
539  indent + "free_v[nfree++] = current.vertex[{}];".format(pt), file=file
540  )
541  else:
542  assert "test" in order and "true" in order and "false" in order
543  check = checks[order["test"]]
544  check_hr = checks_hr[order["test"]]
545  printComments(order, indent, file)
546  nextTests_t = curTests + [
547  "a" + str(order["test"] + 1),
548  ]
549  nextTests_f = curTests + [
550  "!a" + str(order["test"] + 1),
551  ]
552  if order["true"] is None:
553  if order["false"] is None:
554  print(
555  indent
556  + """assert(false && "Case {} should never happen.");""".format(
557  check_hr
558  )
559  )
560  else:
561  print(
562  indent
563  + "assert(!({} <= 0)); // Not {} / {}".format(
564  check, check_hr, ".".join(nextTests_f)
565  ),
566  file=file,
567  )
568  printOrder(
569  order["false"],
570  indent=indent,
571  start=False,
572  file=file,
573  curTests=nextTests_f,
574  )
575  elif order["false"] is None:
576  print(
577  indent
578  + "assert({} <= 0); // {} / {}".format(
579  check, check_hr, ".".join(nextTests_t)
580  ),
581  file=file,
582  )
583  printOrder(
584  order["true"],
585  indent=indent,
586  start=False,
587  file=file,
588  curTests=nextTests_t,
589  )
590  else:
591  print(
592  indent
593  + "if ({} <= 0) {{ // if {} / {}".format(
594  check, check_hr, ".".join(nextTests_t)
595  ),
596  file=file,
597  )
598  printOrder(
599  order["true"],
600  indent=indent + " ",
601  start=False,
602  file=file,
603  curTests=nextTests_t,
604  )
605  print(
606  indent
607  + "}} else {{ // not {} / {}".format(check_hr, ".".join(nextTests_f)),
608  file=file,
609  )
610  printOrder(
611  order["false"],
612  indent=indent + " ",
613  start=False,
614  file=file,
615  curTests=nextTests_f,
616  )
617  print(indent + "}} // end of {}".format(check_hr), file=file)
618 
619  if start:
620  print("", file=file)
621  print("#undef REGION_INSIDE", file=file)
622  print(indent + "return false;", file=file)
623  print("}", file=file)
624 
625 
626 def unit_tests():
627  # a4, a5, a10, a11, a12
628  cases = list(range(len(regions)))
629  pdb.set_trace()
630  left_cases = apply_test_values(
631  cases,
632  test_values=[
633  None,
634  None,
635  None,
636  True,
637  True,
638  None,
639  None,
640  None,
641  None,
642  True,
643  True,
644  True,
645  ],
646  )
647  assert len(left_cases) > 1
648 
649 
650 # unit_tests()
651 
652 score, order = max_number_of_tests(tests, cases)
653 
654 print(score)
655 printOrder(order, indent=" ")
656 
657 # TODO add weights such that:
658 # - it is preferred to have all the use of one check in one branch.
659 # idea: ponderate by the number of remaining tests.
const char * str()


hpp-fcl
Author(s):
autogenerated on Fri Jun 2 2023 02:39:01