-
Notifications
You must be signed in to change notification settings - Fork 1
/
abc.coffee
1340 lines (1120 loc) · 47.5 KB
/
abc.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#methods for exploring postures, ABC style
if (typeof Array::clone != 'function')
Array::clone = ->
cloned = []
for i in this
cloned.push i
cloned
if (typeof String::startsWith != 'function')
String::startsWith = (input) ->
this.substring(0, input.length) == input
squared = (val) ->
val*val
thresholdDistance = 0.15 #the euclidian distance that is considered to be small enough to say two postures are the same
thresholdExplore = 0.45 #distance for finding possibly equal postures, has to be verified with position control though
class posture #i.e. node
constructor: (configuration, csl_mode=[], x_pos=0, timestamp=Date.now()) ->
@name = -99
@csl_mode = csl_mode # [upper, lower]
@configuration = configuration # [body angle, hip joint angle, knee joint angle]
@mean_n = 1 #count the amount of configurations we have merged into this one
@positions = [] #positions of the body part for svg drawing
@body_x = x_pos
@timestamp = timestamp
@edges_out = []
#@edges_in = []
@exit_directions = [0,0,0,0] #h+,h-,k+,k- : list of the target nodes for each direction of each joint
@length = 1 #quirk for searchSubarray
@activation = 1
@subManifoldId = 0
asJSON: =>
#prevent circular references
replacer = (edges)->
new_edges = []
for e in edges
new_edges.push e.target_node.name
new_edges
JSON.stringify {"name":@name, "csl_mode":@csl_mode, "configuration":@configuration, "mean_n":@mean_n, "positions":@positions, "body_x":@body_x, "timestamp":@timestamp, "exit_directions":@exit_directions, "activation":@activation, "edges_out": replacer(@edges_out)}, null, 4
getEdgeTo: (target) =>
for edge in @edges_out
return edge if edge.target_node is target
getEdgeFrom: (source) =>
for edge in source.edges_out
return edge if edge.target_node is this
isEqualTo: (node) =>
@configuration[0] == node.configuration[0] and @configuration[1] == node.configuration[1] and @configuration[2] == node.configuration[2] and @csl_mode[0] == node.csl_mode[0] and @csl_mode[1] == node.csl_mode[1]
#methods to determine if this posture is near another one
euclidDistance: (to) =>
squared(physics.abc.smallestAngleDistance(
physics.abc.wrapAngle(@configuration[0])
physics.abc.wrapAngle(to.configuration[0]))) +
squared(@configuration[1] - to.configuration[1]) +
squared(@configuration[2] - to.configuration[2])
#detect if postures are close enough to be possibly the same
isClose: (to, eps=thresholdDistance) =>
@euclidDistance(to) < eps and @csl_mode[0] == to.csl_mode[0] and @.csl_mode[1] == to.csl_mode[1]
#comparator for exploring
isCloseExplore: (a,i, b,j) =>
#TODO: also consider e.g. r+,s+ and r+,c equal. test that properly, might produce weird results...
if a? and b and b[j]
a.isClose(b[j], thresholdExplore)
else
false
getSubmanifold: =>
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# idx grp hip- hip+ knee- knee+ z_body y_hip x_knee stab ener t_hip t_knee mode xleft dx COGpos
#test_p = new posture([0,0,0], @csl_mode, @body_x, @timestamp)
old_dist = 2
grp = 0
for p in semni_manifold
#test_p.configuration[0] = physics.abc.wrapAngle -p[6]
#test_p.configuration[1] = physics.abc.wrapAngle -p[7]
#test_p.configuration[2] = physics.abc.wrapAngle -p[8]
#dist = @euclidDistance(test_p)
dist = squared(physics.abc.wrapAngleManifold @configuration[0] - physics.abc.wrapAngleManifold -p[6]) +
squared(@configuration[1] - -p[7]) +
squared(@configuration[2] - -p[8])
if dist < old_dist
old_dist = dist
grp = p[1]
#console.log(p[0] + " distance: " + dist + " grp: " + grp)
return grp
#end class posture
class transition #i.e. edge
constructor: (start_node, target_node) ->
@start_node = start_node
@target_node = target_node
@distance = 0 # distance the body traveled
@timedelta = 0 # time the transition took
@csl_mode = []
toString: =>
@start_node.name + "->" + @target_node.name
asJSON: =>
JSON.stringify {"name": @toString(), "csl_mode":@csl_mode, "start_node": @start_node.name, "target_node": @target_node.name, "distance": @distance, "timedelta": @timedelta}, null, 4
isInList: (list) =>
for t in list
return true if @start_node is t.start_node and @target_node is t.target_node
return false
#end class transition
class postureGraph
#class holding the abc learning graph structure
#because arbor (library for drawing graphs) has it's own internal structure, there are two graphs in memory,
#one for drawing and one that also holds the additional data and methods
constructor: (arborGraph) ->
@nodes = [] #list of the posture nodes
@walk_circle_active = false
@arborGraph = arborGraph
addNode: (node) =>
node.name = @nodes.length+1
@nodes.push node
node.subManifoldId = node.getSubmanifold()
return node.name
addPosture: (p) =>
node_name = @addNode p
console.log("found new posture: " + p.configuration + " (posture " + node_name + ")")
window.logging.logNewPosture()
manifoldRenderer.addFixpoint(manifoldRenderer.internalToManifold(p.configuration))
#add an arbor node as well (not connected yet)
data =
label: p.csl_mode
number: p.name
activation: p.activation
configuration: p.configuration
positions: p.positions
subManifoldId: p.subManifoldId
visits: p.mean_n
@arborGraph.addNode p.name, data
return node_name
deletePosture: (p_name) =>
an = @arborGraph.getNode(p_name)
#get outgoing edges
edges = []
for e in @getNodeByName(p_name).edges_out
if e?
edges.push e
for n in @nodes #search for incoming edges
if n? and n.edges_out.length
for e in [0..n.edges_out.length-1]
en = n.edges_out[e]
if en?
if en.target_node.name is p_name #found one
edges.push en
#delete start node references to edge
for d in [0..3]
if n.exit_directions[d] is p_name
n.exit_directions[d] = 0
delete n.edges_out[e]
#delete svg elements
an.data.semni.remove()
an.data.label_svg.remove()
an.data.label_svg2.remove()
an.data.label_svg3.remove()
@arborGraph.renderer.svg_nodes[p_name].remove()
delete @arborGraph.renderer.svg_nodes[p_name]
for e in edges
@arborGraph.renderer.svg_edges[e.start_node.name+"-"+e.target_node.name].remove()
delete @arborGraph.renderer.svg_edges[e.start_node.name+"-"+e.target_node.name]
#delete posture and arbor node
@arborGraph.pruneNode(an)
delete @nodes[p_name-1]
getNodeByIndex: (index) =>
@nodes[index]
getNodeByName: (name) =>
if name>0
@nodes[name-1]
else
console.log("warning: tried to get node with index <= 0!")
length: =>
@nodes.length
meanActivation: =>
act = 0
for n in @nodes
if n?
act += n.activation
return act / @length()
meanVisits: =>
visits = 0
for n in @nodes
if n?
visits += n.mean_n
e = visits / @length()
#standard deviation
sigma = 0
for n in @nodes
if n?
sigma += (n.mean_n - e)*(n.mean_n - e)
console.log("sigma: " + Math.sqrt(sigma/@length()))
return e
maxVisits: =>
visits = 0
for n in @nodes
if n?
visits = if n.mean_n > visits then n.mean_n else visits
return visits
minVisits: =>
visits = 99999
for n in @nodes
if n?
visits = if n.mean_n < visits then n.mean_n else visits
return visits
findDuplicates: =>
for n in @nodes
if n?
for nn in @nodes
if nn?
if n.name isnt nn.name and n.isClose(nn, thresholdExplore)
console.log("duplicates "+ n.name+" and " + nn.name + ". distance " + n.euclidDistance(nn))
return
saveGaphToFile: =>
graph_as_string = ""
edges = []
for n in @nodes
graph_as_string += "\n"+n.asJSON()+","
for e in n.edges_out
edges.push e
edges_as_string = ""
for e in edges
edges_as_string += "\n"+e.asJSON()+","
#remove last comma
graph_as_string = graph_as_string.substring(0, graph_as_string.length - 1)
edges_as_string = edges_as_string.substring(0, edges_as_string.length - 1)
location.href = 'data:text;charset=utf-8,'+encodeURI "{\n"+"\"nodes\": ["+graph_as_string+"],\n"+"\"edges\": ["+edges_as_string+"]"+"\n}"
populateGraphFromJSON: (tj=null) =>
#flatten file and parse it (parse chokes on newlines)
tj = tj.replace(/(\r\n|\n|\r)/gm,"")
t = JSON.parse(tj)
#clear nodes we might already have
@nodes = []
#put the new nodes in
for n in t.nodes
nn = new posture(n.configuration, n.csl_mode, n.body_x, n.timestamp)
nn.name = n.name
nn.mean_n = n.mean_n
nn.activation = n.activation
nn.exit_directions = n.exit_directions
nn.positions = n.positions
nn.configuration = n.configuration
nn.subManifoldId = nn.getSubmanifold()
nn.visits = n.mean_n
@nodes.push nn
manifoldRenderer.addFixpoint(manifoldRenderer.internalToManifold(nn.configuration))
#put in edges
for e in t.edges
n = @getNodeByName e.start_node
nn = @getNodeByName e.target_node
ee = new transition(n, nn)
ee.csl_mode = e.csl_mode
ee.distance = e.distance
ee.timedelta = e.timedelta
n.edges_out.push(ee)
if n.edges_out.length > 4
console.log("warning: more than 4 outgoing edges in " +n.name)
#refresh display graph
ag = @arborGraph
@arborGraph.prune()
@arborGraph.renderer.svg_nodes = {}
@arborGraph.renderer.svg_edges = {}
$("#viewport_svg svg g").slice(1).remove()
$("#viewport_svg svg rect").remove()
$("#viewport_svg svg text").remove()
$("#viewport_svg svg line").remove()
@arborGraph.renderer.initManifoldBar()
@arborGraph.renderer.initVisitCountLegend()
for n in @nodes
physics.abc.drawManifoldStripe(n)
for e in n.edges_out
nn = e.target_node
ag.addEdge(n.name, nn.name, {"label": e.csl_mode, "distance": e.distance, "timedelta": e.timedelta})
source_node = ag.getNode(n.name)
target_node = ag.getNode(nn.name)
source_node.data =
label: n.csl_mode
number: n.name
activation: n.activation
configuration: n.configuration
positions: n.positions
subManifoldId: n.subManifoldId
visits: n.mean_n
target_node.data =
label: nn.csl_mode
number: nn.name
activation: nn.activation
configuration: nn.configuration
positions: nn.positions
subManifoldId: nn.subManifoldId
visits: nn.mean_n
return
loadGraphFromFile: (files) =>
readFile = (file, callback) ->
reader = new FileReader()
reader.onload = (evt) ->
callback file, evt if typeof callback is "function"
reader.readAsBinaryString file
if files.length > 0
readFile files[0], (file, evt) ->
physics.abc.posture_graph.populateGraphFromJSON evt.target.result
@arborGraph.renderer.pause_drawing = false
$("#graph_pause_drawing").attr('checked', false)
@arborGraph.start(true)
@arborGraph.renderer.click_time = Date.now()
@arborGraph.renderer.redraw()
populateGraphFromSemniFile: (data=null) =>
csl_mode_to_string_mode = (mode) ->
for m in [0..mode.length-1]
mode[m] = ["r+","r-","c","s+","s-"][mode[m]]
return mode
#clear nodes we might already have
@nodes = []
data = data.split("\n")
if data[data.length-1] is ''
data.pop()
#create the new nodes
for l in data
vals = []
for v in l.split(" ")
v = v.replace(/(\]|\[)/gm,"").split(",")
for n in [0..v.length-1]
v[n]=Number(v[n])
vals.push(v)
nn = new posture(vals[3], csl_mode_to_string_mode vals[2], 0, Date.now())
nn.name = vals[0][0]
nn.mean_n = 0
nn.activation = vals[4][0]/100
nn.exit_directions = vals[1]
nn.positions = [0,0,0]
#semni to angle mappings
#body: 260 => 0
#508 => 1.57
#0 => -1.57
#knee
#5 => -3.194
#640 => 0
#1007 => 1.908
#hip
#361 => 0
#895 => 2.716
#176 => -0.94
#TODO: use
#body_angle = Math.atan2(accely, accelz)
nn.configuration = [((((vals[3][0])-250)/1023)*2*Math.PI), ((vals[3][1]-361)/1023)*0.818*2*Math.PI, ((vals[3][2]-640)/1023)*0.818*2*Math.PI]
nn.subManifoldId = nn.getSubmanifold()
@nodes.push nn
manifoldRenderer.addFixpoint(manifoldRenderer.internalToManifold(nn.configuration))
#put in edges
for l in data
vals = []
for v in l.split(" ")
vals.push(v.replace(/(\]|\[)/gm,"").split(","))
n = @getNodeByName vals[0][0]
for e in vals[1]
if e > 0 and e <= @nodes.length
nn = @getNodeByName e
if n and nn
ee = new transition(n, nn)
ee.csl_mode = csl_mode_to_string_mode vals[2]
ee.distance = 0
ee.timedelta = 0
n.edges_out.push(ee)
#refresh display graph
ag = @arborGraph
@arborGraph.prune()
@arborGraph.renderer.svg_nodes = {}
@arborGraph.renderer.svg_edges = {}
$("#viewport_svg svg g").slice(1).remove()
$("#viewport_svg svg rect").remove()
$("#viewport_svg svg text").remove()
$("#viewport_svg svg line").remove()
@arborGraph.renderer.initManifoldBar()
for n in @nodes
physics.abc.drawManifoldStripe(n)
for e in n.edges_out
nn = e.target_node
ag.addEdge(n.name, nn.name, {"label": e.csl_mode, "distance": e.distance, "timedelta": e.timedelta})
source_node = ag.getNode(n.name)
target_node = ag.getNode(nn.name)
source_node.data =
label: n.csl_mode
number: n.name
activation: n.activation
configuration: n.configuration
positions: n.positions
subManifoldId: n.subManifoldId
visits: n.mean_n
target_node.data =
label: nn.csl_mode
number: nn.name
activation: nn.activation
configuration: nn.configuration
positions: nn.positions
subManifoldId: nn.subManifoldId
visits: nn.mean_n
#return empty so we dont collect results in for loop (coffee-script...)
return
loadGraphFromSemniFile: (files) =>
readFile = (file, callback) ->
reader = new FileReader()
reader.onload = (evt) ->
callback file, evt if typeof callback is "function"
reader.readAsBinaryString file
if files.length > 0
readFile files[0], (file, evt) ->
physics.abc.posture_graph.populateGraphFromSemniFile evt.target.result
@arborGraph.renderer.pause_drawing = false
$("#graph_pause_drawing").attr('checked', false)
@arborGraph.start(true)
@arborGraph.renderer.click_time = Date.now()
@arborGraph.renderer.redraw()
findElementaryCircles: =>
#implement Tarjan's algorithm for finding circles in directed graphs
#expects no negative weights, no multiedges
# R. Tarjan, Enumeration of the elementary circuits of a directed graph, SIAM Journal on Computing,
# 2 (1973), pp. 211-216
# based on an implementation from https://github.com/josch/cycles_tarjan/blob/master/cycles.py
# might be slow for larger graphs, then consider using
# Enumerating Circuits and Loops in Graphs with Self-Arcs and Multiple-Arcs, K.A.Hawick and H.A.James,
# Proc. 2008 International Conference on Foundations of Computer Science (FCS'08), Las Vegas, USA, 14-17 July 2008
#prepare node lists
A = []
A.push [] for a in [[email protected]]
for num in [[email protected]]
node = @nodes[num]
break unless node
for edge in node.edges_out
A[num].push edge.target_node.name
point_stack = []
marked = {}
marked_stack = []
circles = []
#walk through edges from node v depth-first, marking nodes and remembering the path
parent = this
backtrack = (v) ->
f = false
point_stack.push(v)
marked[v] = true
marked_stack.push(v)
for w in A[v]
if w<s
A[w] = 0
else if w==s
#we found a circle
path = []
d = 0
t = 0
for n in [1..point_stack.length]
#we're at the last node of the path, insert edge to first node
if n is point_stack.length
m = n-1
n = 0
else
m = n-1
edge = parent.nodes[point_stack[m]].getEdgeTo parent.nodes[point_stack[n]]
path.push edge
d += edge.distance
t += edge.timedelta
path = path.concat [d, t, (d/t)*1000]
circles.push(path)
f = true
else if not marked[w]
f = backtrack(w) or f
if f
while marked_stack.slice(-1)[0] != v
u = marked_stack.pop()
marked[u] = false
marked_stack.pop()
marked[v] = false
point_stack.pop()
return f
#initialise markers
for i in [0..A.length-1]
marked[i] = false
#start walking from every node
for s in [0..A.length-1]
backtrack(s)
while marked_stack.length
u = marked_stack.pop()
marked[u] = false
#sort by travel distance
@circles = circles.sort (a,b) ->
if a.slice(-1)[0]<=b.slice(-1)[0] then -1 else 1
walkCircle: =>
if @circles
if @walk_circle_active
@walk_circle_active = false
@best_circle.length = 0
@best_circle = undefined
else
physics.abc.explore_active = false
@best_circle = @circles.slice(-1)[0] #last circle is the one with largest distance
#TODO: go to first posture before we can start walking
#find path from current posture to this one
#use best_circle[0].start_node .csl_mode .configuration
@walk_circle_active = true
#start with first transition
@best_circle[0].active = true
physics.abc.graph.renderer.redraw()
diffuseLearnProgress: =>
#for each node, get activation through all outgoing edges and sum them up
#loop over nodes twice to properly deal with recurrent loops
unless @nodes.length > 1
return
for node in @nodes
if node?
#divide self activation by proper amount of possible edges,
#e.g. s+,r+ only has 3 possible outgoing edges, s-,s- only has two, otherwise we have 4
if "s" in node.csl_mode[0] and "s" in node.csl_mode[1]
divisor = 0.5
else if "s" in node.csl_mode[0] or "s" in node.csl_mode[1]
divisor = 1/3
else
divisor = 0.25
activation_in = 0
node.activation_self = divisor * node.exit_directions.reduce ((x, y) -> if y is 0 then x+1 else x), 0
if node.edges_out.length
for e in node.edges_out
if e?
activation_in += e.target_node.activation
activation_in /= node.edges_out.length
node.activation_tmp = node.activation_self * 0.7 + activation_in * 0.3
for node in @nodes
if node?
node.activation = node.activation_tmp
@arborGraph.getNode(node.name).data.activation = node.activation
return
#end class postureGraph
class abc
constructor: ->
@graph = arbor.ParticleSystem() # display graph, has its own nodes and edges and data for display
@graph.parameters # use center-gravity to make the graph settle nicely (ymmv)
repulsion: 1000 #500
stiffness: 100 #20
friction: 0.5
gravity: true
@graph.renderer = new simni.RendererSVG("#viewport_svg", @graph, @)
@posture_graph = new postureGraph(@graph) # posture graph, logical representation
@last_posture = null # the posture that was visited/created last (i.e. the "current" posture)
@previous_posture = null # the posture that was visited before
@trajectory = [] #last n state points
#defaults
@heuristic = "unseen"
@heuristic_keep_dir = false
@heuristic_keep_joint = true
@explore_active = false
@save_periodically = false
toggleExplore: =>
if not physics.upper_joint.csl_active
$("#toggle_csl").click()
@explore_active = not @explore_active
if @explore_active
console.log "start explore run at "+new Date
else
console.log "stop explore at "+new Date
searchSubarray: (sub, array, cmp) =>
#returns index(es) if subarray is found in array using cmp (list1, index1, list2, index2) as comparator
#otherwise false
found = []
for i in [0..array.length-sub.length] by 1
for j in [0..sub.length-1] by 1
unless cmp(sub,j, array,i+j)
break
if j == sub.length
found.push i
i = _i = i+sub.length
if found.length is 0
return false
else
return found
wrapAngle: (angle) =>
twoPi = 2*Math.PI
return angle - twoPi * (Math.floor( angle / twoPi))
wrapAngleManifold: (bodyangle) =>
#wrap angle in asymmetric range around 0, useful with manifold data
while bodyangle < -1.74*Math.PI
bodyangle+= 2*Math.PI
while bodyangle > 0.77*Math.PI
bodyangle-= 2*Math.PI
return bodyangle
smallestAngleDistance: (a1, a2) =>
#get the shorter of the positive or negative facing angle
angle = Math.PI - Math.abs(Math.abs(a1 - a2) - Math.PI)
MAX_UNIX_TIME = 1924988399 #31/12/2030 23:59:59
time = MAX_UNIX_TIME
detectAttractor: (body, upper_joint, lower_joint, action) =>
##detect if we are currently in a fixpoint or periodic attractor -> posture
p_body = @wrapAngle body.GetAngle() #p = φ
p_hip = upper_joint.GetJointAngle()
p_knee = lower_joint.GetJointAngle()
#find attractors, take a sample of trajectory and try to find it multiple times in the
#past trajectory (with threshold), hence (quasi)periodic behaviour
if @trajectory.length==10000 #corresponds to max periode duration that can be detected
@trajectory.shift()
@trajectory.push [p_body, p_hip, p_knee]
if @trajectory.length > 200 and (Date.now() - time) > 2000
#take last 50 points
last = @trajectory.slice(-50)
eps=0.01
d = @searchSubarray last, @trajectory, (a,i, b,j) ->
Math.abs(a[i][0] - b[j][0]) < eps and Math.abs(a[i][1] - b[j][1]) < eps and Math.abs(a[i][2] - b[j][2]) < eps
#console.log(d)
if d.length > 2 #need to find sample a few times to be periodic
#found a posture, call user method
configuration = @trajectory.pop()
action(configuration, @)
#get rid of saved trajectory
@trajectory = []
time = Date.now()
addEdge: (start_node, target_node, edge_list=start_node.edges_out) =>
#add an edge between two nodes in logical and drawing graphs, may add one or two new nodes to drawing graph
#(does some additional stuff over respective methods)
edge = new transition start_node, target_node
if not edge.isInList(edge_list) and @posture_graph.length() > 1 and not start_node.isEqualTo target_node
console.log("adding edge from posture " + start_node.name + " to posture: " + target_node.name)
#add new edge to logical graph
distance = target_node.body_x - start_node.body_x
edge.distance = distance
timedelta = target_node.timestamp - start_node.timestamp
edge.timedelta = timedelta
edge.csl_mode = target_node.csl_mode
edge_list.push edge
if edge_list.length > 4
console.log("warning: now more than 4 outgoing edges in " +start_node.name)
#target_node.edges_in.push edge
##create new edge in display graph
n0 = start_node.name
n1 = target_node.name
#position new node close to previous one (if there is one)
if @posture_graph.length() > 2
source_node = @graph.getNode n0
offset = 0.2
#randomly place left or right / above or below source node
if Math.floor(Math.random() * 2) then offset_x = offset else offset_x = -offset
if Math.floor(Math.random() * 2) then offset_y = offset else offset_y = -offset
nn1 = @graph.getNode(n1)
unless nn1?
nn1 = @graph.addNode n1
nn1.p.x = source_node.p.x + offset_x
nn1.p.y = source_node.p.y + offset_y
@graph.addEdge n0, n1,
distance: distance.toFixed(3)
timedelta: timedelta
label: @transition_mode
#if we're here for the first time, n0 is not yet initialized (this time addEdge adds two nodes)
if n0 == 1 and n1 == 2
init_node = @graph.getNode n0
init_node.data.label = start_node.csl_mode
init_node.data.number = start_node.name
init_node.data.activation = start_node.activation
init_node.data.positions = start_node.positions
init_node.data.configuration = start_node.configuration
init_node.data.subManifoldId = start_node.subManifoldId
init_node.data.visits = start_node.mean_n
source_node = @graph.getNode n0
@graph.current_node = current_node = @graph.getNode(n1)
current_node.data.label = target_node.csl_mode
current_node.data.number = target_node.name
current_node.data.positions = target_node.positions
current_node.data.configuration = target_node.configuration
current_node.data.activation = target_node.activation
current_node.data.subManifoldId = target_node.subManifoldId
current_node.data.visits = target_node.mean_n
source_node.data.activation = start_node.activation
#re-enable suspended graph layouting for a bit to find new layout
@graph.start(true)
@graph.renderer.click_time = Date.now()
switch_to_random_release_after_position: (joint) =>
@last_posture = null
@previous_posture = null
@graph.current_node = null
#set random release modes
which = Math.floor(Math.random()*2)
ui.set_csl_mode_upper(["r+", "r-"][which])
which = Math.floor(Math.random()*2)
ui.set_csl_mode_lower(["r+", "r-"][which])
#(assuming position controller is ON)
physics.togglePositionController(joint)
#enable csl again
$("#toggle_csl").click()
connectLastPosture: (p) =>
if @last_posture
@last_posture.exit_directions[@last_dir_index] = p.name
if p.name == @last_posture.name
console.log("warning: added self loop for posture "+ p.name)
#set/update body positions for svg drawing
p.positions = [physics.body.GetPosition(), physics.body2.GetPosition(), physics.body3.GetPosition()]
p.configuration = [@wrapAngle(physics.body.GetAngle()), physics.upper_joint.GetJointAngle(), physics.lower_joint.GetJointAngle()]
p.body_x = physics.body.GetWorldCenter().x
p.timestamp = Date.now()
#put node and edges into drawing graph
if @last_posture and @posture_graph.length() > 1
#refresh target position to current x for distance calc
@addEdge @last_posture, p
#update graph render stuff
a_p = @graph.getNode p.name
@graph.current_node = a_p
a_p.data.configuration = p.configuration
a_p.data.positions = p.positions
@graph.renderer.draw_once()
savePosture: (configuration, body, upper_csl, lower_csl) =>
if @manual_noop
return
#create temporary posture object
p = new posture(configuration, [upper_csl, lower_csl], body.GetWorldCenter().x)
p.positions = [physics.body.GetPosition(), physics.body2.GetPosition(), physics.body3.GetPosition()]
uj = physics.upper_joint
lj = physics.lower_joint
#if we have used the position controller to get to this posture, we now have to check if we're
#in the previously expected posture
if physics.upper_joint.position_controller_active and physics.lower_joint.position_controller_active and @last_expected_node
#quirk: expected node can have been saved with or without stall mode instead of contraction
#so we also compare with the expected mode (this gets into a lot of assuming...)
p_expect = new posture(configuration, @last_expected_node.csl_mode, body.GetWorldCenter().x)
if @last_expected_node.isClose(p) or @last_expected_node.isClose(p_expect)
#we're now in the expected node and reached it via position controller (should still be same fixpoint since
#proper body angle resulted from arm angles)
#set posture with the mode that the expected posture has
if not @last_expected_node.isClose(p) and @last_expected_node.isClose(p_expect)
p = p_expect
#disable position controller
physics.togglePositionController(uj)
physics.togglePositionController(lj)
console.log("collected node "+ @last_expected_node.name+" with position controller, back to csl")
#enable csl again
#this will/should use the same mode that didn't reach this node
ui.set_csl_mode_upper @last_expected_node.csl_mode[0]
ui.set_csl_mode_lower @last_expected_node.csl_mode[1]
$("#toggle_csl").click()
found = [@last_expected_node.name-1]
else
#this could also mean there is indeed another position in the
#same direction. could also be another situation now
console.log("warning, could not collect node with position controller. either we fell off the manifold or the context changed. continuing somewhere else.")
#try random csl mode from here and set last_posture etc to null
#(so that way we simply go on and don't make a connection to the next node)
@switch_to_random_release_after_position uj
@switch_to_random_release_after_position lj
@last_expected_node = null
@last_detected = null
@last_posture = null
return
@last_expected_node = null
#if we're trying to reach the closest position that is already saved, we need to catch it here
#also, if we are not in the possible posture, we have to create a new node at the one detected before (it is indeed new)
else if physics.upper_joint.position_controller_active and physics.lower_joint.position_controller_active and @last_test_posture
if p.isClose(@last_test_posture)
console.log("arrived in posture that was too far away for thresholding but was reachable")
found = [@last_test_posture.name-1]
#TODO: merge the previously detected posture and the one we have to get a mean
@last_test_posture = null
#re-enable csl
physics.togglePositionController(uj)
physics.togglePositionController(lj)
$("#toggle_csl").click()
else
#create previous posture as new node
console.log("candidate was not a reachable posture, creating new posture for previously found one")
node_name = @posture_graph.addPosture @last_detected
console.log("connecting new posture "+node_name+ " with previous posture "+@last_posture.name)
@connectLastPosture(@last_detected)
@drawManifoldStripe(p)
@graph.renderer.redraw()
#continue with csl whereever we landed instead
@switch_to_random_release_after_position uj
@switch_to_random_release_after_position lj
@last_posture = @posture_graph.getNodeByName(node_name)
@last_test_posture = null
@last_detected = null
return
#check if there is a posture that we would have expected from the last posture and the last direction we went
#TODO: this is using exit_directions while the node we actually went could be a wrong edge with
#a different target
expected_node = undefined
if @last_posture? and @last_dir_index? and @last_posture.exit_directions[@last_dir_index] > 0
expected_node = @posture_graph.getNodeByName(@last_posture.exit_directions[@last_dir_index])
#search for detected posture in all the nodes that we already have (using larger threshold)
if not found
found = @searchSubarray(p, @posture_graph.nodes, p.isCloseExplore)
if found.length and not expected_node?
parent = @
found.sort (a,b) ->
aa = parent.posture_graph.getNodeByIndex(a)
bb = parent.posture_graph.getNodeByIndex(b)
aa.euclidDistance(p) - bb.euclidDistance(p)
#if the closest existing posture is further away than safe error range (but it was found
#with the larger error range that isCloseExplore uses) we try to reach it with position controller
pp = @posture_graph.getNodeByIndex(found[0])
if thresholdDistance < pp.euclidDistance(p)
#enable position control
console.log("found an existing posture (" + pp.name + ") that is a bit far away but possibly right. trying with position controller if we can reach it from here.")
uj.set_position = pp.configuration[1]
lj.set_position = pp.configuration[2]
physics.togglePositionController(uj)
physics.togglePositionController(lj)
@last_test_posture = pp
@last_detected = p
return
#if we found no close posture (so we would create a new one) but expected one or the one we found is not the
#one we expected from the graph, we try to reach it explicitly (i.e. we are close to the attractor already)
if not found or (@last_posture? and expected_node? and
@posture_graph.getNodeByIndex(found[0]).name isnt expected_node.name)
if expected_node
console.log("we should have arrived in node "+ expected_node.name + ", but we didn't (or arrived too far away)")
console.log("trying to reach it with position controller")
#try to go to this posture with pos controller and see if next detected posture is the expected one
uj.set_position = expected_node.configuration[1]
lj.set_position = expected_node.configuration[2]
#deactivate csl
physics.togglePositionController(uj)
physics.togglePositionController(lj)
#found = [expected_node.name-1]
@last_expected_node = expected_node
return
else
#we didn't find a posture close to this one yet and we didn't expect another one, so add a new one
node_name = @posture_graph.addPosture p
@graph.renderer.redraw()
#we have this posture already, update it
if found.length
new_p = p
f = found[0]
p = @posture_graph.getNodeByIndex f
console.log("re-visiting node " + p.name)
#update to mean of old and current configurations
#(counter is used for proper weighting)
#body angle is wrapped because one turn around of one position and none of the other gives
#weird results
bodyAngle_p = @wrapAngle(p.configuration[0])
bodyAngle_new_p = @wrapAngle(new_p.configuration[0])
if (bodyAngle_p < 1 and bodyAngle_new_p > 6) or (bodyAngle_p > 6 and bodyAngle_new_p < 1)
#wrapping means we need to make sure that angles of e.g 0.1 and 6.2 don't produce wrong
#results (0.0something instead of 3.1), so don't calc any means for now
p.configuration[0] = bodyAngle_new_p
else
p.configuration[0] = (bodyAngle_new_p + bodyAngle_p*p.mean_n) / (p.mean_n+1)
p.configuration[1] = (new_p.configuration[1] + p.configuration[1]*p.mean_n) / (p.mean_n+1)
p.configuration[2] = (new_p.configuration[2] + p.configuration[2]*p.mean_n) / (p.mean_n+1)
p.mean_n += 1
#TODO: save scattering too
#make renderer draw updated semni posture
#(deleting will recreate)
n = @graph.getNode p.name
if n.data.semni
n.data.semni.remove()
n.data.semni = undefined