-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
2553 lines (2165 loc) · 79.1 KB
/
main.py
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
### IMPORT STATEMENTS
import math
import time
import threading
###GLOBAL CONSTANTS
PI, E, LIGHT = math.pi, math.e, 299458792.0
DEG = PI/180
INF=math.inf
pi, e, deg, inf = PI, E, DEG, INF
JIM = complex(0,1)
MIN_HELM = 0.0000001 * DEG # curves with less helm than this
# can be considered straight lines
LAND_PREC = 0.1 # Recommended value for Land.delta_space
INCH = 0.0254
FOOT = 12 * INCH
MILE = 5280 * FOOT
MINUTE = 60.0
HOUR = 60 * MINUTE
DAY = 24 * HOUR
MPH = float(MILE) / HOUR
GRAV = 9.81 # acceleration due to gravity at Earth's surface in SI units
### BASIC MATH FUNCTIONS
sin,cos,tan = math.sin, math.cos, math.tan
arcsin, arccos, arctan = math.asin, math.acos, math.atan
ln, exp = math.log, math.exp
def avg(v):
'''<List of Numbers>
Returns their Average (mean)'''
nu=float(sum(v))
return nu/len(v)
def sgn(x):
if x<0:
return -1
else:
return 1
def prod(v):
'''<List> of Numbers
Multiplies together every element in <v>
and returns the result'''
p = 1
for i in range(len(v)):
p *= v[i]
return p
def xmult(lhop, x1, x2):
'''《lHospital limit》, 《scalar》, 《other scalar》
lhop is winner of l'Hospital's
rule
Multiplies the two POSSIBLY INFINITE scalars 《x1》 and 《x2》 in accordance with whatever lHospital's rule
says 0*inf would be in this situation
'''
if 0 in [x1, x2]:
if inf in [x1, x2]:
return lhop
elif -inf in [x1, x2]:
return -1*lhop
else:
return x1*x2
else:
return x1*x2
def xdiv(lhop, nu, den):
'''《lHospital limit》, 《numerator》, 《denominator》
Similar to xmult, except it does Division rather than multiplication
Can divide by zero without a math error
'''
if abs(nu)==abs(den):
if abs(nu)==inf:
return sgn(nu)*sgn(den)*lhop
elif abs(nu)==0:
return lhop
elif den==0 and nu!=0:
return nu*inf
else:
return nu/den
def eul(theta):
'''<Angle> in radians
Returns phasor (complex number)
with magnitude of one and phase angle <theta>'''
return complex(cos(theta), sin(theta))
def conj(p):
'''<Complex Number>
Returns its Complex Conjugate'''
return complex(p.real , -1 * p.imag)
def angle(x):
'''<Complex number>
Returns the phase angle, in radians'''
k = xdiv(0, x.imag, x.real)
return arctan(k)
### COMPLEX CONSTANTS
#In this file, directions of helm
#will sometimes be stored as unit phasors
#for example 0+j will mean
#due right or due east.
#Sometimes these directions will be absolute to the land,
#other times they will be relative to the direction of the car
# ABSOLUTE DIRECTIONS
NORTH = eul(0)
EAST = eul(PI / 2)
SOUTH = eul(PI)
WEST = eul(-1 * PI / 2)
# RELATIVE DIRECTIONS
#These can be converted to absolute directions
#if you multiply them by an absolute direction phasor
#to "rotate" them.
#For example, NORTH * eul(30*DEG)
#means 30 degrees east of north.
O_CLOCK = eul(30 * DEG)
#People sometimes say "o'clock" as a unit of helm
#for example, "1 o'clock" means 30 degrees rightward
#O_CLOCK is that unit as a direction phasor
#To find the direction phasor for 2 o'clock,
#you would go
#O_CLOCK ** 2
ONE_DEGREE = eul( DEG ) #one degree rightwards as a direction phasor
ONE_RADIAN = eul(1) #one radian, as a direction phasor
### VECTOR FUNCTIONS
def funxvec(funx, v): #ver
'''<Python function> with one input, <List of Inputs>
Returns list of outputs of <funx> given the inputs in <v>
in corresponding order'''
vy = []
for i in range(len(v)):
a=v[i]
vy.append(funx(a))
return vy
def funxvec_2(funx, v1, v2): #ver
n=min(len(v1), len(v2))
vf=[]
for i in range(n):
a1, a2 = v1[i], v2[i]
f=funx(a1,a2)
vf.append(f)
return vf
def funxvec_3(funx, v1, v2, v3):
n = min(len(v1), len(v2))
n = min(n, len(v3))
vf = []
for i in range(n):
a1, a2, a3 = v1[i], v2[i], v3[i]
f = funx(a1, a2, a3)
vf.append( f )
return vf
def kvec(k, vec):
v=[]
for i in range(len(vec)):
v.append(k * vec[i])
return v
def podvec(el, size):
'''<Element Value>, <List Length>
Returns list with <size> elements, all with value of <el>'''
v=[]
for i in range(size):
v.append(el)
return v
def riemvec(ti, tf, n):
'''<Initial Value> on independent axis, <Final Value> on independent axis, <Desired number of subdivisions>
Returns Python list of midpoints on x-axis for the subdivisions of a Riemann sum
running from x=ti to x=tf'''
bar=float(tf-ti)/n
v=[]
for i in range(n):
n_x=0.5+i
x = ti + (bar * n_x)
v.append(x)
return v
def copy(vec ):
'''<Python list>
Returns copy of this list'''
v=[]
for i in range(len(vec)):
v.append(vec[i])
return v
def closest(x, vec):
'''<Number>, <List>
Returns value in <vec> closest to <x>'''
vm=[]
for i in range(len(vec)):
vm.append(abs(x-vec[i]))
ndx = vm.index(min(vm))
return vec[ndx]
def num_in_list(x, vec):
'''<Value>, <List>
Returns number of times <x> appears in <vec>'''
s=0
for a in vec:
s+=int(a==x)
return s
def val_index_vec(x, vec):
'''<Value>, <List>
Returns list with indices where <x> appears in <vec>'''
ret=[]
for i in range(len(vec)):
if vec[i]==x:
ret.append(i)
return ret
### MATH FUNCTIONS FOR THIS PROGRAM
#WARNING: curve_ray doesn't work on perfectly straight lines.
#It only works for curved paths.
def curve_ray(theta, kurv_r):
'''<Angle in radians> subtended by a curve, <Curvature Radius> of that curve
Returns phasor (complex number)
whose Magnitude is the distance from startpoint to endpoint of the curve as the crow flies,
and whose Angle is the angle of that ray WRT driver's direction at startpoint
'''
mag_ang = 0.5 * (PI - theta)
mag = 2 * kurv_r * cos(mag_ang)
return mag * eul(theta / 2)
def to_ray(d_path, helm):
'''#@param d_path is distance travelled on a path
#@param helm is the helm of a car travelling that path
#Returns phasor representing net displacement of that path,
#be it straight or curved'''
ang = d_path * helm
if ang < MIN_HELM:
return d_path * eul(0)
else:
return curve_ray( ang , 1.0/helm)
def unity(x):
'''<Number>
Returns number equal to <x>'''
return 1*x
def always_zero(x):
'''<Number>
Returns 0 no matter the input'''
return 0
def always_one(x):
'''<Number>
Returns 1 no matter what'''
return 1
def kinetic(mass, speed):
'''<Mass>, <Speed> of moving particle in SI units
Returns the Kinetic Energy of that particle, in watt-seconds
'''
return 0.5 * mass * (speed ** 2)
def velocity(speed, helm):
'''<Speed>, <Helm> of moving object in SI units
Returns phasor representing the velocity,
as a phasor representing the arc subtended in 1 second'''
if helm < MIN_HELM:
return speed * eul(0)
else:
return curve_ray(speed, speed * helm)
### FUNXION CLASS
class Funxion:
def __init__(self, funx, initial_val):
'''<Python function> with one arg, <Initial value> for Python function
The function <funx> represents a mathematical function. Instantiates
an object to hold that function, initialized to have input value <init_val>
'''
self.funx = funx
self.in_val = 0.0 + initial_val
self.out_val = self.funx(self.in_val)
self.name = "funxion"
self.BAR_NUM = 10000 #number of subdivisions for Riemann sum
def sweep(self):
self.out_val = self.funx(self.in_val)
def feed(self, new_val):
self.in_val = new_val
self.sweep()
def riemann(self, ti, tf, n):
'''<Initial Value> on independedt axis, <Final Value> on independent axis, <Number of Subdivisions> for Riemann sum
Returns Riemann sum, or approximate integral of funx(t) from t=ti to t=tf'''
vx=riemvec(ti,tf,n)
vy=funxvec(self.funx, vx)
yav=avg(vy)
return yav*(tf-ti)
def riem(self, ti, tf):
'''<Initial Value>, <Final Value>
Returns the approximate integral of self.funx(t)
from t = <ti> through t = <tf>'''
return self.riemann(ti, tf, self.BAR_NUM)
def deriv(self, delta_x):
'''<Differential on x-axis> object will use to find nearby values
Approximates the derivative of self.funx's mathematical equivalent
at t = self.in_val using nearby x-values'''
#delta_y = self.funx(self.in_val+delta_x) - self.funx(self.in_val-delta_x)
#print("\nself.in_val = "+str(self.in_val))
#print("\nself.out_val = "+str(self.out_val))
x_over, x_under = self.in_val + delta_x, self.in_val - delta_x
#print("\nx_over = "+str(x_over)+"\nx_under = "+str(x_under))
y_over, y_under = self.funx(x_over), self.funx(x_under)
#print("\ny_over = "+str(y_over)+"\ny_under = "+str(y_under))
ddx = float(y_over - y_under) / (x_over - x_under)
#print("\nddx = "+str(ddx)+"\n\n")
return ddx
def refunx(self, funx_new):
'''<Python function> to replace self.funx
Changes self.funx and readjusts this Funxion object accordingly'''
self.funx = funx_new
self.sweep()
def rename(self, new_name):
'''<String> representing new name
Renames this Funxion object'''
self.name = new_name
self.sweep()
def copy(self):
'''Creates new Funxion object with same instance variables as this one'''
return Funxion(self.funx, self.in_val)
def copy_name(self, new_name):
'''<String> representing name of new Funxion object
Copies this Funxion and gives that object its own name'''
fu = self.copy()
fu.rename(new_name)
def litter(self, n):
'''<Number of desired copies> of this Funxion object
Returns list of Funxion objects containing <n> copies of this Funxion object'''
v=[]
for i in range(n):
v.append(self.copy())
return v
def scale(self, k):
'''<Scaling factor>
Returns Funxion object just like this one,
except the encapsulated function is scaled by <k>'''
def dummy(x):
return k * self.funx(x)
fu = Funxion(dummy, self.in_val)
return fu
def scale_x(self, k):
'''<Scaling factor>
Returns Funxion object like this one
except shrunk in the x-axis by a factor of <k>'''
def dummy(x):
return self.funx(k * x )
return Funxion(dummy, self.in_val)
def shift(self, k):
'''<Number>
Returns Funxion object like this one,
except with <k> units aded to the output'''
def dummy(x):
return k + self.funx(x )
return Funxion(dummy, self.in_val)
def shift_x(self, k):
'''<NUmber>
Returns FUnxion object like this one,
but representing y(x + <k>) instead of y(x)'''
def dummy(x):
return self.funx(x + k )
return Funxion(dummy, self.in_val)
def tostring(self):
strega="Funxion "+self.name+" has these instance variables:\n"
strega+="self.in_val = "+str(self.in_val)+"\nself.out_val = "+str(self.out_val)
strega+="\nEND Funxion "+self.name+" instance variables\n\n"
return strega
### FUNCTIONS THAT RETURN FUNXION OBJECTS
def Konstant(k):
'''<Constant>
Returns a Funxion object
whose output is always <k>
'''
def dummy(x):
return k
fu = Funxion(dummy, 0)
return fu
def Ramp(k):
'''<Constant>
Returns Funxion object
representing the math function
y(x) = <k> * x'''
def dummy(x):
return k * unity( x )
fu = Funxion(dummy, 0)
return fu
def SumFunxion(funxionvec, in_val):
'''<List of Funxion objects>, <Initial Input Value>
Returns a Funxion object whose output is the sum
of several Funxion outpuots with the same input '''
def dummy(x):
yvec = []
for funxion in funxionvec:
#funxion.feed( x )
#yvec.append(funxion.out_val)
#EXPERIMENTAL LOOP BODY
yvec.append(funxion.funx(x))
#END EXPERIMENTAL LOOP BODY
return sum(yvec)
fu = Funxion(dummy, in_val)
return fu
def Linear(slope, y_i):
'''<slope>, <y-intercept>
Returns Funxion object encapsulating linear function
y = <y_i> + (<slope> * x)'''
fv = [Ramp(slope), Konstant(y_i)]
return SumFunxion(fv, 0)
def ProdFunxion(funxionvec, in_val):
'''<List of Funxion objects>, <Initial Input Value>
Returns a Funxion object whose output is the Product
of several Funxion outputs with the same input '''
def dummy(x):
yvec = []
for funxion in funxionvec:
#funxion.feed( x )
#yvec.append(funxion.out_val)
#EXPERIMENTAL LOOP BODY
yvec.append(funxion.funx(x))
#END EXPERIMENTAL LOOP BODY
return prod(yvec)
fu = Funxion(dummy, 0)
return fu
def CascadeFunxion(funxionvec, in_val):
'''<List of Funxions>, <Initial input>
Returns Function object representing nested/cascaded functions'''
def dummy(x):
y = 0 + x
fv=[]
for fui in funxionvec:
fv.append(fui.funx)
for f in fv:
y = f(y)
return y
return Funxion(dummy, in_val)
def Heav(x_rise):
'''<Rise time> on independent axis
Returns Funxion object representing unit step function
u(x - <x_rise>)'''
def dummy(x):
if x < x_rise:
return 0
else:
return 1
return Funxion(dummy, 0)
def Pulse(x_rise, x_fall):
'''<Rise TIme>, <Fall Time> on independent axis
Returns Funxion object representing rectangular pulse function'''
#return SumFunxion([Heav(x_rise), Heav(x_fall).scale(-1)], 0)
#EXPERIMENTAL CODE BODY:
def dummy(x):
return int((x >= x_rise) and (x_fall >= x))
return Funxion(dummy, 0)
def Exp():
'''Returns Funxion object representing
equation y(x) = e^x'''
return Funxion(exp, 0)
def Sinusoid(w, ph):
'''<Angular Frequency>, <Phase Shift>
Retuerns Funxion object representing sinusiodal function
y(x) = sin(<w> x + <ph> )'''
def dummy(x):
return sin( (w *x) + ph)
return Funxion(dummy, 0)
def Pow(k):
'''<Power or exponent>
Returns Funxion object representing equationy(x) = x ^ <k> '''
def dummy(x):
return x**k
return Funxion(dummy, 0)
def Log():
'''Returns Funxion object
representing natural logarithm function.
Uses absolute value of input to avoid crashing at negative numbers'''
def dummy(x):
return ln( abs(x) )
return Funxion(dummy, 1) #initial input is 1 instead of 0
def Periodic(funxion, period):
'''<Funxion object>, <Period>
Returns Funxion object representing periodic function
with period <period>,
defined as y(x) = <funxion>.funx(x) for 0 <= x <= <period>'''
def dummy(x):
return funxion.funx(x % period )
return Funxion(dummy, 0)
### OBSTACLE CLASS
class Obstacle:
def __init__(self, funxion, position):
'''<Funxion object> representing spatial borders of obstacle, <phasor> representing initial position of obstacle
Returns object representing
the space occupied by an obstacle or vehicle'''
self.name = "obstacle"
self.funxion = funxion
self.position = position
self.orientation = NORTH
def ray_length(self, theta):
'''<Angle> in radians
Returns distance from centerpoint to border at that angle'''
th = theta % (2 * PI)
return self.funxion.funx(th)
def left(self):
'''Returns direction phasor
of the direction Due Left of self.orientation'''
qt = O_CLOCK ** -3
return qt * self.orientation
def right(self):
'''Returns direction phasor
of the direction Due Right of self'''
qt = O_CLOCK ** 3
return qt * self.orientation
def move(self, delta_position):
'''<Phasor> representing change in position
Changes the absolute position of this object'''
self.position += delta_position
def turn(self, ang):
'''<Angle> in radians rightward
Rotates orientation of this Obstacle by <ang> radians'''
self.orientation *= eul(ang)
def within(self, p):
'''<Position phasor>
Retuns boolean indicating whether <p>
is within borders of this Obstacle'''
r = p - self.position
r_adj = r / self.orientation
ang = angle(r_adj)
return (self.ray_length(ang) >= abs(r))
def borderpt(self, ang):
'''<Angle>
Returns position phasor representing position phasor of point
on this Obstacle's border, <ang> radians rightward from self.orientatiin'''
pt = 0
pt += self.position
pt += self.ray_length(ang) * eul(ang)
return pt
def bordervec(self):
'''Returns list of points
on border of this Obstacle,
as position phasors'''
xv=riemvec(0, 2*PI, self.funxion.BAR_NUM)
k = 0 + xv[0]
for x in xv:
x -= k
return funxvec(self.funxion.funx, xv)
def copy(self):
'''Returns copy of this Obstacle objects'''
obst = Obstacle(self.funxion, self.position)
obst.name, obst.orientation = self.name, self.orientation
return obst
def rename(self, new_name):
self.name = new_name
def copy_name(self, new_name):
obst = self.copy()
obst.rename(new_name)
return obst
def tostring(self):
strega = "Obstacle "+self.name+" has these instance variables:\n"
strega += "self.funxion = "+str(self.funxion)+"\n"
strega += "self.position = "+str(self.position)+"\n"
strega += "self.orientation = "+str(self.orientation)+"\n"
strega += "END Obstacle "+self.name+" instance variables\n\n\n"
return strega
### FUNCTIONS THAT RETURN OBSTACLE OBJECTS
def Circle(radius):
'''<Radius> in meters open
Returns Obstacle object representing a circle shape
with radius <radius> meters'''
return Obstacle(Konstant(radius), 0 * eul(0))
def Rectangle(shape_length, shape_width):
'''<Length of rectangle> in direction flush with self.orientation, <Width of rectangle> in direction normal to self.orientation
Returns Obstacle object representing a rectangle
of length <shape_length> meters, width <shape_width> meters'''
def dummy(theta):
th_corn = arctan(shape_width / shape_length)
th = theta % PI
if th_corn < th < (PI - th_corn):
return math.hypot( 0.5*shape_width, 0.5*shape_length*cos(th))
else:
return math.hypot(0.5*shape_length, 0.5*shape_width*sin(th))
fu = Funxion(dummy, 0)
obst = Obstacle(fu, 0*eul(0))
return obst
### CAR CLASS
class Car:
def __init__(self, mass, pwr_drive, energy):
'''<Mass of car>, <Intentional power on car>, <Kinetic energy of car>'''
self.name = "car" #default name for this Car object
self.mass, self.pwr_drive, self.energy = mass, pwr_drive, energy
self.pos, self.delta_pos = 0*eul(0), 0*eul(0) #Position, change in position, stored as a phasor
self.stop = 0 #boolean true if car is intentionally stopped
self.brake = 0 #boolean true if driver is intentionally braking
self.rev = 0 #boolean true if car is in reverse
self.tilt = 0+0.0 #downward tilt of the ground, in radians, 0 for level ground
self.speed = (2.0 * self.energy /self.mass) ** (0.5) #speed, from kinetic energy
self.pwr_grav = GRAV * sin(self.tilt) * self.mass * self.speed #mechanical power due to gravity
self.pwr_other = 0 + 0.0 #mechanical power from sources other than gravity, the engine, or the brakes
self.pwr_net = self.pwr_drive + self.pwr_grav + self.pwr_other
# INSTANCE VARIABLES FOR DISTANCE CALCULATIONS
self.delta_path = 0 + 0.0 #Distance travelled along path in a short time
self.path = 0 + 0.0 #Total distance travelled since instantiation
self.helm = 0 + 0.0 #Horizontal driving angle, in Radians per Meter rightward
self.is_curved = abs(self.helm) > MIN_HELM #Boolean true if car's path is curved
self.delta_tau = 0 #Short time delay for actual time delays like time.sleep()
self.t_poll = 0.1 #Short time delay for predicting the car‘s motion
self.t_now = 0.0 #Amount of time car has been in motion
self.shape = Rectangle(5,3)
self.frix_coeff = 0 #Coefficient of kinetic friction
def sweep(self):
self.speed = (2.0 * self.energy / self.mass) ** (0.5)
self.brake = self.pwr_drive < 0 #Set brake flag if driver intentionally reducing energy
self.pwr_grav = GRAV * sin(self.tilt) * self.mass * self.speed
self.pwr_net = self.pwr_grav + self.pwr_drive + self.pwr_other
self.is_curved = abs(self.helm) > MIN_HELM
if (self.speed <= 0) and (sgn(self.pwr_drive) < 0) :
self.stop = 1
self.energy = 0.0
if (self.pwr_drive > 0 ):
self.stop = 0
self.brake = 0
self.shape.position = self.pos
#CODE NOTE: The instance variables self.path and self.delta_path are NOT affected
#by the sweep() method, because sweep method does not handle the motion itself
#and also because recalculating the displacement too often can lead to inaccurate results
def reverse(self):
'''Puts this Car in reverse'''
if not self.rev:
self.rev = 1
self.helm *= -1
self.tilt *= -1
self.pwr_grav *= -1
self.sweep()
def forward(self):
'''Takes this Car out of reverse'''
if self.rev:
self.rev = 0
self.helm *= -1
self.tilt *= -1
self.pwr_grav *= -1
self.sweep()
def friction(self, m_k):
'''<Coefficient of Kinetic Friction>
Calculates kinetic friction, in SI units, of this car
using the simple model taught in beginner-level physics'''
weight = GRAV * self.mass
weight *= cos( self.tilt )
return -1 * abs(m_k * weight * self.speed )
def frict_encaps(self, kin_energy):
'''<Kinetic Energy> in SI units
Car.friction method, but encapsulated
as a function for use in Funxion objects
NOTE : <kin_eneergy> is a dummy parameter,
this function actually does all calculations using instance variables'''
return self.friction(self.frix_coeff)
def respeed(self, new_speed):
'''<New speed> of Car in SI units
Changes the speed of the Car to <new_speed>
and adjusts instance variables to match.
WARNING: Does not simulate any motion,
just changes the speed'''
self.energy = kinetic(self.mass, new_speed)
self.sweep()
def other_fxn(self):
#Returns default Funxion object
#for use in Land objects
#to calculate self.pwr_other
#for this Car object
return Funxion(self.frict_encaps, self.energy)
def drive_fxn(self):
#Returns default Funxion object
#for use in Land objects
#to output self.pwr_drive
#for this Car object
def dummy(t):
return self.pwr_drive
return Funxion(dummy, self.t_now)
def go(self, delta_t):
'''<Short amount of time> in secondss
Changes the instance variables to model a small amount of motion in the car'''
energy_initial = 0.0 + self.energy
d_t = 0.0 + delta_t
self.sweep()
if (self.pwr_net < 0) and ( abs(self.pwr_net * delta_t) > energy_initial):
d_t = energy_initial / self.pwr_net #amount of time it will take car to stop at this rate
#time.sleep(self.delta_tau) #delay for threading purposes
self.energy += self.pwr_net * d_t
#
self.t_now += d_t
#
self.sweep()
energy_avg = avg([energy_initial, self.energy]) #average energy during the short time,
#assuming constant mechanical power throughout
speed_avg = (2.0 * energy_avg / self.mass) ** 0.5 #average speed during the time elapsed
self.delta_path = speed_avg * d_t
curv_theta = self.helm * self.delta_path
curv_rad = xdiv(0, 1.0, self.helm)
if not self.is_curved:
self.delta_pos = self.delta_path * eul(0)
else:
self.delta_pos = curve_ray(curv_theta, curv_rad)
if not self.rev:
self.path += self.delta_path
self.pos += self.delta_pos
#EXPERIMENTAL CODE
self.shape.move(self.delta_pos)
self.shape.turn(2 * angle(self.delta_pos))
#END EXPERIMENTAL CODE
else:
self.path -= self.delta_path
self.pos -= self.delta_pos
#EXPERIMENTAL CODE
self.shape.move(self.delta_pos)
self.shape.turn(2 * angle(self.delta_pos))
#END EXPERIMENTAL CODE
def go_drive(self, delta_t, drivefunxion):
'''<Amount of time> in seconds for motion, <Funxion objet> representing self.pwr_drive as a function of time
Uses self.go( <delta_t> ) but decides self.pwr_driveby the function encapsulated in <drivefunxion>'''
drivefunxion.feed(self.t_now)
self.pwr_drive = drivefunxion.out_val
self.sweep()
self.go(delta_t)
def go_other(self, drivefunxion, otherfunxion):
'''#@param drivefunxion is Funxion object
#representing self.pwr_drive as a function of Time
#@param otherfunxion is Funxion object
#representing self.pwr_other as a function of Energy'''
otherfunxion.feed(self.energy)
self.pwr_other = otherfunxion.out_val
self.sweep()
self.go_drive(self.t_poll, drivefunxion)
def travel_raw(self, dur, delta_t): #NOT RECOMMENDED
'''<Duration> in seconds, <Polling Period> in seconds
Iterates the method Car.go(delta_t) until <dur> seconds have supposedly elapsed'''
n = int(dur // delta_t)
for i in range(n):
self.go(delta_t)
def travel(self, dur, delta_t): #NOT RECOMMENDED
'''<Duration> in seconds, <Polling Period> in seconds
Iterates the method Car.go(float), but in a threaded manner,
so other code can execute at the same time'''
thr = threading.Thread(target = self.travel_raw, name = "", args = (dur, delta_t))
thr.start()
def to_speed_lin(self, speed_des, accel_time): #NOT RECOMMENDED
'''#@param speed_des is desired speed
#@param accel_time is time it should take to speed up/slow down
#Gets this Car object to desired speed
#by iterating Car.go( float ) at constant self.pwrdrive
'''
energy_des = 0.5 * self.mass * (speed_des ** 2)
energy_delta = energy_des - self.energy
pwr_net_des = energy_delta / accel_time
self.pwr_drive = pwr_net_des - self.pwr_grav - self.pwr_other
self.sweep()
self.travel(accel_time, self.t_poll)
def str_motion(self):
'''Returns string with attributes directly relevant to the motion'''
strega = ""
strega+="self.speed = "+str(self.speed)+" meters per second\n"
strega+="self.pwr_drive = "+str(self.pwr_drive)+" watts\n"
strega+="self.pwr_net = "+str(self.pwr_net)+" watts\n"
strega+="self.energy = "+str(self.energy)+" watt-seconds\n"
strega+="self.path = "+str(self.path)+" meters \n"
strega+="self.delta_path = "+str(self.delta_path)+" meters\n"
strega+="self.pos = "+str(self.pos)+" meters\n"
strega+="self.delta_pos = "+str(self.delta_pos)+" meters\n"
strega += "self.helm = "+str(self.helm)+" radians per meter\n"
strega += "self.t_now = "+str(self.t_now)+" seconds\n"
strega+="\n\n\n"
return strega
def str_power(self):
'''Returns string containig all the
power-related attributes of this Car object'''
strega = "self.pwr_net = "+str(self.pwr_net)+" watts\n"
strega += "self.pwr_drive = "+str(self.pwr_drive)+" watts\n"
strega += "self.pwr_other = "+str(self.pwr_other)+" watts\n"
strega += "self.pwr_grav = "+str(self.pwr_grav) +" watts\n"
strega += "self.tilt = "+str(self.tilt)+" radians downward\n"
strega += "self.energy = "+str(self.energy)+" watt-seconds\n"
strega += "self.t_now = "+str(self.t_now)+" seconds\n\n\n"
return strega
def str_time(self):
'''Returns string containing all time-related attributes
of this Car object'''
strega = "self.delta_tau = "+str(self.delta_tau)+" seconds of processor time\n"
strega += "self.t_poll = "+str(self.t_poll)+" seconds of simulated time\n"
strega += "self.t_now = "+str(self.t_now)+" seconds of simulated time\n\n\n"
return strega
def str_flags(self):
'''Returns string cpontaining all boolean attributes
of this Car object'''
strega = "self.brake = "+str(int(self.brake))+" boolean\n"
strega += "self.stop = "+str(int(self.stop)) +" boolean\n"
strega += "self.rev = "+str(int(self.rev)) +" boolean\n"
strega += "self.is_curved = "+str(int(self.is_curved))+" boolean\n\n\n"
return strega
def tell_motion_raw(self, dur, d_tau):
'''<Duration> in seconds you want to run this method, <Polling Period> in seconds between print statements
Prints attrributes related to the motion of the car every <d_tau> seconds,
until <dur> seconds have elapsed'''
n = int(dur // d_tau)
for i in range(n):
print(self.str_motion())
time.sleep(d_tau)
def tell_motion(self, dur, d_tau):
'''<Duration> in seconds this method will run, <Polling Period>
Prints attributes related to this Car's motion continually every <d_tau> seconds,
but in a threaded manner'''
thr = threading.Thread(target = self.tell_motion_raw, name="", args = (dur, d_tau))
thr.start()
def rename(self, new_name):
self.name = new_name
self.sweep()
def copy(self):
car = Car(self.mass, self.pwr_drive, self.energy)
car.stop, car.brake, car.rev = self.stop, self.brake, self.rev
car.tilt, car.helm = self.tilt, self.helm
car.pwr_grav, car.pwr_other, car.pwr_net = self.pwr_grav, self.pwr_other, self.pwr_net
car.speed, car.path, car.delta_path = self.speed, self.path, self.delta_path
car.pos, car.delta_pos = self.pos, self.delta_pos
car.is_curved, car.delta_tau = self.is_curved, self.delta_tau
car.t_poll, car.t_now = self.t_poll, self.t_now
car.frix_coeff, car.shape = car.frix_coeff, car.shape.copy()
return car
def copy_name(self, new_name):
'''<String> representing name of new object
Copies this Car object and gives resulting object a new name'''
car = self.copy()
car.rename(new_name)
return car
def tostring(self):
strega = "Car "+self.name+" has these attributes:\n"
strega += "self.energy = "+str(self.energy)+" watt-seconds\n"
strega += "self.pwr_drive = "+str(self.pwr_drive)+" watts\n"
strega += "self.pwr_grav = "+str(self.pwr_grav)+" watts\n"
strega += "self.pwr_other = "+str(self.pwr_other)+" watts\n"
strega += "self.pwr_net = "+str(self.pwr_net)+" watts\n"
strega += "self.tilt = "+str(self.tilt)+" radians downhill\n"
strega += "self.mass = "+str(self.mass)+" kilograms\n"
strega += "self.frix_coeff = "+str(self.frix_coeff)+" unitless\n"
strega += "self.stop = "+str(self.stop)+" boolean\n"
strega += "self.brake = "+str(self.brake)+" boolean\n"
strega += "self.rev = "+str(self.rev)+" boolean\n"
strega += "self.is_curved = "+str(self.is_curved)+" boolean\n"
strega += "self.helm = "+str(self.helm)+" radians per meter rightward\n"
strega += "self.path = "+str(self.path)+" meters\n"
strega += "self.pos = "+str(self.pos)+" meters\n"
strega += "self.speed = "+str(self.speed)+" meters per second\n"
strega += "self.delta_path = "+str(self.delta_path)+" meters\n"
strega += "self.delta_pos = "+str(self.delta_pos)+" meters\n"
strega += "self.delta_tau = "+str(self.delta_tau)+" seconds\n"
strega += "self.t_poll = "+str(self.t_poll)+ " seconds\n"
strega += "self.t_now = "+str(self.t_now)+" seconds\n"
strega += "self.shape = "+str(self.shape)+" Obstacle object\n"
strega += "END Car "+self.name+" attributes\n\n\n"
return strega
### CAR - RETURNING FUNCTIONS
def Blankcar():
'''Returns Car with default instance variables'''
return Car(1000,0,0)