-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqsm_old.py
1324 lines (1105 loc) · 69.1 KB
/
qsm_old.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: nico
"""
##%kinematics
import os
import csv
os.environ['QTA_QP_PLATFORM'] = 'wayland'
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from matplotlib import animation
import functools
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from python_tools_master import insect_tools as it
from python_tools_master import wabbit_tools as wt
from usefulFunctions import writeArraytoFile
from datetime import datetime
import time
start_main = time.time()
#different cfd runs: #'phi120.00_phim20.00_dTau0.05' #'phi129.76_phim10.34_dTau0.00' #'intact_wing_phi120.00_phim20.00_dTau0.05'
cfd_run = 'intact_wing_phi120.00_phim20.00_dTau0.05'
def main(cfd_run, folder_name):
#timestamp variable for saving figures with actual timestamp
now = datetime.now()
rightnow = now.strftime(" %d-%m-%Y_%I:%M:%S")+".png"
#global variables:
isLeft = wt.get_ini_parameter(cfd_run+'/PARAMS.ini', 'Insects', 'LeftWing', dtype=bool)
wingShape = wt.get_ini_parameter(cfd_run+'/PARAMS.ini', 'Insects', 'WingShape', dtype=str)
if 'from_file' in wingShape:
wingShape_file = os.path.join(cfd_run, wingShape.replace('from_file::', ''))
time_max = wt.get_ini_parameter(cfd_run+'/PARAMS.ini', 'Time', 'time_max', dtype=float)
u_infty = wt.get_ini_parameter(cfd_run+'/PARAMS.ini', 'ACM-new', 'u_mean_set', dtype=str)
u_infty = u_infty.split(' ')
u_infty = -np.float_(u_infty)
# for component in u_infty:
# float(component)
# u_infty = np.array(u_infty)
kinematics_file = wt.get_ini_parameter(cfd_run+'/PARAMS.ini', 'Insects', 'FlappingMotion_right', dtype=str)
if 'from_file' in kinematics_file:
kinematics_file = os.path.join(cfd_run, kinematics_file.replace('from_file::', ''))
xc, yc = it.visualize_wing_shape_file(wingShape_file)
zc = np.zeros_like(xc)
wingPoints = np.vstack([xc, yc, zc])
wingPoints = np.transpose(wingPoints)
hinge_index = np.argmin(wingPoints[:, 1])
wingtip_index = np.argmax(wingPoints[:, 1])
# #print wing to check positioning and indexing
# plt.figure()
# plt.scatter(wingPoints[:, 0], wingPoints[:, 1])
# i = 0
# for wingPoint in wingPoints:
# plt.text(wingPoint[0], wingPoint[1], str(i))
# i += 1
# plt.xlim([-4,4])
# plt.ylim([-4,4])
# plt.show()
# exit()
# spanwise normalization
min_y = np.min(wingPoints[:, 1])
max_y = np.max(wingPoints[:, 1])
R = max_y-min_y #nonnormalized wing radius
e_wingPoints = wingPoints/R #normalization of the wing points
min_y = np.min(e_wingPoints[:, 1])
max_y = np.max(e_wingPoints[:, 1])
e_R = max_y - min_y #e_R = 1
# #load kinematics data by means of eval_angles_kinematics_file function from insect_tools library
# timeline, phis, alphas, thetas = it.eval_angles_kinematics_file(fname=kinematics_file, time=np.linspace(0.0, 1.0, 101)[:-1]) #time=np.linspace(0.0, 1.0, 101))
# phis = np.radians(phis)
# alphas = np.radians(alphas)
# thetas = np.radians(thetas)
# #timeline reassignment. when the timeline from the function eval_angles_kinematics_file is used
# #the last point of the timeline is the same as the first one (so value[0] = value[-1]) which creates a redundancy because
# #in python value[-1] = value[last]. when calculating time derivatives this redundancy jumbles up the code
# #to solve this we can either do a variable reassigment or remove the last entry in timeline. the second method
# #leaves you with one less point in the array. the first method is preferred.
# timeline = np.linspace(0, 1, timeline.shape[0])
# nt = timeline.shape[0] #number of timesteps
# load kinematics data by means of load_t_file function from insect_tools library
kinematics_cfd = it.load_t_file(cfd_run+'/kinematics.t')
timeline = kinematics_cfd[:,0].flatten()
psis_it = kinematics_cfd[:, 4].flatten()
betas_it = kinematics_cfd[:, 5].flatten()
gammas_it = kinematics_cfd[:, 6].flatten()
etas_it = kinematics_cfd[:, 7].flatten()
if isLeft==0:
alphas_it = kinematics_cfd[:,11].flatten()
phis_it = kinematics_cfd[:,12].flatten()
thetas_it = kinematics_cfd[:,13].flatten()
else:
alphas_it = kinematics_cfd[:, 8].flatten()
phis_it = kinematics_cfd[:, 9].flatten()
thetas_it = kinematics_cfd[:, 10].flatten()
#interpolate psi, beta, gamma, alpha, phi and theta with respect to the original timeline
psis_interp = interp1d(timeline, psis_it, fill_value='extrapolate')
betas_interp = interp1d(timeline, betas_it, fill_value='extrapolate')
gammas_interp = interp1d(timeline, gammas_it, fill_value='extrapolate')
etas_interp = interp1d(timeline, etas_it, fill_value='extrapolate')
alphas_interp = interp1d(timeline, alphas_it, fill_value='extrapolate')
phis_interp = interp1d(timeline, phis_it, fill_value='extrapolate')
thetas_interp = interp1d(timeline, thetas_it, fill_value='extrapolate')
#timeline downsizing
timeline = np.linspace(0, 1, 101)
psis = psis_interp(timeline)[:-1]
betas = betas_interp(timeline)[:-1]
gammas = gammas_interp(timeline)[:-1]
etas = etas_interp(timeline)[:-1]
alphas = alphas_interp(timeline)[:-1]
phis = phis_interp(timeline)[:-1]
thetas = thetas_interp(timeline)[:-1]
timeline = np.linspace(0, 1, 100)
nt = timeline.shape[0] #number of timesteps
#here all of the required variable arrays are created to match the size of the timeline
#since for every timestep each variable must be computed. this will happen in 'generateSequence'
alphas_dt_sequence = np.zeros((nt))
phis_dt_sequence = np.zeros((nt))
thetas_dt_sequence = np.zeros((nt))
alphas_dt_dt_sequence = np.zeros((nt))
phis_dt_dt_sequence = np.zeros((nt))
thetas_dt_dt_sequence = np.zeros((nt))
strokePointsSequence = np.zeros((nt, wingPoints.shape[0], 3))
bodyPointsSequence = np.zeros((nt, wingPoints.shape[0], 3))
globalPointsSequence = np.zeros((nt, wingPoints.shape[0], 3))
rots_wing_b = np.zeros((nt, 3, 1))
rots_wing_s = np.zeros((nt, 3, 1))
rots_wing_w = np.zeros((nt, 3, 1))
rots_wing_g = np.zeros((nt, 3, 1))
planar_rots_wing_s = np.zeros((nt, 3, 1))
planar_rots_wing_w = np.zeros((nt, 3, 1))
planar_rots_wing_g = np.zeros((nt, 3, 1))
planar_rot_acc_wing_g = np.zeros((nt, 3, 1))
planar_rot_acc_wing_w = np.zeros((nt, 3, 1))
planar_rot_acc_wing_s = np.zeros((nt, 3, 1))
blade_acc_wing_w = np.zeros((nt, 3))
us_wing_w = np.zeros((nt, 3, 1))
us_wing_g = np.zeros((nt, 3, 1))
us_wing_g_magnitude = np.zeros((nt))
acc_wing_w = np.zeros((nt, 3, 1))
acc_wing_g = np.zeros((nt, 3, 1))
rot_acc_wing_g = np.zeros((nt, 3, 1))
rot_acc_wing_w = np.zeros((nt, 3, 1))
us_wind_w = np.zeros((nt, 3, 1))
AoA = np.zeros((nt, 1))
e_dragVectors_wing_g = np.zeros((nt, 3))
liftVectors = np.zeros((nt, 3))
e_liftVectors_g = np.zeros((nt, 3))
y_wing_g_sequence = np.zeros((nt, 3))
z_wing_g_sequence = np.zeros((nt, 3))
y_wing_s_sequence = np.zeros((nt, 3))
y_wing_w_sequence = np.zeros((nt, 3))
z_wing_w_sequence = np.zeros((nt, 3))
e_Fam = np.zeros((nt, 3))
wingRotationMatrix_sequence = np.zeros((nt, 3, 3))
wingRotationMatrixTrans_sequence = np.zeros((nt, 3, 3))
strokeRotationMatrix_sequence = np.zeros((nt, 3, 3))
strokeRotationMatrixTrans_sequence = np.zeros((nt, 3, 3))
bodyRotationMatrix_sequence = np.zeros((nt, 3, 3))
bodyRotationMatrixTrans_sequence = np.zeros((nt, 3, 3))
rotationMatrix_g_to_w = np.zeros((nt, 3, 3))
rotationMatrix_w_to_g = np.zeros((nt, 3, 3))
lever = np.zeros((nt))
lever_g = np.zeros((nt))
lever_w = np.zeros((nt))
lever_w_average = 0
delta_t = timeline[1] - timeline[0]
if isLeft == 0:
print('The parsed data correspond to the right wing.')
# forces_CFD = it.load_t_file('cfd_run/forces_rightwing.t', T0=[1.0,2.0])
forces_CFD = it.load_t_file(cfd_run+'/forces_rightwing.t', T0=[1.0, 3.0])
t = forces_CFD[:, 0]-1.0
Fx_CFD_g = forces_CFD[:, 1]
Fy_CFD_g = forces_CFD[:, 2]
Fz_CFD_g = forces_CFD[:, 3]
# moments_CFD = it.load_t_file('cfd_run/moments_rightwing.t', T0=[1.0, 2.0])
moments_CFD = it.load_t_file(cfd_run+'/moments_rightwing.t', T0=[1.0, 3.0])
#no need to read in the time again as it's the same from the forces file
Mx_CFD_g = moments_CFD[:, 1]
My_CFD_g = moments_CFD[:, 2]
Mz_CFD_g = moments_CFD[:, 3]
# M_CFD = moments_CFD[:, 1:4]
else:
# forces_CFD = it.load_t_file('cfd_run/forces_rightwing.t', T0=[1.0,2.0])
forces_CFD = it.load_t_file(cfd_run+'/forces_leftwing.t', T0=[1.0, 3.0])
t = forces_CFD[:, 0]-1.0
Fx_CFD_g = forces_CFD[:, 1]
Fy_CFD_g = forces_CFD[:, 2]
Fz_CFD_g = forces_CFD[:, 3]
# moments_CFD = it.load_t_file('cfd_run/moments_rightwing.t', T0=[1.0, 2.0])
moments_CFD = it.load_t_file(cfd_run+'/moments_leftwing.t', T0=[1.0, 3.0])
#no need to read in the time again as it's the same from the forces file
Mx_CFD_g = moments_CFD[:, 1]
My_CFD_g = moments_CFD[:, 2]
Mz_CFD_g = moments_CFD[:, 3]
# M_CFD = moments_CFD[:, 1:4]
print('The parsed data correspond to the left wing.')
# power_CFD = it.load_t_file('cfd_run/aero_power.t', T0=[1.0, 2.0])
power_CFD = it.load_t_file(cfd_run+'/aero_power.t', T0=[1.0, 3.0])
#no need to read in the time again as it's the same from the forces file
P_CFD = power_CFD[:, 1]
# print(time_max)
# print(np.round(forces_CFD[-1, 0]))
# exit()
# if np.round(forces_CFD[-1, 0]) != time_max:
# raise ValueError('CFD cycle number does not match that of the actual run. Check your PARAMS, forces and moments files\n')
# print('The number of cycles is ', time_max, '. The forces and moments data were however only sampled for ', np.round(t[-1]), ' cycle(s)') #a cycle is defined as 1 downstroke + 1 upstroke ; cycle duration is 1.0 seconds.
Fx_CFD_g_interp = interp1d(t, Fx_CFD_g, fill_value='extrapolate')
Fy_CFD_g_interp = interp1d(t, Fy_CFD_g, fill_value='extrapolate')
Fz_CFD_g_interp = interp1d(t, Fz_CFD_g, fill_value='extrapolate')
F_CFD_g = np.vstack((Fx_CFD_g_interp(timeline), Fy_CFD_g_interp(timeline), Fz_CFD_g_interp(timeline))).transpose()
Mx_CFD_g_interp = interp1d(t, Mx_CFD_g, fill_value='extrapolate')
My_CFD_g_interp = interp1d(t, My_CFD_g, fill_value='extrapolate')
Mz_CFD_g_interp = interp1d(t, Mz_CFD_g, fill_value='extrapolate')
M_CFD_g = np.vstack((Mx_CFD_g_interp(timeline), My_CFD_g_interp(timeline), Mz_CFD_g_interp(timeline))).transpose()
P_CFD_interp = interp1d(t, P_CFD, fill_value='extrapolate')
Fx_CFD_w = np.zeros((t.shape[0]))
Fy_CFD_w = np.zeros((t.shape[0]))
Fz_CFD_w = np.zeros((t.shape[0]))
F_CFD_w = np.zeros((nt, 3))
Fz_CFD_w_vector = np.zeros((nt, 3))
# Mx_CFD_w = np.zeros((t.shape[0]))
# My_CFD_w = np.zeros((t.shape[0]))
# Mz_CFD_w = np.zeros((t.shape[0]))
# Mx_CFD_w_vector = np.zeros((nt, 3))
M_CFD_w = np.zeros((nt, 3))
#QSM moment components used in cost_moments. these will be the optimized moments that best fit the CFD ones
Mx_QSM_w = np.zeros((nt))
My_QSM_w = np.zeros((nt))
Mz_QSM_w = np.zeros((nt))
#QSM moment components used in cost_power. these will be used to calculate the power that best fits the CFD one.
Mx_QSM_w_power = np.zeros((nt))
My_QSM_w_power = np.zeros((nt))
Mz_QSM_w_power = np.zeros((nt))
Mx_QSM_g = np.zeros((nt))
P_QSM_nonoptimized = np.zeros((nt))
P_QSM = np.zeros((nt))
#gloabl reference frame
Ftc = np.zeros((nt, 3))
Ftd = np.zeros((nt, 3))
Frc = np.zeros((nt, 3))
Fam = np.zeros((nt, 3))
Frd = np.zeros((nt, 3))
Fwe = np.zeros((nt, 3))
#wing reference frame
Ftc_w = np.zeros((nt, 3))
Ftd_w = np.zeros((nt, 3))
Frc_w = np.zeros((nt, 3))
Fam_w = np.zeros((nt, 3))
Frd_w = np.zeros((nt, 3))
Fwe_w = np.zeros((nt, 3))
F_QSM_w = np.zeros((nt, 3))
F_QSM_w_2 = np.zeros((nt, 3))
F_QSM_g = np.zeros((nt, 3))
Fz_QSM_w_vector = np.zeros((nt, 3))
F_QSM_gg = np.zeros((nt, 3))
Ftc_magnitude = np.zeros(nt)
Ftd_magnitude = np.zeros(nt)
Frc_magnitude = np.zeros(nt)
Fam_magnitude = np.zeros(nt)
Frd_magnitude = np.zeros(nt)
Fwe_magnitude = np.zeros(nt)
#this function calculates the chord length by splitting into 2 segments (LE and TE segment) and then interpolating along the y-axis
def getChordLength(wingPoints, y_coordinate):
#get the division in wing segments (leading and trailing)
split_index = wingtip_index
righthand_section = wingPoints[:split_index]
lefthand_section = wingPoints[split_index:]
#interpolate righthand section
righthand_section_interpolation = interp1d(righthand_section[:, 1], righthand_section[:, 0], fill_value='extrapolate')
#interpolate lefthand section
lefthand_section_interpolation = interp1d(lefthand_section[:, 1], lefthand_section[:, 0], fill_value='extrapolate')
#generate the chord as a function of y coordinate
chord_length = abs(righthand_section_interpolation(y_coordinate) - lefthand_section_interpolation(y_coordinate))
return chord_length
#the convert_from_*_reference_frame_to_* functions convert points from one reference frame to another
#they take the points and the parameter list (angles) as arguments.
#the function first calculates the rotation matrix and its transpose, and then multiplies each point with the tranpose since
#by convention in this code we derotate as we start out with wing points and they must be converted down to global points.
#this function returns the converted points as well as the rotation matrix and its tranpose
def convert_from_wing_reference_frame_to_stroke_plane(points, parameters):
#points passed into this fxn must be in the wing reference frame x(w) y(w) z(w)
#phi, alpha, theta
phi = parameters[4] #rad
alpha = parameters[5] #rad
theta = parameters[6] #rad
if isLeft == 0:
phi = -phi
alpha = -alpha
rotationMatrix = np.matmul(
np.matmul(
it.Ry(alpha),
it.Rz(theta)
),
it.Rx(phi)
)
rotationMatrixTrans = np.transpose(rotationMatrix)
strokePoints = np.zeros((points.shape[0], 3))
for point in range(points.shape[0]):
x_s = np.matmul(rotationMatrixTrans, points[point])
strokePoints[point, :] = x_s
return strokePoints, rotationMatrix, rotationMatrixTrans
def convert_from_stroke_plane_to_body_reference_frame(points, parameters):
#points must be in stroke plane x(s) y(s) z(s)
eta = parameters[3] #rad
flip_angle = 0
if isLeft == 0:
flip_angle = np.pi
rotationMatrix = np.matmul(
it.Rx(flip_angle),
it.Ry(eta)
)
rotationMatrixTrans = np.transpose(rotationMatrix)
bodyPoints = np.zeros((points.shape[0], 3))
for point in range(points.shape[0]):
x_b = np.matmul(rotationMatrixTrans, points[point])
bodyPoints[point,:] = x_b
return bodyPoints, rotationMatrix, rotationMatrixTrans
def convert_from_body_reference_frame_to_global_reference_frame(points, parameters):
#points passed into this fxn must be in the body reference frame x(b) y(b) z(b)
#phi, alpha, theta
psi = parameters [0] #rad
beta = parameters [1] #rad
gamma = parameters [2] #rad
rotationMatrix = np.matmul(
np.matmul(
it.Rx(psi),
it.Ry(beta)
),
it.Rz(gamma)
)
rotationMatrixTrans = np.transpose(rotationMatrix)
globalPoints = np.zeros((points.shape[0], 3))
for point in range(points.shape[0]):
x_g = np.matmul(rotationMatrixTrans, points[point])
globalPoints[point, :] = x_g
return globalPoints, rotationMatrix, rotationMatrixTrans
#generate rot wing calculates the angular velocity of the wing in all reference frames, as well as the planar angular velocity {𝛀(φ,Θ)}
#which will later be used to calculate the forces on the wing. planar angular velocity {𝛀(φ,Θ)} comes from the decomposition of the motion
#into 'translational' and rotational components, with the rotational component beig defined as ⍺ (the one around the y-axis in our convention)
#this velocity is obtained by setting ⍺ to 0, as can be seen below
def generate_rot_wing(wingRotationMatrix, bodyRotationMatrixTrans, strokeRotationMatrixTrans, phi, phi_dt, alpha, alpha_dt, theta, theta_dt):
if isLeft == 0:
phi = -phi
phi_dt = -phi_dt
alpha = -alpha
alpha_dt = -alpha_dt
phiMatrixTrans = np.transpose(it.Rx(phi)) #np.transpose(getRotationMatrix('x', phi))
alphaMatrixTrans = np.transpose(it.Ry(alpha)) #np.transpose(getRotationMatrix('y', alpha))
thetaMatrixTrans = np.transpose(it.Rz(theta)) #np.transpose(getRotationMatrix('z', theta))
vector_phi_dt = np.array([[phi_dt], [0], [0]])
vector_alpha_dt = np.array([[0], [alpha_dt], [0]])
vector_theta_dt = np.array([[0], [0], [theta_dt]])
rot_wing_s = np.matmul(phiMatrixTrans, (vector_phi_dt+np.matmul(thetaMatrixTrans, (vector_theta_dt+np.matmul(alphaMatrixTrans, vector_alpha_dt)))))
rot_wing_w = np.matmul(wingRotationMatrix, rot_wing_s)
rot_wing_b = np.matmul(strokeRotationMatrixTrans, rot_wing_s)
rot_wing_g = np.matmul(bodyRotationMatrixTrans, rot_wing_b)
planar_rot_wing_s = np.matmul(phiMatrixTrans, (vector_phi_dt+np.matmul(thetaMatrixTrans, (vector_theta_dt))))
planar_rot_wing_w = np.matmul(wingRotationMatrix, np.matmul(phiMatrixTrans, (vector_phi_dt+np.matmul(thetaMatrixTrans, (vector_theta_dt)))))
planar_rot_wing_g = np.matmul(bodyRotationMatrixTrans, np.matmul(strokeRotationMatrixTrans, np.matmul(phiMatrixTrans, (vector_phi_dt+np.matmul(thetaMatrixTrans, (vector_theta_dt))))))
return rot_wing_g, rot_wing_b, rot_wing_s, rot_wing_w, planar_rot_wing_g, planar_rot_wing_s, planar_rot_wing_w #these are all (3x1) vectors
#since the absolute linear velocity of the wing depends both on time and on the position along the wing
#this function calculates only its position dependency
def generate_u_wing_g_position(rot_wing_g, y_wing_g):
# #omega x point
#both input vectors have to be reshaped to (1,3) to meet the requirements of np.cross (last axis of both vectors -> 2 or 3). to that end either reshape(1,3) or flatten() kommen in frage
u_wing_g_position = np.cross(rot_wing_g, y_wing_g)
return u_wing_g_position
#this function calculates the linear velocity of the wing in the wing reference frame
def generate_u_wing_w(u_wing_g, bodyRotationMatrix, strokeRotationMatrix, wingRotationMatrix):
# rotationMatrix = np.matmul(np.matmul(bodyRotationMatrix, strokeRotationMatrix), wingRotationMatrix)
# rotationMatrix = np.matmul(wingRotationMatrix, np.matmul(strokeRotationMatrix, bodyRotationMatrix))
# u_wing_w = np.matmul(rotationMatrix, u_wing_g)
u_wing_w = np.matmul(wingRotationMatrix, np.matmul(strokeRotationMatrix, np.matmul(bodyRotationMatrix, u_wing_g)))
return u_wing_w
def getWindDirectioninWingReferenceFrame(u_flight_g, bodyRotationMatrix, strokeRotationMatrix, wingRotationMatrix):
u_wind_b = np.matmul(bodyRotationMatrix, u_flight_g.reshape(3,1))
u_wind_s = np.matmul(strokeRotationMatrix, u_wind_b)
u_wind_w = np.matmul(wingRotationMatrix, u_wind_s)
return u_wind_w
#in this code AoA is defined as the arccos of the dot product between the unit vector along x direction and the unit vector of the absolute linear velocity
def getAoA(x_wing_g, e_u_wing_g):
AoA = np.arccos(np.dot(x_wing_g, e_u_wing_g))
return AoA
# #alternative definition of AoA
# def getAoA(e_u_wing_g, x_wing_g):
# #should be in the wing reference frame
# AoA = np.arctan2(np.linalg.norm(np.cross(x_wing_g, e_u_wing_g)), np.dot(e_u_wing_g, x_wing_g.reshape(3,1))) #rad
# return AoA
# def getLever(F, M):
# #to find the lever we need to solve for r: M = r x F. however since no inverse of the cross product exists, we have to use the vector triple product
# #what we get is: r = M x F/norm(F)^2 + t*F ; where * is the dot product and t is any constant. for this equation to be valid, F and M must be orthogonal to each other,
# #such that F*M = 0
# for i in range(nt):
# F_norm = (np.linalg.norm(F, axis=1))
# if F_norm[i] != 0:
# lever[i, :] = np.cross(F[i, :], M[i, :])/F_norm[i]**2 + 0*F[i, :] #here I take t = 0
# else:
# lever[i, :] = 0
# return lever
def getLever(M, F):
#to find the lever we need to solve for r: M = r x F. however since no inverse of the cross product exists, we have to use the vector triple product
#what we get is: r = M x F/norm(F)^2 + t*F ; where * is the dot product and t is any constant. for this equation to be valid, F and M must be orthogonal to each other,
#such that F*M = 0
for i in range(nt):
F_norm = (np.linalg.norm(F, axis=1))
if F_norm[i] != 0:
lever[i, :] = np.cross(M[i, :], F[i, :])/F_norm[i]**2 + 0*F[i, :] #here I take t = 0
else:
lever[i, :] = 0
return lever
def animationPlot(ax, timeStep):
#get point set by timeStep number
points = globalPointsSequence[timeStep] #pointsSequence can either be global, body, stroke
#clear the current axis
ax.cla()
# extract the x, y and z coordinates
X = points[:, 0]
Y = points[:, 1]
Z = points[:, 2]
#axis limit
a = 2
trajectory = np.array(globalPointsSequence)[:, wingtip_index]
#3D trajectory
ax.plot(trajectory[:, 0], trajectory[:, 1], trajectory[:, 2], color='k', linestyle='dashed', linewidth=0.5)
#x-y plane trajectory
ax.plot(trajectory[:, 0], trajectory[:, 1], 0*trajectory[:, 2]-a, color='k', linestyle='dashed', linewidth=0.5)
#z-y plane prajectory
ax.plot(0*trajectory[:, 0]-a, trajectory[:, 1], trajectory[:, 2], color='k', linestyle='dashed', linewidth=0.5)
#x-z plane prajectory
ax.plot(trajectory[:, 0], 0*trajectory[:, 1]+a, trajectory[:, 2], color='k', linestyle='dashed', linewidth=0.5)
# #use axis to plot surface
ax.plot_trisurf(X, Y, Z, edgecolors='black')
ax.add_collection3d(Poly3DCollection(verts=[points]))
#shadows
#x-y plane shadow
XY_plane_shadow = np.vstack((X, Y, -a*np.ones_like(Z))).transpose()
ax.add_collection3d(Poly3DCollection(verts=[XY_plane_shadow], color='#d3d3d3'))
#y-z plane shadow
YZ_plane_shadow = np.vstack((-a*np.ones_like(X), Y, Z)).transpose()
ax.add_collection3d(Poly3DCollection(verts=[YZ_plane_shadow], color='#d3d3d3'))
#x-z plane shadow
XZ_plane_shadow = np.vstack(((X, a*np.ones_like(Y), Z))).transpose()
ax.add_collection3d(Poly3DCollection(verts=[XZ_plane_shadow], color='#d3d3d3'))
#velocity at wingtip
u_wing_g_plot = us_wing_g[timeStep]
ax.quiver(X[wingtip_index], Y[wingtip_index], Z[wingtip_index], u_wing_g_plot[0], u_wing_g_plot[1], u_wing_g_plot[2], color='orange', label=r'$\overrightarrow{u}^{(g)}_w$' )
#drag
e_dragVector_wing_g = e_dragVectors_wing_g[timeStep]
ax.quiver(X[wingtip_index], Y[wingtip_index], Z[wingtip_index], e_dragVector_wing_g[0], e_dragVector_wing_g[1], e_dragVector_wing_g[2], color='green', label=r'$\overrightarrow{e_D}^{(g)}_w$' )
#lift
liftVector_g = e_liftVectors_g[timeStep]
ax.quiver(X[wingtip_index], Y[wingtip_index], Z[wingtip_index], liftVector_g[0], liftVector_g[1], liftVector_g[2], color='blue', label=r'$\overrightarrow{e_L}^{(g)}_w$')
#z_wing_g
z_wing_g_sequence_plot = z_wing_g_sequence[timeStep]
ax.quiver(X[wingtip_index], Y[wingtip_index], Z[wingtip_index], z_wing_g_sequence_plot[0], z_wing_g_sequence_plot[1], z_wing_g_sequence_plot[2], color='red', label=r'$\overrightarrow{e_{F_{am}}}^{(g)}_w$')
# #e_Fam
# e_Fam_plot = e_Fam[timeStep]
# ax.quiver(X[wingtip_index], Y[wingtip_index], Z[wingtip_index], e_Fam_plot[0], e_Fam_plot[1], e_Fam_plot[2], color='red', label=r'$\overrightarrow{e_{F_{am}}}^{(g)}_w$')
ax.legend()
#set the axis limits
ax.set_xlim([-a, a])
ax.set_ylim([-a, a])
ax.set_zlim([-a, a])
#set the axis labels
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title(f'Timestep: {timeStep}, ⍺: {np.round(np.degrees(alphas[timeStep]), 2)}, AoA: {np.round(np.degrees(AoA[timeStep]), 2)} \nFl: {np.round(Ftc[timeStep], 4)} \nFd: {np.round(Ftd[timeStep], 4)} \nFrot: {np.round(Frc[timeStep], 4)} \nFam: {np.round(Fam[timeStep], 4)}')
# run the live animation of the wing
def generatePlotsForKinematicsSequence():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
anim = animation.FuncAnimation(fig, functools.partial(animationPlot, ax), frames=len(timeline), repeat=True)
#anim.save('u&d_vectors.gif')
plt.show()
#kinematics
for timeStep in range(nt):
#here the 1st time derivatives of the angles are calculated by means of 2nd order central difference approximations
alphas_dt = (alphas[(timeStep+1)%nt] - alphas[timeStep-1]) / (2*delta_t) #here we compute the modulus of (timestep+1) and nt to prevent overflowing. central difference
phis_dt = (phis[(timeStep+1)%nt] - phis[timeStep-1]) / (2*delta_t)
thetas_dt = (thetas[(timeStep+1)%nt] - thetas[timeStep-1]) / (2*delta_t)
# #here the 1st time derivatives of the angles are calculated by means of 1st order forward difference approximations
# alphas_dt = (phis[(timeStep+1)%nt] - phis[timeStep]) / (delta_t)
# phis_dt = (phis[(timeStep+1)%nt] - phis[timeStep]) / (delta_t) #1st order forward difference approximation of 1st derivative of phi
# thetas_dt = (thetas[(timeStep+1)%nt] - thetas[timeStep]) / (delta_t)
# parameter array: psi [0], beta[1], gamma[2], eta[3], phi[4], alpha[5], theta[6]
parameters = [psis[timeStep], betas[timeStep], gammas[timeStep], etas[timeStep], phis[timeStep], alphas[timeStep], thetas[timeStep]] # 7 angles in radians! #without alphas[timeStep] any rotation around any y axis through an angle of pi/2 gives an error!
parameters_dt = [0, 0, 0, 0, phis_dt, alphas_dt, thetas_dt]
strokePoints, wingRotationMatrix, wingRotationMatrixTrans = convert_from_wing_reference_frame_to_stroke_plane(wingPoints, parameters)
bodyPoints, strokeRotationMatrix, strokeRotationMatrixTrans = convert_from_stroke_plane_to_body_reference_frame(strokePoints, parameters)
globalPoints, bodyRotationMatrix, bodyRotationMatrixTrans = convert_from_body_reference_frame_to_global_reference_frame(bodyPoints, parameters)
strokePointsSequence[timeStep, :] = strokePoints
bodyPointsSequence[timeStep, :] = bodyPoints
globalPointsSequence[timeStep, :] = globalPoints
wingRotationMatrix_sequence[timeStep, :] = wingRotationMatrix
wingRotationMatrixTrans_sequence[timeStep, :] = wingRotationMatrixTrans
strokeRotationMatrix_sequence[timeStep, :] = strokeRotationMatrix
strokeRotationMatrixTrans_sequence[timeStep, :] = strokeRotationMatrixTrans
bodyRotationMatrix_sequence[timeStep, :] = bodyRotationMatrix
bodyRotationMatrixTrans_sequence[timeStep, :] = bodyRotationMatrixTrans
rotationMatrix_g_to_w[timeStep, :] = np.matmul(wingRotationMatrix, np.matmul(strokeRotationMatrix, bodyRotationMatrix))
rotationMatrix_w_to_g[timeStep, :] = np.matmul(np.matmul(bodyRotationMatrixTrans, strokeRotationMatrixTrans), wingRotationMatrixTrans)
#these are all the absolute unit vectors of the wing
#y_wing_g coincides with the tip only if R is normalized.
# x_wing_g = np.matmul((np.matmul(np.matmul(strokeRotationMatrixTrans, wingRotationMatrixTrans), bodyRotationMatrixTrans)), np.array([[1], [0], [0]]))
# y_wing_g = np.matmul((np.matmul(np.matmul(strokeRotationMatrixTrans, wingRotationMatrixTrans), bodyRotationMatrixTrans)), np.array([[0], [1], [0]]))
# z_wing_g = np.matmul((np.matmul(np.matmul(strokeRotationMatrixTrans, wingRotationMatrixTrans), bodyRotationMatrixTrans)), np.array([[0], [0], [1]]))
x_wing_g = np.matmul(bodyRotationMatrixTrans, (np.matmul(strokeRotationMatrixTrans, (np.matmul(wingRotationMatrixTrans, np.array([[1], [0], [0]]))))))
y_wing_g = np.matmul(bodyRotationMatrixTrans, (np.matmul(strokeRotationMatrixTrans, (np.matmul(wingRotationMatrixTrans, np.array([[0], [1], [0]]))))))
z_wing_g = np.matmul(bodyRotationMatrixTrans, (np.matmul(strokeRotationMatrixTrans, (np.matmul(wingRotationMatrixTrans, np.array([[0], [0], [1]]))))))
y_wing_s = np.matmul(wingRotationMatrixTrans, np.array([[0], [1], [0]]))
y_wing_g_sequence[timeStep, :] = y_wing_g.flatten()
z_wing_g_sequence[timeStep, :] = z_wing_g.flatten()
y_wing_s_sequence[timeStep, :] = y_wing_s.reshape(3,)
y_wing_w = np.array([[0], [1], [0]])
z_wing_w = np.array([[0], [0], [1]])
y_wing_w_sequence[timeStep, :] = y_wing_w.reshape(3,)
z_wing_w_sequence[timeStep, :] = z_wing_w.reshape(3,)
rot_wing_g, rot_wing_b, rot_wing_s, rot_wing_w, planar_rot_wing_g, planar_rot_wing_s, planar_rot_wing_w = generate_rot_wing(wingRotationMatrix, bodyRotationMatrixTrans, strokeRotationMatrixTrans, parameters[4], parameters_dt[4], parameters[5],
parameters_dt[5], parameters[6], parameters_dt[6])
rots_wing_b[timeStep, :] = rot_wing_b
rots_wing_s[timeStep, :] = rot_wing_s
rots_wing_w[timeStep, :] = rot_wing_w
rots_wing_g[timeStep, :] = rot_wing_g
planar_rots_wing_s[timeStep, :] = planar_rot_wing_s
planar_rots_wing_w[timeStep, :] = planar_rot_wing_w
planar_rots_wing_g[timeStep, :] = planar_rot_wing_g
# u_infty = np.array([0.248, 0.0, 0.6]) #absolute wind velocity
u_wing_g = generate_u_wing_g_position(rot_wing_g.reshape(1,3), y_wing_g.reshape(1,3)) + u_infty
us_wing_g[timeStep, :] = (u_wing_g).reshape(3,1) #remember to rename variables since u_infty has been introduced!
u_wing_w = generate_u_wing_w(u_wing_g.reshape(3,1), bodyRotationMatrix, strokeRotationMatrix, wingRotationMatrix)
us_wing_w[timeStep, :] = u_wing_w
#absolute mean flow velocity is defined as the (linear) sum of the absolute winG velocity and the absolute winD velocity
u_flight_g = u_wing_g
u_wind_w = getWindDirectioninWingReferenceFrame(u_flight_g, bodyRotationMatrix, strokeRotationMatrix, wingRotationMatrix) - np.array(u_wing_w.reshape(3,1))
us_wind_w[timeStep, :] = u_wind_w
u_wing_g_magnitude = np.linalg.norm(u_wing_g)
us_wing_g_magnitude[timeStep] = u_wing_g_magnitude
if u_wing_g_magnitude != 0:
e_u_wing_g = u_wing_g/u_wing_g_magnitude
else:
e_u_wing_g = u_wing_g
e_dragVector_wing_g = -e_u_wing_g
e_dragVectors_wing_g[timeStep, :] = e_dragVector_wing_g
#lift. lift vector is multiplied with the sign of alpha to have their signs match
liftVector_g = np.cross(e_u_wing_g, y_wing_g.flatten())
if isLeft == 0:
liftVector_g = liftVector_g*np.sign(-alphas[timeStep])
else:
liftVector_g = liftVector_g*np.sign(alphas[timeStep])
liftVectors[timeStep, :] = liftVector_g
aoa = getAoA(x_wing_g.reshape(1,3), e_u_wing_g.reshape(3,1)) #use this one for getAoA with arccos
# aoa = getAoA(e_u_wing_g, x_wing_g.flatten()) #use this one for getAoA with arctan
AoA[timeStep, :] = aoa
liftVector_magnitude = np.sqrt(liftVector_g[0, 0]**2 + liftVector_g[0, 1]**2 + liftVector_g[0, 2]**2)
if liftVector_magnitude != 0:
e_liftVector_g = liftVector_g / liftVector_magnitude
else:
e_liftVector_g = liftVector_g
e_liftVectors_g[timeStep, :] = e_liftVector_g
alphas_dt_sequence[timeStep] = alphas_dt
phis_dt_sequence[timeStep] = phis_dt
thetas_dt_sequence[timeStep] = thetas_dt
alphas_dt_dt = (alphas[(timeStep+1)%nt] - 2*alphas[timeStep] + alphas[timeStep-1]) / (delta_t**2) #2nd order central difference approximation of 2nd derivative of alpha
phis_dt_dt = (phis[(timeStep+1)%nt] - 2*phis[timeStep] + phis[timeStep-1]) / (delta_t**2) #2nd order central difference approximation of 2nd derivative of phi
thetas_dt_dt = (thetas[(timeStep+1)%nt] - 2*thetas[timeStep] + thetas[timeStep-1]) / (delta_t**2) #2nd order central difference approximation of 2nd derivative of theta
# phis_dt_dt = (phis_dt_sequence[(timeStep+1)%nt] - phis_dt_sequence[timeStep-1]) / (2*delta_t) #2nd order central difference approximation of 1st derivative of phi_dt
# thetas_dt_dt = (thetas_dt_sequence[(timeStep+1)%nt] - thetas_dt_sequence[timeStep-1]) / (2*delta_t) #2nd order central difference approximation of 1st derivative of theta_dt
alphas_dt_dt_sequence[timeStep] = alphas_dt_dt
phis_dt_dt_sequence[timeStep] = phis_dt_dt
thetas_dt_dt_sequence[timeStep] = thetas_dt_dt
# #validation of our u_wing_g by means of a first order approximation
# #left and right derivative:
# verifying_us_wing_g = np.zeros((nt, wingPoints.shape[0], 3))
# currentGlobalPoint = globalPointsSequence[timeStep]
# leftGlobalPoint = globalPointsSequence[timeStep-1]
# rightGlobalPoint = globalPointsSequence[(timeStep+1)%len(timeline)]
# LHD = (currentGlobalPoint - leftGlobalPoint) / delta_t
# RHD = (rightGlobalPoint - currentGlobalPoint) / delta_t
# verifying_us_wing_g[timeStep, :] = (LHD+RHD)/2
# verifying_us_wing_g = verifying_us_wing_g
#calculation of wingtip acceleration and angular acceleration in wing reference frame
for timeStep in range(nt):
acc_wing_w[timeStep, :] = np.matmul(rotationMatrix_g_to_w[timeStep, :], (us_wing_g[(timeStep+1)%nt] - us_wing_g[timeStep-1])/(2*delta_t)) #acc_wing_w, central difference
# acc_wing_w[timeStep, :] = np.matmul(rotationMatrix_g_to_w[timeStep, :], (us_wing_g[(timeStep+1)%nt] - us_wing_g[timeStep])/(delta_t)) #acc_wing_w, forward difference
acc_wing_g[timeStep, :] = (us_wing_g[(timeStep+1)%nt] - us_wing_g[timeStep-1])/(2*delta_t)
rot_acc_wing_g[timeStep, :] = (rots_wing_g[(timeStep+1)%nt] - rots_wing_g[timeStep-1]) / (2*delta_t) #central scheme
# rot_acc_wing_g[timeStep, :] = (rots_wing_g[(timeStep+1)%nt] - rots_wing_g[timeStep]) / (delta_t) #forward scheme
rot_acc_wing_w[timeStep, :] = np.matmul(rotationMatrix_g_to_w[timeStep, :], rot_acc_wing_g[timeStep, :])
#calculation of wingtip planar velocity (planar since the contribution from alpha/spanwise component is not taken into account) in global and wing reference frames
rots_wing_g_magnitude = np.linalg.norm(rots_wing_g, axis=1).reshape(nt,)
planar_rots_wing_g_magnitude = np.linalg.norm(planar_rots_wing_g, axis=1).reshape(nt,) #here we reshap[(timeStep+1)%nt]e to fix dimensionality issues as planar_rots_wing_g_magnitude is of shape (nt, 1) and it should be of shape (nt,)
rots_wing_w_magnitude = np.linalg.norm(rots_wing_w, axis=1).reshape(nt,)
planar_rots_wing_w_magnitude = np.linalg.norm(planar_rots_wing_w, axis=1).reshape(nt,)
#computation of M_CFD_w and F_CFD_w
for i in range(nt):
M_CFD_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], M_CFD_g[i, :])
# M_CFD_w[i, :] = np.matmul(wingRotationMatrix_sequence[i, :], np.matmul(strokeRotationMatrix_sequence[i, :], np.matmul(bodyRotationMatrix_sequence[i, :], M_CFD_g[i, :])))
# Mx_CFD_w_vector[i, :] = M_CFD_w[i, :]
# Mx_CFD_w_vector[i, 1:3] = 0
F_CFD_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], F_CFD_g[i, :])
# Fz_CFD_w_vector[i, :] = F_CFD_w[i, :]
# Fz_CFD_w_vector[i, 0:2] = 0
# data_new = it.insectSimulation_postProcessing(cfd_run)
# t_Mw = data_new[1961:3921, 0]-1
# Mx_CFD_w = data_new[1961:3921, 4]
# My_CFD_w = data_new[1961:3921, 5]
# Mz_CFD_w = data_new[1961:3921, 6]
# Mx_CFD_w_interp = interp1d(t_Mw, Mx_CFD_w, fill_value='extrapolate')
# My_CFD_w_interp = interp1d(t_Mw, My_CFD_w, fill_value='extrapolate')
# Mz_CFD_w_interp = interp1d(t_Mw, Mz_CFD_w, fill_value='extrapolate')
# # M_CFD_w = np.vstack((Mx_CFD_w_interp(timeline), My_CFD_w_interp(timeline), Mz_CFD_w_interp(timeline))).transpose()
# # M_CFD_w = np.vstack((Mx_CFD_w, My_CFD_w, Mz_CFD_w)).transpose()
# #cfd moments in wing reference frame (insect tools)
# plt.plot(timeline, Mx_CFD_w_interp(timeline), label='Mx_CFD_w_it', color='red')
# plt.plot(timeline, My_CFD_w_interp(timeline), label='My_CFD_w_it', color='green')
# plt.plot(timeline, Mz_CFD_w_interp(timeline), label='Mz_CFD_w_it', color='blue')
# plt.xlabel('t/T [s]')
# plt.ylabel('Moment [mN*mm]')
# #cfd moments in wing reference frame
# plt.plot(timeline, M_CFD_w[:, 0], label='Mx_CFD_w', color='yellow')
# plt.plot(timeline, M_CFD_w[:, 1], label='My_CFD_w', color='pink')
# plt.plot(timeline, M_CFD_w[:, 2], label='Mz_CFD_w', color='purple')
# plt.xlabel('t/T [s]')
# plt.ylabel('Moment [mN*mm]')
# plt.title('CFD moments in wing reference frame')
# plt.legend()
# plt.show()
# exit()
############################################################################################################################################################################################
##%% dynamics
from scipy.integrate import trapz, simpson
import scipy.optimize as opt
import time
def getAerodynamicCoefficients(x0, AoA):
deg2rad = np.pi/180.0
rad2deg = 180.0/np.pi
AoA = rad2deg*AoA
# Cl and Cd definitions from Dickinson 1999
Cl = x0[0] + x0[1]*np.sin( deg2rad*(2.13*AoA - 7.20) )
Cd = x0[2] + x0[3]*np.cos( deg2rad*(2.04*AoA - 9.82) )
Crot = x0[4]
Cam1 = x0[5]
Cam2 = x0[6]
Crd = x0[7]
# Cwe = x0[8]
return Cl, Cd, Crot, Cam1, Cam2, Crd #, Cwe
#cost function which tells us how far off our QSM values are from the CFD ones for the forces
def cost_forces(x, nb=1000, show_plots=False):
#global variable must be imported in order to modify them locally
nonlocal Ftc_magnitude, Ftd_magnitude, Frc_magnitude, Fam_magnitude, Frd_magnitude, Fam, AoA, F_QSM_g, F_QSM_w
Cl, Cd, Crot, Cam1, Cam2, Crd = getAerodynamicCoefficients(x, np.array(AoA))
# chord calculation
y_space = np.linspace(min_y, max_y, nb)
c = getChordLength(e_wingPoints, y_space)
# plt.plot(c,y_space)
# plt.show()
# exit()
rho = 1.225
#both Cl and Cd are of shape (nt, 1), this however poses a dimensional issue when the magnitude of the lift/drag force is to be multiplied
#with their corresponding vectors. to fix this, we reshape Cl and Cd to be of shape (nt,)
Cl = Cl.reshape(nt,)
Cd = Cd.reshape(nt,)
AoA = AoA.reshape(nt,)
#computation following Nakata 2015 eqns. 2.4a-c
c_interpolation = interp1d(y_space, c) #we create a function that interpolates our chord (c) w respect to our span (y_space)
#the following comes from defining lift/drag in the following way: dFl = 0.5*rho*Cl*v^2*c*dr -> where v = linear velocity, c = chord length, dr = chord width
#v can be broken into 𝛀(φ,Θ)*r (cf. lines 245-248). plugging that into our equation we get: dFl = 0.5*rho*Cl*𝛀^2(φ,Θ)*r^2*c*dr (lift in each blade)
#integrating both sides, and pulling constants out of integrand on RHS: Ftc = 0.5*rho*Cl*𝛀^2(φ,Θ)*∫c*r^2*dr
#our function def Cr2 then calculates the product of c and r^2 ; I (second moment of area) performs the integration of the product
#drag is pretty much the same except that instead of Cl we use Cd: Ftd = 0.5*rho*Cd*𝛀^2(φ,Θ)*∫c*r^2*dr
#and the rotational force is defined as follows: Frc = 0.5*rho*Crot*𝛀(φ,Θ)*∫c^2*r*dr
def Cr2(r):
return c_interpolation(r) * r**2
def C2r(r):
return (c_interpolation(r)**2) * r
def C2(r):
return (c_interpolation(r)**2)
def C3r3(r):
return(np.sqrt((c_interpolation(r)**3)*(r**3)))
Iam = simpson(C2(y_space), y_space)
Iwe = simpson(C3r3(y_space), y_space)
Ild = simpson(Cr2(y_space), y_space) #second moment of area for lift/drag calculations
Irot = simpson(C2r(y_space), y_space) #second moment of area for rotational force calculation
#calculation of forces not absorbing wing shape related and density of fluid terms into force coefficients
Ftc_magnitude = 0.5*rho*Cl*(planar_rots_wing_w_magnitude**2)*Ild #Nakata et al. 2015
Ftd_magnitude = 0.5*rho*Cd*(planar_rots_wing_w_magnitude**2)*Ild #Nakata et al. 2015
Frc_magnitude = rho*Crot*planar_rots_wing_w_magnitude*alphas_dt_sequence*Irot #Nakata et al. 2015
Fam_magnitude = -Cam1*rho*np.pi/4*Iam*acc_wing_w[:, 2] -Cam2*rho*np.pi/8*Iam*rot_acc_wing_w[:, 1] #Cai et al. 2021 #second term should be time derivative of rots_wing_w
Frd_magnitude = -1/6*rho*Crd*np.abs(alphas_dt_sequence)*alphas_dt_sequence #Cai et al. 2021
#Fwe_magnitude = 1/2*rho*rots_wing_w_magnitude*np.sqrt(rots_wing_w_magnitude)*Iwe*Cwe
#Fwe_magnitude = 1/2*rho*phis*np.sign(phis_dt_sequence)*np.sqrt(np.abs(phis_dt_sequence))*Iwe*Cwe
# #calculation of forces absorbing wing shape related and density of fluid terms into force coefficients
# Ftc_magnitude = Cl*(planar_rots_wing_w_magnitude**2)
# Ftd_magnitude = Cd*(planar_rots_wing_w_magnitude**2)
# Frc_magnitude = Crot*planar_rots_wing_w_magnitude*alphas_dt_sequence
# Fam_magnitude = Cam1*acc_wing_w[:, 2] + Cam2*rot_acc_wing_w[:, 1]
# Frd_magnitude = Crd*np.abs(alphas_dt_sequence)*alphas_dt_sequence
# # Fwe_magnitude = Cwe*rots_wing_w_magnitude*np.sqrt(rots_wing_w_magnitude)
# vector calculation of Ftc, Ftd, Frc, Fam, Frd and Fwe arrays of the form (nt, 3).these vectors are in the global reference frame
for i in range(nt):
Ftc[i, :] = (Ftc_magnitude[i] * e_liftVectors_g[i])
Ftd[i, :] = (Ftd_magnitude[i] * e_dragVectors_wing_g[i])
Frc[i, :] = (Frc_magnitude[i] * z_wing_g_sequence[i])
Fam[i, :] = (Fam_magnitude[i] * z_wing_g_sequence[i])
Frd[i, :] = (Frd_magnitude[i] * z_wing_g_sequence[i])
Fwe[i, :] = (Fwe_magnitude[i] * z_wing_g_sequence[i])
Fx_QSM_g = Ftc[:, 0] + Ftd[:, 0] + Frc[:, 0] + Fam[:, 0] + Frd[:, 0] + Fwe[:, 0]
Fy_QSM_g = Ftc[:, 1] + Ftd[:, 1] + Frc[:, 1] + Fam[:, 1] + Frd[:, 1] + Fwe[:, 1]
Fz_QSM_g = Ftc[:, 2] + Ftd[:, 2] + Frc[:, 2] + Fam[:, 2] + Frd[:, 2] + Fwe[:, 2]
F_QSM_g[:] = Ftc + Ftd + Frc + Fam + Frd + Fwe
K_forces_num = np.linalg.norm(Fx_QSM_g-Fx_CFD_g_interp(timeline)) + np.linalg.norm(Fz_QSM_g-Fz_CFD_g_interp(timeline)) #+ np.linalg.norm(Fy_QSM_g+Fy_CFD_g_interp(timeline))
K_forces_den = np.linalg.norm(Fx_CFD_g_interp(timeline)) + np.linalg.norm(Fz_CFD_g_interp(timeline)) #+ np.linalg.norm(-Fy_CFD_g_interp(timeline))
if K_forces_den != 0:
K_forces = K_forces_num/K_forces_den
else:
K_forces = K_forces_num
for i in range(nt):
F_QSM_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], F_QSM_g[i, :])
# Ftc_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], Ftc[i, :])
# Ftd_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], Ftd[i, :])
# Frc_w[i, :] = (Frc_magnitude[i] * z_wing_w_sequence[i])
# Fam_w[i, :] = (Fam_magnitude[i] * z_wing_w_sequence[i])
# Frd_w[i, :] = (Frd_magnitude[i] * z_wing_w_sequence[i])
# Fwe_w[i, :] = (Fwe_magnitude[i] * z_wing_w_sequence[i])
# Fz_QSM_w_vector[i, :] = F_QSM_w[i, :]
# Fz_QSM_w_vector[i, 0:2] = 0
# F_QSM_w[i, :] = np.matmul(wingRotationMatrix_sequence[i, :], np.matmul(strokeRotationMatrix_sequence[i, :], np.matmul(bodyRotationMatrix_sequence[i, :], F_QSM_g[i, :])))
# F_QSM_gg[i, :] = np.matmul(bodyRotationMatrixTrans_sequence[i, :], np.matmul(strokeRotationMatrixTrans_sequence[i, :], np.matmul(wingRotationMatrixTrans_sequence[i, :], F_QSM_w[i, :])))
# F_QSM_gg[i, :] = np.matmul(rotationMatrix_w_to_g[i, :], F_QSM_w[i, :])
# F_QSM_w[i, :] = np.matmul(rotationMatrix_g_to_w[i, :], np.array([[1], [1], [1]]).reshape(3,))
# # F_QSM_w[i, :] = np.matmul(wingRotationMatrix_sequence[i, :], np.matmul(strokeRotationMatrix_sequence[i, :], np.matmul(bodyRotationMatrix_sequence[i, :], np.array([[1], [1], [1]]).reshape(3,))))
if show_plots:
##FIGURE 1
fig, axes = plt.subplots(3, 2, figsize = (15, 15))
#angles
axes[0, 0].plot(timeline, np.degrees(phis), label='ɸ')
axes[0, 0].plot(timeline, np.degrees(alphas), label ='⍺')
axes[0, 0].plot(timeline, np.degrees(thetas), label='Θ')
axes[0, 0].plot(timeline, np.degrees(AoA), label='AoA', color = 'purple')
axes[0, 0].set_xlabel('t/T [s]')
axes[0, 0].set_ylabel('[˚]')
axes[0, 0].legend(loc = 'upper right')
#u_wing_w (tip velocity in wing reference frame )
axes[0, 1].plot(timeline, us_wing_w[:, 0], label='u_x_wing_w')
axes[0, 1].plot(timeline, us_wing_w[:, 1], label='u_y_wing_w')
axes[0, 1].plot(timeline, us_wing_w[:, 2], label='u_z_wing_w')
axes[0, 1].set_xlabel('t/T [s]')
axes[0, 1].set_ylabel('[mm/s]')
axes[0, 1].set_title('Tip velocity in wing reference frame')
axes[0, 1].legend()
#a_wing_w (tip acceleration in wing reference frame )
axes[1, 0].plot(timeline, acc_wing_w[:, 0], label='a_x_wing_w')
axes[1, 0].plot(timeline, acc_wing_w[:, 1], label='a_y_wing_w')
axes[1, 0].plot(timeline, acc_wing_w[:, 2], label='a_z_wing_w')
axes[1, 0].set_xlabel('t/T [s]')
axes[1, 0].set_ylabel('[mm/s²]')
axes[1, 0].set_title('Tip acceleration in wing reference frame')
axes[1, 0].legend()
#rot_wing_w (tip velocity in wing reference frame )
axes[1, 1].plot(timeline, rots_wing_w[:, 0], label='rot_x_wing_w')
axes[1, 1].plot(timeline, rots_wing_w[:, 1], label='rot_y_wing_w')
axes[1, 1].plot(timeline, rots_wing_w[:, 2], label='rot_z_wing_w')
axes[1, 1].set_xlabel('t/T [s]')
axes[1, 1].set_ylabel('rad/s')
axes[1, 1].set_title('Angular velocity in wing reference frame')
axes[1, 1].legend()
#rot_acc_wing_w (angular acceleration in wing reference frame )
axes[2, 0].plot(timeline, rot_acc_wing_w[:, 0], label='rot_acc_x_wing_w')
axes[2, 0].plot(timeline, rot_acc_wing_w[:, 1], label='rot_acc_y_wing_w')
axes[2, 0].plot(timeline, rot_acc_wing_g[:, 2], label='rot_acc_z_wing_w')
axes[2, 0].set_xlabel('t/T [s]')
axes[2, 0].set_ylabel('[rad/s]²')
axes[2, 0].set_title('Angular acceleration in wing reference frame')
axes[2, 0].legend()
#alphas_dt
axes[2, 1].plot(timeline, alphas_dt_sequence)
axes[2, 1].set_xlabel('t/T [s]')
axes[2, 1].set_ylabel('[rad/s]')
axes[2, 1].set_title('Time derivative of alpha')
axes[2, 1].legend()
plt.subplots_adjust(left=0.07, bottom=0.05, right=0.960, top=0.970, wspace=0.185, hspace=0.28)
# plt.subplot_tool()
# plt.show()
plt.savefig(folder_name+'/kinematics_figure.png', dpi=300)
##FIGURE 2
fig, axes = plt.subplots(2, 2, figsize = (15, 10))
#coefficients
graphAoA = np.linspace(-9, 90, 100)*(np.pi/180)
gCl, gCd, gCrot, gCam1, gCam2, gCrd = getAerodynamicCoefficients(x, graphAoA)
axes[0, 0].plot(np.degrees(graphAoA), gCl, label='Cl', color='#0F95F1')
axes[0, 0].plot(np.degrees(graphAoA), gCd, label='Cd', color='#F1AC0F')
# ax.plot(np.degrees(graphAoA), gCrot*np.ones_like(gCl), label='Crot')
axes[0, 0].set_title('Lift and drag coeffficients')
axes[0, 0].set_xlabel('AoA[°]')
axes[0, 0].set_ylabel('[]')
axes[0, 0].legend(loc = 'upper right')
#vertical forces
axes[0, 1].plot(timeline, Ftc[:, 2], label = 'Vertical lift force', color='gold')
axes[0, 1].plot(timeline, Frc[:, 2], label = 'Vertical rotational force', color='orange')
axes[0, 1].plot(timeline, Ftd[:, 2], label = 'Vertical drag force', color='lightgreen')
axes[0, 1].plot(timeline, Fam[:, 2], label = 'Vertical added mass force', color='red')
axes[0, 1].plot(timeline, Frd[:, 2], label = 'Vertical rotational drag force', color='green')
# axes[0, 1].plot(timeline, Fwe[:, 2], label = 'Vertical wagner effect force')
axes[0, 1].plot(timeline, Fz_QSM_g, label = 'Vertical QSM force', ls='-.', color='blue')
axes[0, 1].plot(timeline, Fz_CFD_g_interp(timeline), label = 'Vertical CFD force', ls='--', color='purple')
axes[0, 1].set_xlabel('t/T [s]')
axes[0, 1].set_ylabel('Force [mN]')
axes[0, 1].set_title('Vertical components of forces in global coordinate system')
axes[0, 1].legend(loc = 'lower right')
# #vertical forces_w
# plt.figure()
# plt.plot(timeline, Ftc[:, 2], label = 'Vertical lift force_w', color='gold')
# plt.plot(timeline, Frc_w[:, 2], label = 'Vertical rotational force_w', color='orange')
# plt.plot(timeline, Ftd_w[:, 2], label = 'Vertical drag force_w', color='lightgreen')
# plt.plot(timeline, Fam_w[:, 2], label = 'Vertical added mass force_w', color='red')
# plt.plot(timeline, Frd_w[:, 2], label = 'Vertical rotational drag force_w', color='green')
# # plt.plot(timeline, Fwe_w[:, 2], label = 'Vertical wagner effect force_w')
# plt.plot(timeline, F_QSM_w[:, 2], label = 'Vertical QSM force_w' , color='blue')
# plt.xlabel('t/T [s]')
# plt.ylabel('Force [mN]')
# plt.legend()
# plt.show()
#qsm + cfd force components in wing reference frame
axes[1, 0].plot(timeline, F_QSM_w[:, 0], label='Fx_QSM_w', c='r')
axes[1, 0].plot(timeline, F_CFD_w[:, 0], ls='-.', label='Fx_CFD_w', c='r')
axes[1, 0].plot(timeline, F_QSM_w[:, 1], label='Fy_QSM_w', c='g')
axes[1, 0].plot(timeline, F_CFD_w[:, 1], ls='-.', label='Fy_CFD_w', c='g')
axes[1, 0].plot(timeline, F_QSM_w[:, 2], label='Fz_QSM_w', c='b')