text:
module EpemaData
# using Base: Forward, find_all_in_cache_path
import ..AircraftSystems
AS = AircraftSystems
DF = AS.DelimitedFiles
FM = AS.FLOWMath
LS = AS.LaTeXStrings
LA = AS.LinearAlgebra
plt = AS.PyPlot
S = AS.Splines
###################################################################

wing = Dict()
wing["span"] = 2.58 # m
wing["area"] = 0.62 # m^2
wing["chord_root"] = 0.279 # m
wing["chord_tip"] = 0.161 # m
wing["mac"] = 0.24 # m
wing["airfoil"] = "NACA 64_2-015A"
wing["chords"] = [0.0 wing["chord_root"]; 0.3429 wing["chord_root"]; 1.0 wing["chord_tip"]]
wing["chords"][:,1] .*= wing["span"]/2

rotor = Dict()
rotor["Nblades"] = 6
rotor["2y/b"] = 0.35
rotor["y"] = rotor["2y/b"] * wing["span"] / 2 # m
rotor["diameter"] = 0.406 # m - at the tip
rotor["radius"] = rotor["diameter"] / 2 # m - at the tip
rotor["diameter_hub"] = 0.084 # m
rotor["radius_hub"] = rotor["diameter_hub"] / 2 # m
rotor["pitch at r/R=0.7"] = 30.0 # deg.
rotor["r/R vs chord/R"] = [
    0.206897 0.15571575695159628
    0.23119777158774377 0.15571575695159628;
    0.24373259052924792 0.15489186405767247;
    0.254874651810585 0.15427394438722963;
    0.2646239554317549 0.1536560247167868;
    0.2771587743732591 0.152832131822863;
    0.28690807799442897 0.15242018537590113;
    0.29944289693593307 0.1515962924819773;
    0.31058495821727017 0.15097837281153448;
    0.32033426183844016 0.1505664263645726;
    0.33008356545961004 0.14994850669412974;
    0.34122562674094703 0.1493305870236869;
    0.35027855153203336 0.14871266735324407;
    0.3621169916434541 0.14809474768280123;
    0.3746518105849582 0.14747682801235837;
    0.3857938718662953 0.14685890834191553;
    0.39484679665738176 0.14644696189495365;
    0.4032033426183844 0.14582904222451082;
    0.41364902506963797 0.14562306900102984;
    0.42479108635097473 0.14582904222451082;
    0.43454038997214484 0.14603501544799175;
    0.44846796657381605 0.1462409886714727;
    0.4623955431754875 0.14624098867147267;
    0.4798050139275766 0.14644696189495363;
    0.4972144846796658 0.14665293511843458;
    0.5097493036211699 0.14665293511843458;
    0.5250696378830083 0.1474768280123584;
    0.5181058495821728 0.1470648815653965;
    0.5376044568245125 0.14809474768280123;
    0.5536211699164346 0.14891864057672502;
    0.5682451253481894 0.14953656024716785;
    0.5793871866295265 0.15015447991761072;
    0.5919220055710307 0.1505664263645726;
    0.6016713091922006 0.15097837281153448;
    0.6142061281337048 0.1515962924819773;
    0.6267409470752089 0.1518022657054583;
    0.6406685236768802 0.15200823892893922;
    0.6559888579387188 0.15221421215242015;
    0.6740947075208914 0.15262615859938206;
    0.6866295264623956 0.152832131822863;
    0.6991643454038998 0.1532440782698249;
    0.7103064066852367 0.1532440782698249;
    0.7207520891364904 0.15345005149330587;
    0.7381615598885793 0.15303810504634396;
    0.756267409470752 0.15242018537590113;
    0.7743732590529246 0.15200823892893922;
    0.7862116991643455 0.1518022657054583;
    0.7889972144846799 0.1518022657054583;
    0.7924791086350975 0.1511843460350154;
    0.7966573816155988 0.15036045314109164;
    0.8077994428969361 0.1480947476828012;
    0.8217270194986074 0.1454170957775489;
    0.83983286908078 0.1419155509783728;
    0.8565459610027856 0.1388259526261586;
    0.8586350974930362 0.1384140061791967;
    0.8607242339832869 0.13779608650875386;
    0.8662952646239553 0.13553038105046342;
    0.8746518105849582 0.13223480947476826;
    0.8837047353760447 0.12852729145211123;
    0.8920612813370473 0.12523171987641607;
    0.8990250696378829 0.1223480947476828;
    0.9059888579387186 0.11946446961894952;
    0.9129526462395544 0.11658084449021626;
    0.91991643454039 0.113697219361483;
    0.9317548746518106 0.10875386199794025;
    0.9366295264623955 0.10628218331616889;
    0.9456824512534819 0.10175077239958805;
    0.953342618384401 0.09763130792996907;
    0.9610027855153204 0.09351184346035016;
    0.9658774373259055 0.09083419155509764;
    0.9721448467966572 0.08692070030895985;
    0.9770194986072421 0.08362512873326469;
    0.9811977715877437 0.08074150360453139;
    0.9860724233983288 0.07765190525231722;
    0.9902506963788301 0.07456230690010296;
    0.9937325905292481 0.07209062821833162;
    0.998607242339833 0.06920700308959835;
    1.0 0.06817713697219355
]

rotor["r/R vs twist"] = [
    0.206897  52.6519
    0.231782  52.6519
    0.254709  51.5181
    0.272959  50.5684
    0.290999  49.6514
    0.308355  48.7413
    0.325711  47.8624
    0.34396   46.9143
    0.359896  46.0206
    0.377252  45.1761
    0.394608  44.2941
    0.411964  43.4694
    0.431143  42.4781
    0.446501  41.7411
    0.463856  40.87
    0.480862  40.0688
    0.498218  39.2055
    0.516415  38.325
    0.534507  37.5021
    0.552705  36.631
    0.570797  35.7754
    0.588153  34.9354
    0.606192  34.0759
    0.625284  33.2219
    0.643376  32.4775
    0.661416  31.6888
    0.680507  30.8908
    0.698599  30.113
    0.716639  29.3438
    0.735573  28.7137
    0.756242  28.0557
    0.775386  27.4763
    0.79432   26.9007
    0.814621  26.3477
    0.833765  25.8429
    0.854066  25.2976
    0.87321   24.8006
    0.891776  24.3386
    0.909566  23.9081
    0.926505  23.4784
    0.942502  23.0943
    0.959858  22.6918
    0.979055  22.3024
    1.0       21.9829
    ]

    rotor["chord vs. r/R"] = [
    0.20689655 0.15571575695159628
    0.23119777158774377 0.15571575695159628;
    0.24373259052924792 0.15489186405767247;
    0.254874651810585 0.15427394438722963;
    0.2646239554317549 0.1536560247167868;
    0.2771587743732591 0.152832131822863;
    0.28690807799442897 0.15242018537590113;
    0.29944289693593307 0.1515962924819773;
    0.31058495821727017 0.15097837281153448;
    0.32033426183844016 0.1505664263645726;
    0.33008356545961004 0.14994850669412974;
    0.34122562674094703 0.1493305870236869;
    0.35027855153203336 0.14871266735324407;
    0.3621169916434541 0.14809474768280123;
    0.3746518105849582 0.14747682801235837;
    0.3857938718662953 0.14685890834191553;
    0.39484679665738176 0.14644696189495365;
    0.4032033426183844 0.14582904222451082;
    0.41364902506963797 0.14562306900102984;
    0.42479108635097473 0.14582904222451082;
    0.43454038997214484 0.14603501544799175;
    0.44846796657381605 0.1462409886714727;
    0.4623955431754875 0.14624098867147267;
    0.4798050139275766 0.14644696189495363;
    0.4972144846796658 0.14665293511843458;
    0.5097493036211699 0.14665293511843458;
    0.5250696378830083 0.1474768280123584;
    0.5181058495821728 0.1470648815653965;
    0.5376044568245125 0.14809474768280123;
    0.5536211699164346 0.14891864057672502;
    0.5682451253481894 0.14953656024716785;
    0.5793871866295265 0.15015447991761072;
    0.5919220055710307 0.1505664263645726;
    0.6016713091922006 0.15097837281153448;
    0.6142061281337048 0.1515962924819773;
    0.6267409470752089 0.1518022657054583;
    0.6406685236768802 0.15200823892893922;
    0.6559888579387188 0.15221421215242015;
    0.6740947075208914 0.15262615859938206;
    0.6866295264623956 0.152832131822863;
    0.6991643454038998 0.1532440782698249;
    0.7103064066852367 0.1532440782698249;
    0.7207520891364904 0.15345005149330587;
    0.7381615598885793 0.15303810504634396;
    0.756267409470752 0.15242018537590113;
    0.7743732590529246 0.15200823892893922;
    0.7862116991643455 0.1518022657054583;
    0.7889972144846799 0.1518022657054583;
    0.7924791086350975 0.1511843460350154;
    0.7966573816155988 0.15036045314109164;
    0.8077994428969361 0.1480947476828012;
    0.8217270194986074 0.1454170957775489;
    0.83983286908078 0.1419155509783728;
    0.8565459610027856 0.1388259526261586;
    0.8586350974930362 0.1384140061791967;
    0.8607242339832869 0.13779608650875386;
    0.8662952646239553 0.13553038105046342;
    0.8746518105849582 0.13223480947476826;
    0.8837047353760447 0.12852729145211123;
    0.8920612813370473 0.12523171987641607;
    0.8990250696378829 0.1223480947476828;
    0.9059888579387186 0.11946446961894952;
    0.9129526462395544 0.11658084449021626;
    0.91991643454039 0.113697219361483;
    0.9317548746518106 0.10875386199794025;
    0.9366295264623955 0.10628218331616889;
    0.9456824512534819 0.10175077239958805;
    0.953342618384401 0.09763130792996907;
    0.9610027855153204 0.09351184346035016;
    0.9658774373259055 0.09083419155509764;
    0.9721448467966572 0.08692070030895985;
    0.9770194986072421 0.08362512873326469;
    0.9811977715877437 0.08074150360453139;
    0.9860724233983288 0.07765190525231722;
    0.9902506963788301 0.07456230690010296;
    0.9937325905292481 0.07209062821833162;
    0.998607242339833 0.06920700308959835;
    1.0 0.06817713697219355
]
rotor["chord vs. r/R"][:,2] .*= rotor["radius"]
rotor["chords_raw"] = [
    0.20689655 0.15571575695159628
    0.23119777158774377 0.15571575695159628;
    0.24373259052924792 0.15489186405767247;
    0.254874651810585 0.15427394438722963;
    0.2646239554317549 0.1536560247167868;
    0.2771587743732591 0.152832131822863;
    0.28690807799442897 0.15242018537590113;
    0.29944289693593307 0.1515962924819773;
    0.31058495821727017 0.15097837281153448;
    0.32033426183844016 0.1505664263645726;
    0.33008356545961004 0.14994850669412974;
    0.34122562674094703 0.1493305870236869;
    0.35027855153203336 0.14871266735324407;
    0.3621169916434541 0.14809474768280123;
    0.3746518105849582 0.14747682801235837;
    0.3857938718662953 0.14685890834191553;
    0.39484679665738176 0.14644696189495365;
    0.4032033426183844 0.14582904222451082;
    0.41364902506963797 0.14562306900102984;
    0.42479108635097473 0.14582904222451082;
    0.43454038997214484 0.14603501544799175;
    0.44846796657381605 0.1462409886714727;
    0.4623955431754875 0.14624098867147267;
    0.4798050139275766 0.14644696189495363;
    0.4972144846796658 0.14665293511843458;
    0.5097493036211699 0.14665293511843458;
    0.5250696378830083 0.1474768280123584;
    0.5181058495821728 0.1470648815653965;
    0.5376044568245125 0.14809474768280123;
    0.5536211699164346 0.14891864057672502;
    0.5682451253481894 0.14953656024716785;
    0.5793871866295265 0.15015447991761072;
    0.5919220055710307 0.1505664263645726;
    0.6016713091922006 0.15097837281153448;
    0.6142061281337048 0.1515962924819773;
    0.6267409470752089 0.1518022657054583;
    0.6406685236768802 0.15200823892893922;
    0.6559888579387188 0.15221421215242015;
    0.6740947075208914 0.15262615859938206;
    0.6866295264623956 0.152832131822863;
    0.6991643454038998 0.1532440782698249;
    0.7103064066852367 0.1532440782698249;
    0.7207520891364904 0.15345005149330587;
    0.7381615598885793 0.15303810504634396;
    0.756267409470752 0.15242018537590113;
    0.7743732590529246 0.15200823892893922;
    0.7862116991643455 0.1518022657054583;
    0.7889972144846799 0.1518022657054583;
    0.7924791086350975 0.1511843460350154;
    0.7966573816155988 0.15036045314109164;
    0.8077994428969361 0.1480947476828012;
    0.8217270194986074 0.1454170957775489;
    0.83983286908078 0.1419155509783728;
    0.8565459610027856 0.1388259526261586;
    0.8586350974930362 0.1384140061791967;
    0.8607242339832869 0.13779608650875386;
    0.8662952646239553 0.13553038105046342;
    0.8746518105849582 0.13223480947476826;
    0.8837047353760447 0.12852729145211123;
    0.8920612813370473 0.12523171987641607;
    0.8990250696378829 0.1223480947476828;
    0.9059888579387186 0.11946446961894952;
    0.9129526462395544 0.11658084449021626;
    0.91991643454039 0.113697219361483;
    0.9317548746518106 0.10875386199794025;
    0.9366295264623955 0.10628218331616889;
    0.9456824512534819 0.10175077239958805;
    0.953342618384401 0.09763130792996907;
    0.9610027855153204 0.09351184346035016;
    0.9658774373259055 0.09083419155509764;
    0.9721448467966572 0.08692070030895985;
    0.9770194986072421 0.08362512873326469;
    0.9811977715877437 0.08074150360453139;
    0.9860724233983288 0.07765190525231722;
    0.9902506963788301 0.07456230690010296;
    0.9937325905292481 0.07209062821833162;
    0.998607242339833 0.06920700308959835;
    1.0 0.06817713697219355
] .* rotor["diameter"] / 2

rotor["twist vs. r/R"] = [
    0.20689655 0.17550647087207333
    0.23178214543117073 0.17550647087207333;
    0.254709079221614 0.17172693423980753;
    0.272959074351832 0.16856128423584651;
    0.29099869489841634 0.16550452636961388;
    0.3083545980481913 0.16247110253289448;
    0.3257105011979664 0.15954138549401164;
    0.3439604963281844 0.15638092082994243;
    0.3598963710384323 0.1534019430620872;
    0.3772522741882074 0.15058698440404278;
    0.39460817733798237 0.1476470538168881;
    0.41196408048775746 0.14489803801665985;
    0.4311433691656533 0.14159373067714975;
    0.4465005746342795 0.13913707301966827;
    0.4638564777840545 0.13623328268024454;
    0.4808617566277734 0.1335628326359531;
    0.4982176597775484 0.13068496899598853;
    0.516415061261858 0.1277500666172139;
    0.5345072754543507 0.12500702181443685;
    0.5527046769386602 0.12210323147501312;
    0.5707968911311531 0.1192512945345077;
    0.588152794280928 0.11645121099292055;
    0.6061924148275124 0.11358631070268556;
    0.625283908292265 0.11073955910207196;
    0.6433761224847576 0.10825837396383223;
    0.661415743031342 0.10562940663867541;
    0.6805072364960946 0.10296932727416763;
    0.6985994506885872 0.1003766573282536;
    0.7166390712351716 0.09781250675174463;
    0.7355727837621989 0.09571244409555427;
    0.7562420866042037 0.093519045321311;
    0.7753861737148646 0.09158750621160505;
    0.7943198862418919 0.08966893045162869;
    0.8146210335625379 0.08782554212008381;
    0.8337651206731987 0.08614289932518562;
    0.8540662679938448 0.08432543769309989;
    0.8732103551045057 0.08266872159766081;
    0.8917759121101739 0.08112867564978787;
    0.9095657128386935 0.07969363283472447;
    0.9265052496250269 0.07826118268960697;
    0.9425024835888346 0.07698105190381194;
    0.9598583867386096 0.07563934520680143;
    0.9790550674951789 0.07434141553606027;
    1.0 0.073276368795515
]

rotor["twist vs. r/R"][:,2] .*= 60 / 0.2 # convert because I got the axes wrong when digitizing the data

rotor["twists_raw"] = [
    0.20689655          52.651941261622 # tacked on to reach hub radius
    0.2317821454311707  52.651941261622;
    0.254709079221614   51.51808027194226;
    0.272959074351832   50.56838527075396;
    0.2909986948984163  49.651357910884165;
    0.3083545980481913  48.74133075986835;
    0.3257105011979664  47.862415648203495;
    0.3439604963281844  46.91427624898273;
    0.3598963710384323  46.02058291862616;
    0.3772522741882074  45.176095321212834;
    0.3946081773379823  44.29411614506643;
    0.4119640804877574  43.469411404997956;
    0.4311433691656533  42.47811920314493;
    0.4465005746342795  41.74112190590048;
    0.4638564777840545  40.86998480407336;
    0.4808617566277734  40.068849790785926;
    0.4982176597775484  39.20549069879656;
    0.516415061261858   38.32501998516417;
    0.5345072754543507  37.502106544331056;
    0.5527046769386602  36.63096944250393;
    0.5707968911311531  35.77538836035231;
    0.588152794280928   34.935363297876165;
    0.6061924148275124  34.07589321080567;
    0.625283908292265   33.22186773062159;
    0.6433761224847576  32.47751218914967;
    0.661415743031342   31.688821991602623;
    0.6805072364960946  30.89079818225029;
    0.6985994506885872  30.11299719847608;
    0.7166390712351716  29.34375202552339;
    0.7355727837621989  28.71373322866628;
    0.7562420866042037  28.0557135963933;
    0.7753861737148646  27.476251863481515;
    0.7943198862418919  26.900679135488605;
    0.8146210335625379  26.34766263602514;
    0.8337651206731987  25.842869797555686;
    0.8540662679938448  25.297631307929965;
    0.8732103551045057  24.80061647929824;
    0.8917759121101739  24.338602694936363;
    0.9095657128386935  23.90808985041734;
    0.9265052496250269  23.47835480688209;
    0.9425024835888346  23.094315571143582;
    0.9598583867386096  22.69180356204043;
    0.9790550674951789  22.302424660818083;
    1.0000000000000000  21.9829106386545
]

rotor["airfoils_raw"] = [
    [
        0.9974489795918366 0.013191489361702113;
        0.9795918367346939 0.01574468085106384;
        0.9502551020408163 0.02212765957446816;
        0.9298469387755102 0.025957446808510642;
        0.9068877551020409 0.030425531914893666;
        0.8852040816326532 0.034893617021276635;
        0.858418367346939 0.03999999999999998;
        0.8265306122448981 0.0463829787234043;
        0.8010204081632655 0.051489361702127645;
        0.7691326530612246 0.057872340425531965;
        0.7372448979591839 0.06425531914893617;
        0.7079081632653061 0.07000000000000006;
        0.6670918367346939 0.07829787234042557;
        0.6237244897959184 0.08659574468085113;
        0.5663265306122449 0.09808510638297879;
        0.5178571428571428 0.10765957446808516;
        0.48596938775510207 0.11276595744680856;
        0.45982142857142866 0.11723404255319153;
        0.43877551020408173 0.12042553191489364;
        0.41900510204081637 0.12297872340425536;
        0.3992346938775511 0.12553191489361704;
        0.38073979591836743 0.12744680851063833;
        0.36096938775510207 0.12936170212765957;
        0.3431122448979592 0.13063829787234044;
        0.3252551020408163 0.1319148936170213;
        0.3073979591836735 0.13319148936170216;
        0.29017857142857145 0.13319148936170216;
        0.2780612244897959 0.13319148936170216;
        0.26020408163265313 0.13319148936170216;
        0.24617346938775514 0.13319148936170216;
        0.21811224489795916 0.1319148936170213;
        0.19897959183673475 0.13063829787234044;
        0.17920918367346939 0.12936170212765963;
        0.1658163265306123 0.12808510638297876;
        0.15369897959183676 0.1268085106382979;
        0.14413265306122447 0.12553191489361704;
        0.13584183673469385 0.12425531914893617;
        0.12755102040816324 0.12297872340425536;
        0.11734693877551017 0.12042553191489364;
        0.1026785714285714 0.1165957446808511;
        0.09630102040816327 0.11468085106382986;
        0.0892857142857143 0.11212765957446813;
        0.08482142857142855 0.11021276595744689;
        0.07844387755102034 0.10765957446808516;
        0.07079081632653059 0.104468085106383;
        0.06568877551020402 0.10191489361702127;
        0.059311224489795894 0.09808510638297879;
        0.0522959183673469 0.09361702127659577;
        0.04528061224489793 0.08787234042553199;
        0.04081632653061218 0.0840425531914894;
        0.03507653061224486 0.078936170212766;
        0.029974489795918324 0.07446808510638303;
        0.026147959183673436 0.06936170212765963;
        0.02232142857142852 0.06425531914893617;
        0.01913265306122444 0.05978723404255326;
        0.01594387755102039 0.05531914893617024;
        0.012117346938775475 0.04893617021276603;
        0.008928571428571425 0.04255319148936171;
        0.0063775510204081565 0.0361702127659575;
        0.004464285714285698 0.029148936170212802;
        0.002551020408163268 0.020851063829787297;
        0.001275510204081648 0.010000000000000064;
        0 -0.0008510638297872242;
        0.001275510204081648 -0.00787234042553192;
        0.002551020408163268 -0.013617021276595642;
        0.004464285714285726 -0.019361702127659586;
        0.006377551020408129 -0.02446808510638293;
        0.009566326530612235 -0.030212765957446763;
        0.01275510204081634 -0.03531914893617022;
        0.01658163265306123 -0.040425531914893564;
        0.021683673469387765 -0.04553191489361702;
        0.0267857142857143 -0.050638297872340476;
        0.03380102040816327 -0.05574468085106382;
        0.0433673469387755 -0.06212765957446814;
        0.054846938775510196 -0.06914893617021273;
        0.06760204081632656 -0.07489361702127656;
        0.0816326530612245 -0.08000000000000002;
        0.0969387755102041 -0.08510638297872336;
        0.11224489795918369 -0.08893617021276595;
        0.13392857142857145 -0.09340425531914892;
        0.1517857142857143 -0.09659574468085103;
        0.17346938775510207 -0.09914893617021275;
        0.201530612244898 -0.10042553191489362;
        0.23788265306122447 -0.10170212765957448;
        0.2493622448979591 -0.10170212765957448;
        0.274234693877551 -0.10170212765957448;
        0.32334183673469397 -0.09978723404255324;
        0.36543367346938777 -0.09595744680851065;
        0.4215561224489796 -0.09021276595744682;
        0.46045918367346944 -0.08510638297872336;
        0.4936224489795919 -0.08063829787234039;
        0.528061224489796 -0.07553191489361705;
        0.556122448979592 -0.07106382978723408;
        0.5880102040816326 -0.06595744680851062;
        0.6256377551020409 -0.05957446808510641;
        0.6581632653061227 -0.05446808510638296;
        0.7161989795918369 -0.044255319148936156;
        0.7474489795918369 -0.03914893617021281;
        0.7863520408163267 -0.03276595744680849;
        0.878188775510204 -0.019999999999999962;
        0.9368622448979593 -0.013617021276595753;
        0.9623724489795917 -0.01234042553191489;
        1.0000000000000002 -0.011702127659574513;
    ],
    [
        1.000 0.006808510638297904;
        0.9808673469387756 0.009361702127659632;
        0.9285714285714286 0.020212765957446865;
        0.8705357142857142 0.03234042553191491;
        0.8290816326530612 0.03999999999999998;
        0.774872448979592 0.05021276595744678;
        0.7359693877551021 0.0565957446808511;
        0.677295918367347 0.0668085106382979;
        0.6332908163265307 0.07319148936170217;
        0.6001275510204085 0.07765957446808514;
        0.5701530612244898 0.0821276595744681;
        0.5369897959183674 0.0859574468085107;
        0.4987244897959184 0.08978723404255318;
        0.4700255102040816 0.0923404255319149;
        0.4451530612244899 0.0942553191489362;
        0.42346938775510207 0.0961702127659575;
        0.3992346938775511 0.09744680851063836;
        0.3418367346938776 0.10000000000000009;
        0.30038265306122447 0.10000000000000009;
        0.27614795918367346 0.10000000000000009;
        0.23979591836734693 0.09872340425531922;
        0.20790816326530615 0.0961702127659575;
        0.17793367346938777 0.09361702127659577;
        0.15051020408163263 0.09042553191489361;
        0.12627551020408162 0.0859574468085107;
        0.11989795918367346 0.08468085106382983;
        0.11224489795918369 0.08276595744680854;
        0.10012755102040816 0.08021276595744681;
        0.0892857142857143 0.0770212765957447;
        0.08035714285714285 0.07446808510638303;
        0.07079081632653059 0.0706382978723405;
        0.06186224489795919 0.0668085106382979;
        0.05420918367346941 0.06297872340425531;
        0.04910714285714285 0.06042553191489369;
        0.04209183673469388 0.0565957446808511;
        0.037627551020408156 0.05404255319148937;
        0.03188775510204081 0.05021276595744678;
        0.028061224489795922 0.047659574468085164;
        0.024872448979591844 0.045106382978723436;
        0.021683673469387765 0.041914893617021276;
        0.01785714285714282 0.03808510638297874;
        0.014668367346938715 0.0342553191489362;
        0.012117346938775503 0.0310638297872341;
        0.009566326530612235 0.027234042553191506;
        0.007015306122448994 0.023404255319149025;
        0.004464285714285698 0.019574468085106433;
        0.0031887755102040505 0.01574468085106384;
        0.001913265306122458 0.012553191489361681;
        0.0012755102040816202 0.0087234042553192;
        0.0006377551020408101 0.004255319148936176;
        0.0000 -0.00021276595744684768;
        0.001275510204081648 -0.0034042553191488967;
        0.002551020408163268 -0.005957446808510625;
        0.0051020408163265085 -0.00914893617021273;
        0.007653061224489804 -0.011063829787234025;
        0.011479591836734693 -0.013617021276595753;
        0.014668367346938771 -0.015531914893616994;
        0.01849489795918366 -0.016808510638297858;
        0.024234693877551006 -0.018723404255319098;
        0.028061224489795894 -0.019999999999999962;
        0.035714285714285726 -0.021914893617021314;
        0.04400510204081631 -0.023829787234042554;
        0.05484693877551022 -0.025744680851063795;
        0.06887755102040816 -0.027659574468085035;
        0.0816326530612245 -0.0289361702127659;
        0.10012755102040816 -0.030212765957446763;
        0.11862244897959184 -0.03085106382978725;
        0.14540816326530615 -0.032127659574468115;
        0.16964285714285715 -0.032127659574468115;
        0.19515306122448978 -0.032127659574468115;
        0.22321428571428575 -0.03148936170212763;
        0.24808673469387754 -0.03085106382978725;
        0.27359693877551017 -0.030212765957446763;
        0.31122448979591855 -0.0289361702127659;
        0.34948979591836743 -0.027659574468085035;
        0.38392857142857145 -0.026382978723404282;
        0.4260204081632654 -0.024468085106383042;
        0.46428571428571436 -0.023191489361702178;
        0.4942602040816327 -0.021914893617021314;
        0.5286989795918369 -0.02063829787234045;
        0.5510204081632655 -0.019999999999999962;
        0.587372448979592 -0.018723404255319098;
        0.6466836734693877 -0.016808510638297858;
        0.6804846938775511 -0.015531914893616994;
        0.7193877551020409 -0.01425531914893624;
        0.7633928571428572 -0.012978723404255377;
        0.8150510204081634 -0.011063829787234025;
        0.8520408163265307 -0.009787234042553161;
        0.8839285714285714 -0.008510638297872297;
        0.9221938775510203 -0.007234042553191489;
        0.9630102040816328 -0.006595744680851057;
        1.0000 -0.005957446808510625;
    ],
    [
        1.0000 0.0036170212765957444;
        0.9744897959183674 0.009361702127659632;
        0.9362244897959184 0.01829787234042557;
        0.9196428571428572 0.02212765957446816;
        0.9049744897959184 0.02531914893617021;
        0.8934948979591837 0.02787234042553194;
        0.8832908163265307 0.029787234042553234;
        0.8635204081632655 0.03361702127659577;
        0.8507653061224492 0.0361702127659575;
        0.8252551020408163 0.041276595744680844;
        0.8035714285714286 0.045106382978723436;
        0.7716836734693877 0.05021276595744678;
        0.7474489795918369 0.05404255319148937;
        0.721938775510204 0.05723404255319153;
        0.7002551020408163 0.06042553191489369;
        0.6760204081632655 0.06297872340425531;
        0.6505102040816326 0.06553191489361704;
        0.6224489795918369 0.06808510638297877;
        0.590561224489796 0.0706382978723405;
        0.5637755102040816 0.07255319148936173;
        0.5382653061224492 0.07446808510638303;
        0.5012755102040818 0.07574468085106384;
        0.4630102040816327 0.0770212765957447;
        0.427295918367347 0.0770212765957447;
        0.40688775510204084 0.0770212765957447;
        0.3724489795918368 0.0770212765957447;
        0.34693877551020413 0.07574468085106384;
        0.31313775510204084 0.07446808510638303;
        0.28762755102040816 0.07319148936170217;
        0.2716836734693878 0.0719148936170213;
        0.2538265306122449 0.0706382978723405;
        0.22831632653061223 0.06808510638297877;
        0.20663265306122452 0.06553191489361704;
        0.19323979591836737 0.06425531914893617;
        0.17920918367346939 0.06297872340425531;
        0.16964285714285715 0.06170212765957456;
        0.16198979591836737 0.06042553191489369;
        0.15306122448979592 0.05914893617021283;
        0.1428571428571429 0.057872340425531965;
        0.13392857142857145 0.0565957446808511;
        0.12372448979591838 0.054680851063829805;
        0.11352040816326531 0.05276595744680851;
        0.10395408163265307 0.051489361702127645;
        0.09247448979591841 0.04893617021276603;
        0.08545918367346941 0.047659574468085164;
        0.07971938775510204 0.0463829787234043;
        0.07461734693877556 0.045106382978723436;
        0.06760204081632651 0.04305319148936171;
        0.06122448979591835 0.041276595744680844;
        0.057397959183673464 0.03999999999999998;
        0.0535714285714286 0.03872340425531917;
        0.04655612244897958 0.0361702127659575;
        0.04272959183673469 0.034893617021276635;
        0.038265306122448994 0.03297872340425534;
        0.03252551020408165 0.030425531914893666;
        0.028698979591836732 0.02851063829787237;
        0.023596938775510196 0.025957446808510642;
        0.019132653061224497 0.023404255319149025;
        0.015306122448979581 0.020851063829787297;
        0.012117346938775503 0.01829787234042557;
        0.008928571428571425 0.01574468085106384;
        0.005739795918367346 0.01191489361702125;
        0.002551020408163268 0.006808510638297904;
        0.001275510204081648 0.004255319148936176;
        0.0000 -0.00021276595744684768;
        0.001275510204081648 -0.0021276595744680327;
        0.0038265306122448883 -0.004680851063829761;
        0.007653061224489804 -0.007234042553191489;
        0.010204081632653073 -0.008510638297872297;
        0.014668367346938771 -0.009787234042553161;
        0.019132653061224497 -0.010425531914893593;
        0.023596938775510196 -0.011063829787234025;
        0.028061224489795922 -0.011063829787234025;
        0.03316326530612246 -0.011063829787234025;
        0.038265306122448994 -0.011063829787234025;
        0.04400510204081631 -0.010425531914893593;
        0.05102040816326531 -0.010425531914893593;
        0.057397959183673464 -0.009787234042553161;
        0.0656887755102041 -0.008510638297872297;
        0.07653061224489796 -0.007234042553191489;
        0.08545918367346941 -0.005957446808510625;
        0.09566326530612246 -0.004680851063829761;
        0.1058673469387755 -0.0034042553191488967;
        0.12181122448979592 -0.0008510638297872242;
        0.139030612244898 0.0017021276595744483;
        0.14668367346938777 0.0029787234042553123;
        0.163265306122449 0.00553191489361704;
        0.17283163265306123 0.006808510638297904;
        0.18176020408163268 0.008085106382978768;
        0.1938775510204082 0.009361702127659632;
        0.20344387755102045 0.010638297872340496;
        0.2142857142857143 0.01191489361702125;
        0.22767857142857145 0.013191489361702113;
        0.2436224489795919 0.01510638297872341;
        0.2525510204081633 0.01574468085106384;
        0.2716836734693878 0.017021276595744705;
        0.291454081632653 0.01829787234042557;
        0.3125 0.019574468085106433;
        0.3405612244897959 0.020851063829787297;
        0.364795918367347 0.02212765957446816;
        0.3909438775510204 0.023404255319149025;
        0.43239795918367346 0.024042553191489346;
        0.46938775510204084 0.02468085106382978;
        0.5255102040816326 0.02468085106382978;
        0.5720663265306123 0.024042553191489346;
        0.6173469387755102 0.023404255319148914;
        0.6639030612244898 0.02148936170212773;
        0.7136479591836735 0.018936170212766;
        0.7372448979591839 0.017659574468085137;
        0.7684948979591839 0.01574468085106384;
        0.8131377551020409 0.013191489361702113;
        0.8341836734693879 0.01191489361702125;
        0.8533163265306123 0.010000000000000064;
        0.8635204081632655 0.009361702127659632;
        0.8807397959183674 0.008085106382978768;
        0.9011479591836735 0.006170212765957472;
        0.9253826530612246 0.004255319148936176;
        0.9528061224489797 0.0017021276595744483;
        0.9815051020408163 -0.0008510638297872242;
        1.00000 -0.0008510638297872242;
    ],
    [
        1.00000 0.0029787234042553123;
        0.9917091836734695 0.004893617021276608;
        0.9834183673469388 0.006808510638297904;
        0.971938775510204 0.0087234042553192;
        0.9400510204081631 0.014468085106383088;
        0.9202806122448979 0.01829787234042557;
        0.8903061224489794 0.023404255319149025;
        0.8660714285714286 0.027234042553191506;
        0.8392857142857144 0.0310638297872341;
        0.8035714285714286 0.0361702127659575;
        0.7735969387755102 0.03999999999999998;
        0.7423469387755102 0.04382978723404257;
        0.7072704081632655 0.047659574468085164;
        0.6664540816326532 0.051489361702127645;
        0.6275510204081631 0.054680851063829805;
        0.575892857142857 0.0585106382978724;
        0.5344387755102038 0.06170212765957456;
        0.49043367346938743 0.06425531914893623;
        0.44132653061224464 0.06553191489361704;
        0.38903061224489793 0.0668085106382979;
        0.34693877551020397 0.0667085106382979;
        0.3022959183673467 0.06567021276595747;
        0.2627551020408163 0.06425531914893617;
        0.22895408163265307 0.06234042553191488;
        0.19897959183673464 0.06042553191489369;
        0.1626275510204082 0.057872340425531965;
        0.13520408163265307 0.05531914893617024;
        0.11607142857142855 0.05276595744680851;
        0.10012755102040816 0.05011276595744678;
        0.08801020408163265 0.047659574468085164;
        0.07780612244897961 0.045106382978723436;
        0.06823979591836735 0.04255319148936171;
        0.05420918367346941 0.03872340425531917;
        0.0433673469387755 0.034893617021276635;
        0.03380102040816327 0.0310638297872341;
        0.028061224489795922 0.02851063829787237;
        0.023596938775510196 0.025957446808510642;
        0.01913265306122447 0.023404255319149025;
        0.015306122448979581 0.020851063829787297;
        0.011479591836734665 0.01829787234042557;
        0.008928571428571425 0.01574468085106384;
        0.005739795918367346 0.012553191489361681;
        0.002551020408163268 0.008085106382978768;
        0.0006377551020408101 0.004255319148936176;
        0.00000 -0.00021276595744684768;
        0.0012755102040816202 -0.0034042553191488967;
        0.004464285714285726 -0.005957446808510625;
        0.009566326530612235 -0.008510638297872297;
        0.013392857142857123 -0.009787234042553161;
        0.01849489795918366 -0.011063829787234025;
        0.023596938775510196 -0.01234042553191489;
        0.03188775510204081 -0.013617021276595753;
        0.04910714285714285 -0.014893617021276506;
        0.07206632653061223 -0.015531914893616994;
        0.10204081632653061 -0.015531914893616994;
        0.1294642857142857 -0.014893617021276617;
        0.15816326530612246 -0.013617021276595753;
        0.18622448979591838 -0.01234042553191489;
        0.2142857142857143 -0.011063829787234025;
        0.25000000000000006 -0.009787234042553161;
        0.2831632653061225 -0.008510638297872297;
        0.32015306122448983 -0.007234042553191489;
        0.3571428571428572 -0.005957446808510625;
        0.4005102040816327 -0.005219148936170193;
        0.44132653061224486 -0.004042553191489329;
        0.48596938775510207 -0.0034042553191488967;
        0.5293367346938775 -0.0027659574468084647;
        0.5797193877551021 -0.0027659574468084647;
        0.6403061224489797 -0.0027659574468084647;
        0.7091836734693879 -0.0027659574468084647;
        0.7608418367346941 -0.0027659574468084647;
        0.8118622448979593 -0.0027659574468084647;
        0.8571428571428572 -0.0029659574468084647;
        0.8928571428571428 -0.0032042553191488967;
        0.9432397959183674 -0.0034042553191488967;
        0.9693877551020409 -0.003404255319148952;
        1.00000 -0.0034042553191488967;
    ]
]

results = Dict{String,Union{Int, String, Float64, Vector{Float64}, Array{Float64,2}}}()

# Figure 7.7
results["lift distribution VLM rotors off"] = [0.008860759493670795 0.42547770700636944
                                                0.027848101265822683 0.4245222929936306
                                                0.0468354430379746 0.4245222929936306
                                                0.06582278481012652 0.4245222929936306
                                                0.08481012658227843 0.42356687898089174
                                                0.10379746835443032 0.42356687898089174
                                                0.12278481012658227 0.4226114649681529
                                                0.1417721518987341 0.4226114649681529
                                                0.16075949367088604 0.42165605095541403
                                                0.18101265822784807 0.4207006369426752
                                                0.2 0.4197452229299363
                                                0.21898734177215184 0.41878980891719747
                                                0.23670886075949366 0.4178343949044586
                                                0.25569620253164554 0.4159235668789809
                                                0.27594936708860757 0.41496815286624206
                                                0.2949367088607595 0.41305732484076435
                                                0.31392405063291146 0.4111464968152866
                                                0.3329113924050633 0.4092356687898089
                                                0.3607594936708861 0.40445859872611467
                                                0.39746835443037987 0.39777070063694264
                                                0.4341772151898734 0.3910828025477707
                                                0.47088607594936716 0.3834394904458599
                                                0.5075949367088608 0.3748407643312102
                                                0.5430379746835444 0.3652866242038217
                                                0.5810126582278483 0.3557324840764331
                                                0.6151898734177216 0.34617834394904456
                                                0.6531645569620255 0.3347133757961783
                                                0.6898734177215191 0.3232484076433121
                                                0.7265822784810129 0.30987261146496814
                                                0.7620253164556964 0.2964968152866242
                                                0.8000000000000002 0.2812101910828026
                                                0.8354430379746839 0.26305732484076433
                                                0.8721518987341775 0.2429936305732484
                                                0.9088607594936712 0.21815286624203828
                                                0.9455696202531648 0.18471337579617836
                                                0.9822784810126585 0.13312101910828028]

# Figure 7.8
results["lift distribution VLM rotors on"] = [0.00966  0.518
                                                0.0295   0.52
                                                0.0482   0.516
                                                0.0669   0.519
                                                0.0847   0.522
                                                0.104    0.53
                                                0.123    0.541
                                                0.142    0.555
                                                0.161    0.573
                                                0.181    0.602
                                                0.2      0.675
                                                0.219    0.813
                                                0.238    0.874
                                                0.256    0.883
                                                0.275    0.844
                                                0.295    0.754
                                                0.314    0.529
                                                0.333    0.439
                                                0.361    0.419
                                                0.397    0.167
                                                0.434    0.112
                                                0.47     0.163
                                                0.508    0.27
                                                0.543    0.295
                                                0.58     0.304
                                                0.616    0.306
                                                0.654    0.303
                                                0.689    0.297
                                                0.726    0.289
                                                0.762    0.278
                                                0.799    0.265
                                                0.835    0.25
                                                0.872    0.232
                                                0.908    0.209
                                                0.945    0.178
                                                0.981    0.129]

results["normalized cl experimental"] = [0.0461   0.512
                                        0.139    0.579
                                        0.244    0.754
                                        0.287    0.686
                                        0.4      0.331
                                        0.438   -0.0593
                                        0.562    0.32
                                        0.687    0.315
                                        0.809    0.283]

results["normalized cl upper error bars"] = [0.015
                                            0.016
                                            0.045
                                            0.183
                                            0.187
                                            0.0417
                                            0.014
                                            0.012
                                            0.013]

results["normalized cl lower error bars"] = [0.015
                                            0.018
                                            0.044
                                            0.184
                                            0.183
                                            0.0446
                                            0.011
                                            0.014
                                            0.012]

setup = Dict{String,Union{Int, String, Float64, Vector{Float64}, Array{Float64,2}}}()
setup["Re"] = 0.3e6
setup["Vinf"] = 19.0 # m/s
setup["wing_aoa"] = 4.0 # deg.
setup["J"] = 0.695
setup["rotor_CT"] = 0.30

epema_eta = [
    [
        0.39907904834996144 0.4526289577244005;
        0.4973138910207212 0.5378398458549013;
        0.600153491941673 0.6145449943665193;
        0.6999232540291632 0.6710341111346976;
        0.798158096699923 0.724330105648177;
        0.8979278587874134 0.7520958181610362;
        0.9976976208749039 0.751138126418576;
        1.0990023023791249 0.6437990888457081;
        1.2033768227168071 0.27476118939925875;
    ],
    [
        0.5981966719565649 0.5397182236147184;
        0.696552040863189 0.6025364977471156;
        0.7979670461445826 0.6600372504094993;
        0.8993738869526614 0.7132826795801419;
        1.0007643988141104 0.7580174617673022;
        1.097527695424323 0.79104765501018;
        1.1988753438008684 0.8134419888656995;
    ],
    [
        0.6979053023151386 0.5078563665032071;
        0.7993468421348057 0.5791869205137494;
        0.8976899643314572 0.6356222094085349;
        1.0006337672410712 0.6899322858994442;
        1.0974235983895575 0.7367922804904807;
        1.1987855345944043 0.7666334304565471;
        1.2986003031060718 0.7900907787376703;
        1.3999622393109181 0.8199319287037368;
        1.499756596639299 0.8327509682555072;
        1.601034846992667 0.8189750524312269;
        1.6990922126232961 0.7264740191150731;
        1.8012256915564038 0.35844323905067577
    ]
]

epema_ct = [
    [
    0.3985663082437276 0.30549662487946005;
    0.49749103942652323 0.28312439729990363;
    0.5992831541218637 0.2595949855351978;
    0.7010752688172042 0.22912246865959504;
    0.7985663082437275 0.19749276759884285;
    0.9003584229390678 0.15969141755062688;
    0.9992831541218636 0.11417550626808104;
    1.101075268817204 0.06325940212150438;
    1.2014336917562722 0.01311475409836066;
    ],
    [
    0.5992831541218637 0.3270973963355835;
    0.6982078853046594 0.30703953712632603;
    0.7999999999999998 0.28466730954676955;
    0.9003584229390678 0.2588235294117648;
    0.9992831541218636 0.22757955641272906;
    1.0982078853046593 0.19286403085824497;
    1.1985663082437275 0.1581485053037609;
    ],
    [
    0.6967741935483871 0.3579556412729027;
    0.7985663082437275 0.3525554484088718;
    0.897491039426523 0.33905496624879466;
    0.9978494623655914 0.3186113789778207;
    1.0967741935483868 0.2950819672131148;
    1.1985663082437275 0.2619093539054967;
    1.297491039426523 0.22256509161041468;
    1.3992831541218635 0.18707810993249763;
    1.4982078853046594 0.15043394406943106;
    1.5985663082437271 0.11031822565091615;
    1.697491039426523 0.06325940212150438;
    1.7992831541218635 0.015429122468659573;
    ]
]

ct_xrotor = [
    [
        0.3985663082437276 0.32015429122468664;
        0.49749103942652323 0.2989392478302797;
        0.5992831541218637 0.2626808100289297;
        0.6996415770609317 0.22873674059787855;
        0.7985663082437275 0.19749276759884285;
        0.9003584229390678 0.14773384763741565;
        0.9978494623655914 0.10183220829315337;
        1.0996415770609316 0.054387656702025056;
    ],
    [
        0.5992831541218637 0.3502410800385729;
        0.6967741935483871 0.31205400192864036;
        0.7985663082437275 0.2808100289296047;
        0.9003584229390678 0.2464802314368371;
        0.9978494623655914 0.21947926711668278;
        1.0967741935483868 0.18090646094503376;
        1.1985663082437275 0.13654773384763746
    ],
    [
        0.6967741935483871 0.3988428158148506;
        0.7985663082437275 0.37801350048216015;
        0.8974910394265232 0.3529411764705883;
        0.9978494623655914 0.3232401157184186;
        1.0967741935483868 0.2950819672131148;
        1.1985663082437275 0.2595949855351978;
        1.3992831541218638 0.18399228543876572;
        1.496774193548387 0.1307618129218901;
        1.5985663082437271 0.08563162970106075;
        1.697491039426523 0.035486981677917084;
    ]
]

function cosinepoints(Npoints)
    xupper_desired = 1.0 .- sin.(range(0,pi/2,length=Npoints))
    # xupper_desired[1] = 1.0
    # xupper_desired[end] = 0.0
    xlower_desired = reverse(xupper_desired)

    return xupper_desired, xlower_desired
end

function i_nearby(vec, targetvalue, npoints = 5)
    sortedvec = sort(vec) # sort in ascending order
    itarget_sorted = findfirst((x) -> x >= targetvalue, sortedvec)
    if isodd(npoints)
        halfrange = Int((npoints-1)/2)
        itargets_sorted = itarget_sorted-halfrange : itarget_sorted+halfrange
    else
        halfrange = Int(npoints/2)
        itargets_sorted = itarget_sorted-halfrange : itarget_sorted+halfrange-1
    end
    targetvalues = sortedvec[itargets_sorted]
    itargets = Int64[]
    for (i,val) in enumerate(targetvalues)
        push!(itargets, findfirst((x) -> isapprox(x,val), vec))
    end
    return itargets
end

function get_midpointline(airfoil, midpoint_xc = 0.5)
    midpoint_i = findfirst((x) -> isapprox(x, minimum(abs.(airfoil[:,1] .- midpoint_xc))), abs.(airfoil[:,1] .- midpoint_xc))
    midpoint_x = airfoil[midpoint_i,1]
    target_i = i_nearby(airfoil[:,1], midpoint_x, 7) # find the indicies of the 5 points with nearest x coordinates
    upper_y = maximum(airfoil[target_i,2])
    lower_y = minimum(airfoil[target_i,2])
    midpoint_y = (upper_y + lower_y) / 2
    # get midpoint chord line
    mid_x = [0.0, midpoint_xc, 1.0]
    mid_y = [0.0, midpoint_y, 0.0]
    get_mid_y(x) = FM.linear(mid_x, mid_y, x)
    return get_mid_y
end

get_leadingedge_i(airfoil) = findfirst((x) -> x == minimum(airfoil[:,1]), airfoil[:,1])

function condition_airfoils!(rotor, Npoints_cosine, Npoints_smoothed;
        smoothing_n = 30,
        smoothing_degree = 3,
    )
    # sort airfoil data
    rotor["airfoils"] = similar(rotor["airfoils_raw"])
    for (i,airfoil) in enumerate(rotor["airfoils_raw"])
        points = [(airfoil[i,1], airfoil[i,2]) for i in 1:length(airfoil[:,1])]

        # # find upper apex
        # i_apex_upper = findfirst((x) -> x == maximum(airfoil[:,2]), airfoil[:,2])
        # x_apex_upper = airfoil[i_apex_upper,1]
        # # find point directly below upper apex
        # i_apex_lower = findfirst((x) -> x > airfoil[i_apex_upper,1], airfoil[i_apex_upper+1:end,1]) + i_apex_upper
        # x_apex_lower = airfoil[i_apex_lower,1]

        # find midpoint
        midpoint_xc = 0.4
        get_mid_y = get_midpointline(airfoil, midpoint_xc)
        # upper surface
        upper_i = findall((x) -> x[2] > get_mid_y(x[1]), points)
        upper_points = deepcopy(points[upper_i])
        upper_points_sorted = sort(upper_points, by = (x) -> x[1], rev = true)
        upper_points_matrix = [upper_points_sorted[i][j] for i in 1:length(upper_points_sorted), j in 1:2]
        # lower surface
        # lower_i = findall((x) -> x<=0, airfoil[:,2])
        lower_i = findall((x) -> x[2] <= get_mid_y(x[1]), points)
        # lower_points = [(point[i,1], point[i,2]) for i in lower_i]
        lower_points = deepcopy(points[lower_i])
        lower_points_sorted = sort(lower_points, by = (x) -> x[1], rev = false)
        lower_points_matrix = [lower_points_sorted[i][j] for i in 1:length(lower_points_sorted), j in 1:2]

        rotor["airfoils"][i] = vcat(upper_points_matrix, lower_points_matrix)
    end
    # interpolate to cosine spacing
    # rotor["cosineairfoils"] = similar(rotor["airfoils"])
    rotor["cosineairfoils"] = [zeros(Npoints_cosine*2,2) for i = 1:length(rotor["airfoils"])]
    for (i,airfoil) in enumerate(rotor["airfoils"])
        # interpolate cosine spaced points
        i_leadingedge = get_leadingedge_i(airfoil)
        # get desired points
        xupper_interpolated, xlower_interpolated = cosinepoints(Npoints_cosine)
        @assert length(xupper_interpolated) + length(xlower_interpolated) == Npoints_cosine * 2
        # interpolate
        cosineairfoil = zeros(2*Npoints_cosine,2)
        # afinter_upper = FM.Akima(airfoil[1:i_leadingedge,1], airfoil[1:i_leadingedge,2])
        # afinter_lower = FM.Akima(airfoil[i_leadingedge:end,1], airfoil[i_leadingedge:end,2])
        cosineairfoil[:,1] .= vcat(xupper_interpolated, xlower_interpolated)
        # upper surface
        x_upper = reverse(airfoil[1:i_leadingedge,1])
        y_upper = reverse(airfoil[1:i_leadingedge,2])
        cosine_x_upper = reverse(xupper_interpolated)
        cosine_y_upper = FM.linear(x_upper, y_upper, cosine_x_upper)
        reverse!(cosine_y_upper)
        cosineairfoil[1:Npoints_cosine, 2] .= cosine_y_upper
        # lowerr surface
        x_lower = airfoil[i_leadingedge:end,1] #TODO: do I need the +1?
        y_lower = airfoil[i_leadingedge:end,2]
        cosine_x_lower = xlower_interpolated
        cosine_y_lower = FM.linear(x_lower, y_lower, cosine_x_lower)
        cosineairfoil[Npoints_cosine+1:end,2] .= cosine_y_lower
        # store airfoil
        rotor["cosineairfoils"][i] .= cosineairfoil
    end
    # smooth polars
    rotor["airfoils_smoothed"] = [zeros(Npoints_smoothed*2 - 1,2) for i = 1:length(rotor["airfoils"])]
    # for (i,airfoil) in enumerate(rotor["cosineairfoils"])
    for (i,airfoil) in enumerate(rotor["airfoils"])
        # make splines
        points = [[airfoil[i,1], airfoil[i,2]] for i in 1:size(airfoil)[1]]
        n_controlpoints = smoothing_n
        p = smoothing_degree
        bspline = S.leastsquarescurve(points, n_controlpoints, p)
        # get coordinates
        t_vec1 = 0.5 * sin.(range(0, pi/2, length = Npoints_smoothed))
        t_vec2 = 1 .- reverse(t_vec1)
        t_vec = vcat(t_vec1, t_vec2[2:end])
        smoothedairfoil = zeros(length(t_vec),2)
        for i = 1:length(t_vec)
            smoothedairfoil[i,1], smoothedairfoil[i,2] = S.curvepoint(bspline, t_vec[i])
        end
        rotor["airfoils_smoothed"][i] .= smoothedairfoil
    end
    # stretch to start and end at (1,0)
    rotor["airfoils_aligned"] = similar(rotor["airfoils_smoothed"])
    for (i,airfoil) in enumerate(rotor["airfoils_smoothed"])
        alignedairfoil = deepcopy(airfoil)
        # move leading edge to (0,0) (translate entire airfoil)
        i_leadingedge = get_leadingedge_i(alignedairfoil)
        leadingedgey = alignedairfoil[i_leadingedge, 2]
        leadingedgex = alignedairfoil[i_leadingedge, 1]
        alignedairfoil[:,1] .-= leadingedgex
        alignedairfoil[:,2] .-= leadingedgey
        @assert isapprox(alignedairfoil[i_leadingedge,1], 0; atol=1e-9) && isapprox(alignedairfoil[i_leadingedge,2], 0; atol=1e-9) "leading edge not at zero"
        # move first point to (1,0)
        firstpoint = alignedairfoil[1,:]
        firstdisplacement = [1.0,0.0] .- firstpoint
        # find upper apex
        # i_apex_upper = findfirst((x) -> x == maximum(alignedairfoil[:,2]), alignedairfoil[:,2])
        # x_apex_upper = alignedairfoil[i_apex_upper,1]
        i_apex_upper = i_leadingedge
        x_apex_upper = leadingedgex
        # translate all upper points proportional to how far the x coordinate is from the leading edge
        for i=1:i_apex_upper
            alignedairfoil[i,:] .+= firstdisplacement .* (alignedairfoil[i,1] - x_apex_upper) / (firstpoint[1] - x_apex_upper)
            # alignedairfoil[i,:] .+= [1.0,0.0]
        end
        # move this point to (1,0)
        lastpoint = alignedairfoil[end,:]
        lastdisplacement = [1.0, 0.0] .- lastpoint
        # find point directly below upper apex
        # i_apex_lower = findfirst((x) -> x > alignedairfoil[i_apex_upper,1], alignedairfoil[i_apex_upper+1:end,1]) + i_apex_upper
        # x_apex_lower = alignedairfoil[i_apex_lower,1]
        i_apex_lower = i_leadingedge
        x_apex_lower = leadingedgex
        # translate all upper points proportional to how far the x coordinate is from the leading edge
        npoints = size(alignedairfoil)[1]
        for i=i_apex_lower:npoints
            alignedairfoil[i,:] .+= lastdisplacement .* (alignedairfoil[i,1] - x_apex_lower) / (lastpoint[1] - x_apex_lower)
        end
        # store airfoil
        rotor["airfoils_aligned"][i] = alignedairfoil
    end
    return nothing
end

function interpolate_contours!(rotor, Npoints_condition, Npoints_smoothed, Npoints_interpolate, rs_desired;
    rs = [0.230, 0.405, 0.790, 1.000],
    kwargs...
    )
    condition_airfoils!(rotor, Npoints_condition, Npoints_smoothed; kwargs...)
    rotor["airfoils_interpolated"] = Vector{Array{Float64,2}}(undef,length(rs_desired)) # [zeros(Npoints * 2 - 1,2) for i in 1:length(rs_desired)]
    for i in 1:length(rotor["airfoils_interpolated"])
    # for (i,airfoil) in enumerate(rotor["airfoils_interpolated"])
        i_inside = 0
        i_outside = 0
        if rs_desired[i] <= rs[1]
            # println("\n\t<= rs[1]")
            i_inside = 1
            i_outside = 1
            # rotor["airfoils_interpolated"][i] = deepcopy(rotor["airfoils_aligned"][1])
            # continue
        elseif rs_desired[i] >= rs[end]
            # println("\n\t>= rs[end]")
            # rotor["airfoils_interpolated"][i] = deepcopy(rotor["airfoils_aligned"][end])
            # continue
            i_inside = length(rs)
            i_outside = length(rs)
        else
            # println("\n\t< rs[end], > rs[1]")
            # get bracket
            for j=2:length(rs)
                # println("\t\trs[j] = $(rs[j])")
                if rs[j] > rs_desired[i]
                    i_inside = j-1
                    i_outside = j
                    # println("\t\tGot it!\n\t\ti_inside = $i_inside\n\t\ti_outside = $i_outside")
                    break
                end
            end
        end
        @assert i_inside > 0 && i_outside > 0 "Bracket failed in airfoil interpolation"
        xpoints_upper, xpoints_lower = cosinepoints(Npoints_interpolate)
        # get inside points
        i_leadingedge_inside = get_leadingedge_i(rotor["airfoils_aligned"][i_inside])
        xs_upper_inside = reverse(rotor["airfoils_aligned"][i_inside][1:i_leadingedge_inside,1])
        ys_upper_inside = reverse(rotor["airfoils_aligned"][i_inside][1:i_leadingedge_inside,2])
        xs_lower_inside = rotor["airfoils_aligned"][i_inside][i_leadingedge_inside:end,1]
        ys_lower_inside = rotor["airfoils_aligned"][i_inside][i_leadingedge_inside:end,2]
        # align inside points
        ys_aligned_upper_inside = FM.linear(xs_upper_inside, ys_upper_inside, reverse(xpoints_upper))
        ys_aligned_lower_inside = FM.linear(xs_lower_inside, ys_lower_inside, xpoints_lower)
        # check if inside and outside are the same
        if i_inside == i_outside
            ys_interpolated = vcat(reverse(ys_aligned_upper_inside), ys_aligned_lower_inside[2:end])
        else
            # get outside points
            i_leadingedge_outside = get_leadingedge_i(rotor["airfoils_aligned"][i_outside])
            xs_upper_outside = reverse(rotor["airfoils_aligned"][i_outside][1:i_leadingedge_outside,1])
            ys_upper_outside = reverse(rotor["airfoils_aligned"][i_outside][1:i_leadingedge_outside,2])
            xs_lower_outside = rotor["airfoils_aligned"][i_outside][i_leadingedge_outside:end,1]
            ys_lower_outside = rotor["airfoils_aligned"][i_outside][i_leadingedge_outside:end,2]
            # align outside points
            ys_aligned_upper_outside = FM.linear(xs_upper_outside, ys_upper_outside, reverse(xpoints_upper))
            ys_aligned_lower_outside = FM.linear(xs_lower_outside, ys_lower_outside, xpoints_lower)
            # interpolate to desired radial stations
            alpha = (rs[i_outside] - rs_desired[i]) / (rs[i_outside] - rs[i_inside])
            ys_interpolated_upper = ys_aligned_upper_inside * alpha .+ ys_aligned_upper_outside * (1 - alpha)
            ys_interpolated_lower = ys_aligned_lower_inside * alpha .+ ys_aligned_lower_outside * (1 - alpha)
            ys_interpolated = vcat(reverse(ys_interpolated_upper), ys_interpolated_lower[2:end])
        end
        # create interpolated airfoil
        airfoil = zeros(length(ys_interpolated), 2)
        airfoil[:,1] .= vcat(xpoints_upper, xpoints_lower[2:end])
        airfoil[:,2] .= ys_interpolated
        rotor["airfoils_interpolated"][i] = airfoil
    end
    return nothing
end

# condition_airfoils!(rotor)
function plot_contours(rotor, Npoints, Npoints_smoothed, plot_directory;
    extension = ".pdf",
    smoothing_degree = 3,
    smoothing_n = 30
)
    condition_airfoils!(rotor, Npoints, Npoints_smoothed;
        smoothing_degree = smoothing_degree,
        smoothing_n = smoothing_n
    )
    plotnames = ["epema_0230", "epema_0405", "epema_0790", "epema_1000"]
    for i in 1:length(rotor["airfoils"])
        fig = plt.figure("Epema_airfoils_$i")
        fig.clear()
        fig.add_subplot(111)
        a = fig.get_axes()[1]
        a.plot(rotor["airfoils"][i][:,1], rotor["airfoils"][i][:,2], "-+", label="raw")
        # a.plot(rotor["cosineairfoils"][i][:,1], rotor["cosineairfoils"][i][:,2], "x-", label="cosine")
        # a.plot(rotor["airfoils_smoothed"][i][:,1], rotor["airfoils_smoothed"][i][:,2], "--", label="smoothed")
        a.plot(rotor["airfoils_aligned"][i][:,1], rotor["airfoils_aligned"][i][:,2], label="aligned")
        # plot midpoint line
        line = get_midpointline(rotor["airfoils_raw"][i], 0.4)
        linex = range(0,1.0, length=100)
        a.plot(linex, line.(linex), label = "midpoint line")
        a.legend()
        savepath = joinpath(plot_directory, plotnames[i] * extension)
        fig.savefig(savepath, bbox_inches="tight")
    end
    return rotor
end

function plot_interpolated_contours(rotor, Npoints_condition, Npoints_smoothed, Npoints_interpolate, plot_directory;
            rs_desired = [0.207, 0.3, 0.4, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0],
            rs = [0.230, 0.405, 0.790, 1.000],
            plotname = "epema_interpolated.pdf",
            kwargs...
        )
    interpolate_contours!(rotor, Npoints_condition, Npoints_smoothed, Npoints_interpolate, rs_desired; rs = rs, kwargs...)
    # names_0 = ["epema_0230", "epema_0405", "epema_0790", "epema_1000"]
    locations_interpolated = ["0" * "$(Int(round(x, digits=0)))" for x in rs_desired[1:end-1] .* 1000]
    push!(locations_interpolated, "1000")
    # names_interpolated = "epema_interpolated_" .* locations_interpolated
    fig = plt.figure("Epema_airfoils_interpolated")
    fig.clear()
    fig.set_size_inches(14, 6, forward=true)
    fig.add_subplot(111)
    a = fig.get_axes()[1]
    for (i, airfoil) in enumerate(rotor["airfoils_interpolated"]) # [[1,2,3]])
        cratio = i / length(rotor["airfoils_interpolated"])
        a.plot(airfoil[:,1], airfoil[:,2], label="r/R = " * locations_interpolated[i], color=(0.05, 0.85-cratio*0.7, 0.15 + 0.75 * cratio))
        # # plot midpoint line
        # line = get_midpointline(rotor["airfoils_interpolated"][i], 0.4)
        # linex = range(0,1.0, length=100)
        # a.plot(linex, line.(linex), label = "midpoint line")
    end
    a.legend(loc="upper left", bbox_to_anchor=(1.01,1))
    a.set_xlabel(LS.L"x/c")
    a.set_ylabel(LS.L"y/c")
    fig.tight_layout()
    savepath = joinpath(plot_directory, plotname)
    fig.savefig(savepath, bbox_inches="tight")
    return nothing
end

function write_all_contours(dir, rotor, plot_directory, Npoints, Npoints_smoothed;
        extension = ".pdf",
        smoothing_n = 30,
        smoothing_degree = 3
    )
    println("Conditioning contours...")
    plot_contours(rotor, Npoints, Npoints_smoothed, plot_directory;
        extension = extension,
        smoothing_n = smoothing_n,
        smoothing_degree = smoothing_degree,
    )
    println("\nWriting files...")
    # write_contours(dir, rotor)
    # write_contours_cosine(dir, rotor)
    write_contours_aligned(dir, rotor)
    # write_contours_smoothed(dir, rotor)
    println("Done.")
    return nothing
end

# write contour files
# function write_contours(dir, rotor)
#     if !isdir(dir)
#         mkpath(dir)
#     end
#     filenames = joinpath.(dir, ["epema_0230", "epema_0405", "epema_0790", "epema_1000"] .* ".dat")
#     println("Writing to:\n\t$filenames")
#     for (i,airfoil) in enumerate(rotor["airfoils"])
#         open(filenames[i], "w") do io
#             DF.writedlm(io,["x" "y"], ',')
#             DF.writedlm(io, airfoil, ',')
#         end
#     end
# end

# function write_contours_cosine(dir, rotor)
#     if !isdir(dir)
#         mkpath(dir)
#     end
#     filenames = joinpath.(dir, ["epema_cosine_0230", "epema_cosine_0405", "epema_cosine_0790", "epema_cosine_1000"] .* ".dat")
#     println("Writing to:\n\t$filenames")
#     for (i,airfoil) in enumerate(rotor["cosineairfoils"])
#         open(filenames[i], "w") do io
#             DF.writedlm(io,["x" "y"], ',')
#             DF.writedlm(io, airfoil, ',')
#         end
#     end
# end

function write_contours_aligned(dir, rotor)
    if !isdir(dir)
        mkpath(dir)
    end
    filenames = joinpath.(dir, ["epema_aligned_0230", "epema_aligned_0405", "epema_aligned_0790", "epema_aligned_1000"] .* ".dat")
    println("Writing to:\n\t$filenames")
    for (i,airfoil) in enumerate(rotor["airfoils_aligned"])
        open(filenames[i], "w") do io
            DF.writedlm(io,["x" "y"], ',')
            DF.writedlm(io, airfoil, ',')
        end
    end
end

# function write_contours_smoothed(dir, rotor)
#     if !isdir(dir)
#         mkpath(dir)
#     end
#     filenames = joinpath.(dir, ["epema_smoothed_0230", "epema_smoothed_0405", "epema_smoothed_0790", "epema_smoothed_1000"] .* ".dat")
#     println("Writing to:\n\t$filenames")
#     for (i,airfoil) in enumerate(rotor["airfoils_smoothed"])
#         open(filenames[i], "w") do io
#             DF.writedlm(io,["x" "y"], ',')
#             DF.writedlm(io, airfoil, ',')
#         end
#     end
# end

function write_contours(dir, airfoils, filenames)
    if !isdir(dir)
        mkpath(dir)
    end
    println("Writing to:\n\t$filenames")
    for (i,airfoil) in enumerate(airfoils)
        open(joinpath(dir,filenames[i]), "w") do io
            DF.writedlm(io,["x" "y"], ',')
            DF.writedlm(io, airfoil, ',')
        end
    end
end

function scans_to_interpolated_contours(contourdirectory, plot_directory, rs_desired)
    println("================= BEGIN BUILDING CONTOURS =================")
    smoothing_n = 30
    smoothing_degree = 2
    Npoints = 20
    Npoints_smoothed = 300
    Npoints_interpolate = 130
    if !isdir(contourdirectory); mkpath(contourdirectory); end
    if !isdir(plot_directory); mkpath(plot_directory); end
    write_all_contours(contourdirectory, rotor, plot_directory, Npoints, Npoints_smoothed;
        extension = ".pdf",
        smoothing_n = smoothing_n,
        smoothing_degree = smoothing_degree
    )

    plot_interpolated_contours(rotor, Npoints, Npoints_smoothed, Npoints_interpolate, plot_directory;
                rs_desired = rs_desired,
                rs = [0.230, 0.405, 0.790, 1.000],
                plotname = "epema_interpolated_bspline_n$(smoothing_n).pdf",
                smoothing_n = smoothing_n,
                smoothing_degree = smoothing_degree
            )
    # write to files
    locations = string.(Int.(round.(rs_desired .* 1000, digits=0)))
    locations[1:end-1] = "0" .* locations[1:end-1]
    filenames = "epema_interpolated_bspline_n$(smoothing_n)_" .* locations .* ".dat"
    write_contours(contourdirectory, rotor["airfoils_interpolated"], filenames)
    println("========================= FINISHED =========================")
    return nothing
end

end # module

max_stars_repo_name: byuflowlab/AircraftSystems

max_issues_repo_name: byuflowlab/AircraftSystems

max_forks_repo_name: byuflowlab/AircraftSystems


text:
using IcanProj
using NetCDF
using DataFrames
using PyPlot
using Dates
using CSV

# Settings

timeplot = DateTime(2011,4,1,0)

variable = "swe"

cfg = 1

file_forest = "/data04/jmg/fsm_simulations/netcdf/fsmres_forest/results_$(cfg)/$(variable)_1km.nc"

file_open = "/data04/jmg/fsm_simulations/netcdf/fsmres_open/results_$(cfg)/$(variable)_1km.nc"

# Load data

df_links = CSV.read(joinpath(dirname(pathof(IcanProj)), "..", "data", "df_links.csv")) |> DataFrame

tarray = ncread(file_forest, "time_array")

time = [DateTime(tarray[1,i],tarray[2,i],tarray[3,i],tarray[4,i]) for i in 1:size(tarray, 2)]

itime = findfirst(isequal(timeplot), time)

data_forest = ncread(file_forest, variable, start = [1,itime], count=[-1,1])

data_open = ncread(file_open, variable, start = [1,itime], count=[-1,1])

data_forest = dropdims(data_forest, dims = 2)

data_open = dropdims(data_open, dims = 2)

# Create dataframe for linking results

ind_senorge = convert.(Int, ncread(file_forest, "id"))

df = DataFrame(ind_senorge = ind_senorge,
               ind_linear = 1:length(ind_senorge))

df_final = join(df_links, df, on=:ind_senorge)

# Plots

map_forest = project_results(data_forest, df_final, :ind_linear)

figure()
imshow(map_forest)
cb = colorbar()
cb[:set_label]("SWE (mm)")

map_open = project_results(data_open, df_final, :ind_linear)

figure()
imshow(map_open)
colorbar()

figure()
imshow(map_open .- map_forest)
cb = colorbar()
cb[:set_label]("SWE open minus forest (mm)")
title("Date: $(Dates.format(time[itime], "yyyy-mm-dd"))")

ext: jl


text:
using GCoptimization
using Base.Test

# test gco types
@test isbits(GCOSparseDataCost) == true

# test low-level APIs
include("lowlevel.jl")

# test high-level APIs
include("highlevel.jl")

# misc.
gridgraph = gco_create(2, 2, 9)

gco_setdatacost(gridgraph, 1, 3, 1)
gco_setdatacost(gridgraph, 2, 3, 2)
gco_setdatacost(gridgraph, 3, 3, 3)
gco_setdatacost(gridgraph, 4, 3, 4)

gco_setlabeling(gridgraph, [3 3 3 3])
gco_setlabelcost(gridgraph, 7)

@test gco_energy(gridgraph)[2] == 10
@test gco_energy(gridgraph)[4] == 7

gco_setsmoothcost(gridgraph, 3, 3, 5)
@test gco_energy(gridgraph)[3] == 2 * 2 * 5

gridgraph2 = gco_create(2, 2, 9)

gco_setdatacost(gridgraph2, 1, 4, 1)
gco_setdatacost(gridgraph2, 2, 7, 2)
gco_setdatacost(gridgraph2, 3, 3, 3)
gco_setdatacost(gridgraph2, 4, 2, 4)

gco_setlabeling(gridgraph2, [1 3 2 4], [4 3 7 2])
@test gco_energy(gridgraph2)[2] == 10

@test_throws AssertionError gco_setlabeling(gridgraph2, [1 3 2 4], [4 3 7 2 5])
@test_throws AssertionError gco_setlabeling(gridgraph2, [1 3 2 4], [4 3 10 2])

gco_setlabelorder(gridgraph2, true)


text:
# %% imports

function norm_obs(obs::Array{Float64})
    pos = obs[1]
    theta = mod(obs[3]+pi, 2pi) - pi
    vel = [obs[2], obs[4]]
    pos_scaled = (pos + 2.4)/4.8
    cone = 12*pi/180
    theta_scaled = (theta + cone)/(2*cone)
    vel_scaled = (vel .+ 7.5)/15
    obs_scaled = [pos_scaled, vel_scaled[1], theta_scaled, vel_scaled[2]]
    return obs_scaled
end

function sig(z::Float64, alpha::Float64)
    return 1.0/(1.0+exp(-z/alpha))
end

function norm_sig(obs::Array{Float64})
    pos = obs[1] # in [-2.4,2.4]
    theta = mod(obs[3]+pi,2pi) - pi # in [-pi, pi]
    vel = [obs[2], obs[4]] # in [-15,15]
    alpha_pos = 2.4/5
    alpha_theta = pi/5
    alpha_vel = 15/5
    pos_scaled = sig(pos,alpha_pos)
    theta_scaled = sig(theta,alpha_theta)
    vel_scaled = sig.(vel,alpha_vel)
    obs_scaled = [pos_scaled, vel_scaled[1], theta_scaled, vel_scaled[2]]
    return obs_scaled
end

# %%
# poisson spikes for 4 neuron input
function poisson_spike(obs::Array{Float64})
    spikes = []
    obs_s = norm_obs(obs)
    for idx in eachindex(obs_s)
        value = rand()
        if value > obs_s[idx]
            push!(spikes, idx)
        end
    end
    spikes
end

# # no stochasticity input spikes
function int_spikes_normed(obs::Array{Float64})
    spikes =  Tuple{Int64,Float64}[]
    obs_s = norm_sig(obs)
    for idx in eachindex(obs_s)
        push!(spikes, (idx,obs_s[idx]))
    end
    return spikes
end

# # no stochasticity input spikes
function int_spikes(obs::Array{Float64})
    spikes =  Tuple{Int64,Float64}[]
    for idx in eachindex(obs)
        push!(spikes, (idx,obs[idx]))
    end
    return spikes
end

# # %% test int_spikes

max_stars_repo_name: SuReLI/RL-STDP

max_issues_repo_name: SuReLI/RL-STDP

max_forks_repo_name: SuReLI/RL-STDP


text:
# Tests for DEAPeers and DEAPeersDMU

# Struct for testing DEA model extension without names and peers information
struct wrongDEAmodel <: AbstractDEAModel
    n::Int64
    eff::Vector
end

@testset "DEAPeers" begin

    # Test agains results in R
    X = [5 13; 16 12; 16 26; 17 15; 18 14; 23 6; 25 10; 27 22; 37 14; 42 25; 5 17]
    Y = [12; 14; 25; 26; 8; 9; 27; 30; 31; 26; 12]

    P = peers(dea(X, Y, orient = :Input, rts = :CRS))

    @test typeof(P) == DEAPeers
    @test eltype(P) == DEAPeersDMU

    @test size(P) == (11,)
    @test ispeer(P, 1, 1) == true
    @test ispeer(P, 1, 2) == false
    @test ispeer(P, 2, 1) == false
    @test ispeer(P, 2, 3) == false

    @test convert(Matrix, P) ≈
    [1.000000000  0  0 0.0000000000  0  0 0.00000000000  0  0   0   0
    0.000000000  0  0 0.4249783174  0  0 0.10928013877  0  0   0   0
    1.134321653  0  0 0.4380053908  0  0 0.00000000000  0  0   0   0
    0.000000000  0  0 1.0000000000  0  0 0.00000000000  0  0   0   0
    0.000000000  0  0 0.2573807721  0  0 0.04844814534  0  0   0   0
    0.000000000  0  0 0.0000000000  0  0 0.33333333333  0  0   0   0
    0.000000000  0  0 0.0000000000  0  0 1.00000000000  0  0   0   0
    0.000000000  0  0 1.0348650979  0  0 0.11457435013  0  0   0   0
    0.000000000  0  0 0.0000000000  0  0 1.14814814815  0  0   0   0
    0.000000000  0  0 0.4905660377  0  0 0.49056603774  0  0   0   0
    0.000000000  0  0 0.0000000000  0  0 0.00000000000  0  0   0   1]

    # Test using DMU names
    firms = ["A"; "B"; "C"; "D"; "E"; "F"; "G"; "H"; "I"; "J"; "L"]

    Pn = peers(dea(X, Y, orient = :Input, rts = :CRS, names = firms))

    @test typeof(P) == DEAPeers
    @test eltype(P) == DEAPeersDMU

    @test size(P) == (11,)
    @test length(P) == 11
    @test firstindex(P) == 1
    @test lastindex(P) == 11

    @test ispeer(Pn, "A", "A") == true
    @test ispeer(Pn, "A", "B") == false
    @test ispeer(Pn, "B", "A") == false
    @test ispeer(Pn, "B", "C") == false

    # Test peers of a specific DMU
    P2 = P[2]

    @test typeof(P2) == DEAPeersDMU
    @test eltype(P2) == Tuple{Tuple{Int64,String},Float64}

    @test size(P2) == (2,)
    @test length(P2) == 2
    @test firstindex(P2) == 1
    @test lastindex(P2) == 2

    @test ispeer(P2, 1) == false
    @test ispeer(P2, 2) == false
    @test ispeer(P2, 3) == false
    @test ispeer(P2, 4) == true
    @test ispeer(P2, 7) == true

    # Test peers of a specific DMU using names
    P2n = Pn[2]

    @test ispeer(P2n, "A") == false
    @test ispeer(P2n, "B") == false
    @test ispeer(P2n, "C") == false
    @test ispeer(P2n, "D") == true
    @test ispeer(P2n, "G") == true

    # Test elements of DEAPeersDMU
    @test P2n[1] == ((4, "D"), 0.424978317432784)
    @test P2n[2] == ((7, "G"), 0.10928013876843023)

    # Sum function
    @test sum(P, dims = 2) ≈ [ 1.0
            0.5342584562012143
            1.5723270440251573
            1.0
            0.30582891748675245
            0.3333333333333333
            1.0
            1.1494394480719479
            1.1481481481481481
            0.9811320754716981
            1.0]

    @test sum(peers(dea(X, Y, rts = :VRS)), dims = 2) ≈ ones(11, 1)

    # Test references names
    Pnamesref = peers(dea(X, Y), namesref =["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"])
    @test Pnamesref.dmunamesref == ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"]
    @test Pnamesref[11][1][1][2] == "K"

    # Test conversions
    @test typeof(convert(Matrix, P)) == Array{Float64,2}
    @test typeof(convert(SparseMatrixCSC, P)) == SparseMatrixCSC{Float64,Int64}

    # Print
    show(IOBuffer(), P)
    show(IOBuffer(), P2)

    # Test errors
    @test_throws DimensionMismatch peers(dea(X, Y, rts = :VRS), namesref = ["A"]) #  Length of references names different to number of references DMUs
    @test_throws ArgumentError ispeer(Pn, "W", "A") # First name does not exists
    @test_throws ArgumentError ispeer(Pn, "A", "W") # Second name does not exists

    Prepeated = peers(dea([1; 1; 1; 1; 1], [1 ; 2 ;3; 4; 5], orient = :Output, names = ["A", "B", "B", "C", "C"]))
    @test_throws ArgumentError ispeer(Prepeated, "C", "A") # First name is repeated
    @test_throws ArgumentError ispeer(Prepeated, "A", "C") # Second name does not exists

    @test_throws BoundsError ispeer(P2, 0) # BoundsErrror

    # Test struct defaults and errors
    @test names(wrongDEAmodel(3, [1; 2; 3])) == ["1"; "2"; "3"] # Default names if dmunames not in struct
    @test_throws ArgumentError peers(wrongDEAmodel(3, [1; 2; 3])) # Model does not have info on peers

end


text:
# Factorize two different matrices (one real, one complex) and solve for multiple rhs and free memory
using Distributed
using LinearAlgebra
using SparseArrays
using Test

include("getDivGrad.jl");

Ar = getDivGrad(30,31,33);
n  = size(Ar,1);
Ac  = Ar + im*spdiagm(0=>rand(n));

nrhs = 10;
rhsr = randn(n,nrhs) + im*randn(n,nrhs);
rhsc = randn(n,nrhs);

# println("Factorize real matrix on main instance and destroy")
# tic();
# fact = 0;
# for i=1:100;
# 	fact = factorMUMPS(Ar,1)
# 	destroyMUMPS(fact)
# end
# toc();

println("Factorize real matrix on remote worker and destroy")
@time begin
	fact = 0;
	for i=1:10;
		fact = remotecall_fetch(factorMUMPS,workers()[1], Ar,1)
		remotecall_fetch(destroyMUMPS,workers()[1],fact)
	end
end
println("DONE!")


text:
n = rand(1:10)
p = -2*Float64[1:n;] + (n + 1.5);
C = matrixdepot("cauchy", p)
@test_approx_eq matrixdepot("dingdong", n) C
println("'dingdong' passed test...")


text:
function solve()
    s = readline()
    t = readline()

    if s == t[1:end-1]
        "Yes"
    else
        "No"
    end
end

println(solve())

max_stars_repo_name: aishikawa/atcoder-julia

max_issues_repo_name: aishikawa/atcoder-julia

max_forks_repo_name: aishikawa/atcoder-julia


text:
remove_and_overwrite = true
validate_package_version = true

JULIA_ENV = joinpath(@__DIR__, "..", "..")
data_root = joinpath(@__DIR__, "..", "..", "data")
data_input_root = joinpath(data_root, "input", "processed")
data_output_root = joinpath(data_root, "output")

worker_list = [("localhost", 1)]
exeflags = `--project="$JULIA_ENV"`
sshflags= `-i path/to/ssh/key/file`

fmt_string = "[{name} | {date} | {level}]: {msg}"
loglevel = "debug"

### data directories ###
data_dirs = Dict("semantic" => ["Arrhythmia", "Annthyroid", "Cardiotocography", "HeartDisease", "Hepatitis", "PageBlocks", "Parkinson", "Pima", "SpamBase", "Stamps"],
                 "literature" => ["ALOI", "Glass", "Ionosphere", "KDDCup99", "Lymphography", "PenDigits", "Shuttle", "WBC", "WDBC", "WPBC", "Waveform"])

max_stars_repo_name: kit-dbis/ocal-evaluation

max_issues_repo_name: kit-dbis/ocal-evaluation

max_forks_repo_name: kit-dbis/ocal-evaluation


text:
function FiniteDifferences.to_vec(x::Composite{P, T}) where{P, T<:Tuple}
    x_tuple = convert(Tuple, x)
    x_vec, back_tuple = FiniteDifferences.to_vec(x_tuple)
    function CompositeTuple_from_vec(y_vec)
        y_tuple = back_tuple(y_vec)
        return Composite{P, typeof(y_tuple)}(y_tuple)
    end
    return x_vec, CompositeTuple_from_vec
end


text:
using FastaIO
using DataFrames
using Lumberjack
include(Pkg.dir("PseudoGenomes", "src","pseudogenome-deletions.jl"))

function parse_line_to_arrays!(seq_id, position, ref, variant, line, seq_id_format)
     line_array =  map(x->strip(x), split(line,'\t') )
      if seq_id_format == "ucsc"
          push!(seq_id, ens2ucsc( line_array[1]) )
      else
         push!(seq_id, line_array[1] )
      end
      push!(position, parseint( line_array[2]) )
      push!(ref, line_array[4][1] )
      push!(variant, line_array[5][1] )
end

# we only want the seq_id,pos,ref,variant from the vcf file.
# if dataframes readtable allowed for columns choosing that would prob
# do the job. This takes a file that has been queried by vcf-query for a
# particular strain or individual or whatever.
# e.g.  vcf-query  ftp://ftp-mouse.sanger.ac.uk/REL-1410-SNPs_Indels/mgp.v4.snps.dbSNP.vcf.gz -c CAST_EiJ -f '%CHROM\t%POS\t%ID\t%REF\t%ALT\t%QUAL\t%FILTER\t%INFO\n' > mgp.v4.snps.dbSNP.CAST.vcf
function read_snp_positions_from_columns(filename::String; seq_id_format="ucsc", gzip=false )
    #locate comments and header
    heading_rgx = Regex("^#CHROM")
    comment_rgx = Regex("^#")
    line_itr = nothing
    line_array=Any[]
    seq_ids = ASCIIString[]
    positions = Int64[]
    refs = Char[]
    variants = Char[]
    data_line_num = 0 # this only counts datalines
    df = DataFrame()
    header = false
    if gzip
       #line_itr = eachline(GZip.open(filename) ) - GZip is broken for some versions of gzip - gives an extraline - see bug report
       line_itr  = eachline(`zcat $filename`)
    else
       line_itr  = eachline(open(filename) )
    end

    # peak at the first line - check for comment lines and header

    first_line = first(line_itr)
    if ismatch(heading_rgx,first_line )
        header = false
        Lumberjack.info("Found Header in first line")
    elseif ismatch(comment_rgx,first_line)
         header = true
         Lumberjack.info("Found comment in first line")
    else
        Lumberjack.info("Found dataline in first line")
        parse_line_to_arrays!(seq_ids,positions,refs,variants, first_line, seq_id_format)
        data_line_num +=1
    end
    # ignore until header
    if header
        for line in line_itr
            line=chomp(line)
            if ismatch(heading_rgx, line)
                Lumberjack.info("DETECT HEADING: $line")
                break
            elseif ismatch(comment_rgx,line)
               # anything above the line heading is a comment
               Lumberjack.info("DETECT COMMENT: $line")
            else
               Lumberjack.info("ASSUME THIS IS FIRST DATAROW\n$line")
               parse_line_to_arrays!(seq_ids,positions,refs,variants, line, seq_id_format)
               data_line_num +=1
               break
            end
        end
    end

    # process datalines
    for line in line_itr
        data_line_num += 1
        parse_line_to_arrays!(seq_ids,positions,refs,variants, line, seq_id_format)
        if (data_line_num % 1000000) == 0
            Lumberjack.info("read $data_line_num lines")
        end
    end
    return DataFrame(seq_id = seq_ids, position = positions, ref=refs, variant=variants)
end

function read_vcf_columns(filename::String, cols, sequence_id_col=1; gzip="false" )
    sequence_dict = Dict()
    ignore_rgx = Regex("^#")
    for line in eachline(open(filename))
       if ismatch(ignore_rgx, line)
           continue
       end
       # split and choose columns
       line_array =  map(x->strip(x), split(line,'\t')[cols])
       seq_id = line_array[sequence_id_col]

       if ! haskey( sequence_dict, seq_id )
           println("Don't see-",seq_id)
           df = DataFrame()
           for i = 1:length(cols)
               df[i] = Any[]
           end
           sequence_dict[ seq_id ] =df
       end

       # push onto the dataframe
       push!(sequence_dict[   seq_id  ],line_array)
    end
    return sequence_dict
end

# See vcf-file1.vcf for the kind of file you would expect this function to take.
# Essentially a vcf file or one without a heading, but with the mandatory columns present.
# We don't have a vcf parser, and dataframes readtable doesnt allow to choose
# columns meaning we don't get its datatype inference, as that would do the job.

function read_snp_positions_from_columnsOld(filename::String,seq_id_format="ucsc" )
    sequence_dict = Dict()
    ignore_rgx = Regex("^#")
    cols = [1,2,4,5]
    line_array=Any[]
    seq_id = ASCIIString[]
    positions = Int64[]
    ref = Char[]
    variant = Char[]
    line_num = 0
    for line in eachline(open(filename))
        line_num += 1
        if ismatch(ignore_rgx, line)
            continue
        end
        # split and choose columns
        line_array =  map(x->strip(x), split(line,'\t') )

        if seq_id_format == "ucsc"
            push!(seq_id, ens2ucsc( line_array[1]) )
        else
           push!(seq_id, line_array[1] )
        end
        push!(positions, parseint( line_array[2]))
        push!(ref,line_array[4][1] )
        push!(variant,line_array[5][1] )
        if (line_num % 1000000) == 0
            Lumberjack.info("read $line_num lines")
        end
    end
    return DataFrame(seq_id = seq_id, position = positions, ref=ref, variant=variant)
end

# substitutes ref allele with variant allele
function substitute_snps!(seq,snps::DataFrame)
    num_substituted=0
    for row=1:nrow(snps)
        pos = snps[:position][row]
        snp_ref_nuc = snps[:ref][row]
        snp_variant_nuc = snps[:variant][row]
        seq_ref_nuc = seq[pos]
        if uppercase(seq_ref_nuc) == uppercase(snp_ref_nuc)
            seq[pos]=snp_variant_nuc
            num_substituted +=1
        else
            Lumberjack.warn("Got inconsistent reference nucleotide between vcf file and ref sequence at position $pos.\nsnp ref nuc:$snp_ref_nuc seq\nseq ref nuc:$seq_ref_nuc")
        end
    end
    return (num_substituted,nrow(snps))
end

function check_variant_alleles(seq,snps::DataFrame)
    num_substituted=0
    num_ref=0
    num_other=0
    nuc_other_type = Dict()
    for row=1:nrow(snps)
        pos = snps[:position][row]
        snp_ref_nuc = snps[:ref][row]
        snp_variant_nuc = snps[:variant][row]
        seq_ref_nuc = seq[pos]
        if uppercase(seq_ref_nuc) == uppercase(snp_ref_nuc)
            num_ref +=1
        elseif uppercase(seq_ref_nuc) == uppercase( snp_variant_nuc )
            num_substituted +=1
        else
            num_other +=1
            if haskey(nuc_other_type, seq_ref_nuc)
                nuc_other_type[seq_ref_nuc] +=1
            else
                nuc_other_type[seq_ref_nuc]=1
            end
       end
    end
    return (num_substituted,num_ref,num_other,nuc_other_type)
end

function check_variant_alleles_in_fasta_file(fastafile,vcf_filename; seq_id_format="ucsc" )
    Lumberjack.info("Start reading VCF file")
    snp_df = read_snp_positions_from_columns(vcf_filename,seq_id_format=seq_id_format)
    fr = FastaReader{Vector{Char}}(fastafile)
    Lumberjack.info("Start checking snp substitution of fasta file")
    for (desc, seq) in fr
         seqlength = length(seq)
         Lumberjack.info("processing $desc - length: $seqlength")
         seqid_df = snp_df[ snp_df[:seq_id] .== desc,:]
         if nrow(seqid_df) > 0
             (num_substituted,num_ref,num_other,nuc_other_type) = check_variant_alleles(seq, seqid_df )
              nuc_type_nums_str = join( map(x->join(x,":"),nuc_other_type), " ")
              Lumberjack.info("num substituted: $num_substituted , num_ref: $num_ref, num_other: $num_other $nuc_type_nums_str")
         end
    end
    Lumberjack.info("read $(fr.num_parsed) entries")
end

function substitute_snps_in_fasta_file(filename,vcf_filename , outfilename)
    Lumberjack.info("Start reading VCF file")
    snp_df = read_snp_positions_from_columns(vcf_filename)
    fr = FastaReader{Vector{Char}}(filename)
    fw = FastaWriter(outfilename)
    Lumberjack.info("Start snp substitution of fasta file")
    for (desc, seq) in fr
         seqlength = length(seq)
         Lumberjack.info("processing $desc - length: $seqlength")
         seqid_df = snp_df[ snp_df[:seq_id] .== desc,:]
         if nrow(seqid_df) > 0
             (num_substituted,total_snps) = substitute_snps!(seq, seqid_df )
              Lumberjack.info("num substituted: $num_substituted of $total_snps")
         end
         writeentry(fw, desc, seq)
    end
   Lumberjack.info("read $(fr.num_parsed) entries")
   close(fw)
end

function find_cpgs(seq)
    CGpos=Int64[] # make the array a particular max size if this should go faster
    cflag=false
    cpos =1
    for i in 1:length(seq)
        if seq[i] == 'C' || seq[i] == 'c'
            cflag=true
            cpos = i
        elseif seq[i] == 'G' || seq[i] == 'g'
            if cflag == true
               push!(CGpos,cpos)
            end
            cflag=false
        else
            cflag = false
        end
    end
    return CGpos
end

function add_cpg_sequence_features!(gtable::DataFrame,seq_id,starts;source="source",coord_type="zero-based")
    for i=1:length(starts)
       # if length(stops) == 0
       if coord_type == "zero-based"
           push!(gtable, [seq_id, starts[i]-1,starts[i]-1,"+",source])
           push!(gtable, [seq_id, starts[i],starts[i],"-",source])
       else
           push!(gtable, [seq_id, starts[i],starts[i],"+",source])
           push!(gtable, [seq_id, starts[i]+1,starts[i]+1,"-",source])
       end
    end
end

# make_cpg_bedfile - which looks like this:
# chr1 1 1 + source
# chr2 2 2 - source
function make_cg_bedfile(fastafile,bedfile;coord_type="zero-based")
    fr = FastaReader{Vector{Char}}(fastafile)
    CG_table = DataFrame(seq_id=String[],start=Int64[],stop=Int64[],strand=ASCIIString[],source=ASCIIString[])
    Lumberjack.info("Start CG detection in fasta file")
    for (desc, seq) in fr
         seqlength = length(seq)
         Lumberjack.info("processing $desc - length: $seqlength")
         CG_positions = find_cpgs(seq)
         add_cpg_sequence_features!(CG_table,desc,CG_positions,coord_type=coord_type)
         num_cgs = length(CG_positions)
         Lumberjack.info("num CGs found : $num_cgs")
    end
    close(fr)
    Lumberjack.info("read $(fr.num_parsed) sequences")
    sort!(CG_table)
    Lumberjack.info("sorted $(fr.num_parsed) sequences")
    writetable(bedfile, CG_table, header = false, separator='\t')
    Lumberjack.info("written cg bed file to $bedfile")
end

#function write_cpg_bedfile(fastafile, bedfile)
#     make_cpg_bedfile(fastfile)
#end

## Remove
#function add_sequence_features!(gtable::DataFrame,seq_id,starts;stops=[])
#    for i=1:length(starts)
#        if length(stops) == 0
#            push!(gtable, [seq_id, starts[i],starts[i]+1] )
#        else
#            push!(gtable, [seq_id, starts[i],stops[i]])
#        end
#    end
#end

#
#function make_cpg_bedfile_type1(fastafile,bedfile)
#    fr = FastaReader{Vector{Char}}(fastafile)
#    CG_table = DataFrame(seq_id=String[],start=Int64[],stop=Int64[])
#    Lumberjack.info("Start CG detection in fasta file")
#    for (desc, seq) in fr
#         seqlength = length(seq)
#         Lumberjack.info("processing $desc - length: $seqlength")
#         CG_positions = find_cpgs(seq)
#         add_sequence_features!(CG_table,desc,CG_positions)
#         num_cgs = length(CG_positions)
#         Lumberjack.info("num CGs found : $num_cgs")
#    end
#    close(fr)
#    Lumberjack.info("read $(fr.num_parsed) sequences")
#    writetable(bedfile, CG_table, separator='\t')
#end


text:
builtin_joltage( adapters ) = maximum(adapters) + 3

function load( filename )
    open(filename) do f
        return parse.(Int, readlines(f))
    end
end

function part1( puzzle )
    adapt = vcat(0, puzzle..., builtin_joltage( puzzle ) )
    sort!(adapt)
    differences = []
    adapter = 0
    for x in adapt
         possibles = 0 .< adapt .- adapter .<= 3
         if any(possibles)
             new_adapter = adapt[findfirst(possibles)]
             push!(differences, new_adapter - adapter )
             adapter = new_adapter
         else
             return count(==(1), differences), count(==(3), differences)
         end
    end
end

function part2( puzzle ) # brute force is too exepensive

    function count_path( current_adapter, adapters)

        possibles = 0 .< adapters .- current_adapter .<= 3

        if any(possibles)
            for adapter in adapters[possibles]
                count_path( adapter, adapters)
            end
        else
            count += 1
        end
    end

    adapters = vcat(0, puzzle...)
    sort!(adapters)
    count = 0
    count_path( 0, adapters)

    return count

end

# I found the solution with the help of this reference
# https://www.geeksforgeeks.org/count-possible-paths-source-destination-exactly-k-edges/

function count_arrangements(puzzle)

    adapt = vcat(0, puzzle..., builtin_joltage(puzzle))

    n = length(adapt)

    graph = zeros(Bool,n, n)
    for i in 1:n, j in 1:n
        graph[i,j] = (0 < adapt[j] - adapt[i] <= 3)
    end

    count =  zeros(Int,n,n,n)

    e = 1
    while e <= n && adapt[e] < builtin_joltage(adapt)

        for i in 1:n, j in 1:n

            if (e == 1 && i == j)
                count[i,j,e] = 1
            end
            if (e == 2 && graph[i,j])
                count[i,j,e] = 1
            end

            if e > 2
                for k in 1:n
                    if graph[i,k]
                        count[i,j,e] += count[k,j,e-1]
                    end
                end
            end
        end
        e = e+1

    end

    return sum(count[1,n,:])

end

puzzle = load("input10.txt")
println(prod(part1(puzzle)))
puzzle = load("input10.txt")
println(count_arrangements(puzzle))

max_stars_repo_name: pnavaro/AdventOfCode

max_issues_repo_name: pnavaro/AdventOfCode

max_forks_repo_name: pnavaro/AdventOfCode


text:
"""
    couple_models(aero::Wagner, stru::TypicalSection)

Create an aerostructural model using an unsteady aerodynamic model based on
Wagner's function and a two-degree of freedom typical section model.  This model
introduces the freestream velocity ``U_\\infty`` and air density ``\\rho_\\infty``
as additional parameters.
"""
couple_models(aero::Wagner, stru::TypicalSection) = (aero, stru)

# --- Traits --- #

number_of_additional_parameters(::Type{<:Wagner}, ::Type{TypicalSection}) = 2

coupling_inplaceness(::Type{<:Wagner}, ::Type{TypicalSection}) = OutOfPlace()

coupling_rate_jacobian_type(::Type{<:Wagner}, ::Type{TypicalSection}) = Constant()
coupling_state_jacobian_type(::Type{<:Wagner}, ::Type{TypicalSection}) = Linear()
coupling_parameter_jacobian_type(::Type{<:Wagner}, ::Type{TypicalSection}) = Nonlinear()
coupling_time_gradient_type(::Type{<:Wagner}, ::Type{TypicalSection}) = Zeros()

# --- Methods --- #

function get_coupling_inputs(aero::Wagner, stru::TypicalSection, dx, x, p, t)
    # extract rate variables
    dλ1, dλ2, dh, dθ, dhdot, dθdot = dx
    # extract state variables
    λ1, λ2, h, θ, hdot, θdot = x
    # extract parameters
    a, b, a0, α0, cd0, cm0, kh, kθ, m, Sθ, Iθ, U, ρ = p
    # extract model constants
    C1 = aero.C1
    C2 = aero.C2
    # local freestream velocity components
    u, v, ω = section_velocities(U, θ, hdot, θdot)
    udot, vdot, ωdot = section_accelerations(dhdot, dθdot)
    # calculate loads
    N, A, M = wagner_loads(a, b, ρ, a0, α0, cd0, cm0, C1, C2, u, v, ω, vdot, ωdot, λ1, λ2)
    # lift is approximately normal force
    L = N
    # return inputs
    return SVector(u, v, ω, L, M)
end

# --- Performance Overloads --- #

function get_coupling_rate_jacobian(aero::Wagner, stru::TypicalSection, p)
    # extract parameters
    a, b, a0, α0, cd0, cm0, kh, kθ, m, Sθ, Iθ, U, ρ = p
    # calculate loads
    N_hddot, A_hddot, M_hddot = wagner_loads_vdot(a, b, ρ)
    N_θddot, A_θddot, M_θddot = wagner_loads_ωdot(a, b, ρ)
    # construct submatrices
    Jyaa = @SMatrix [0 0; 0 0; 0 0]
    Jyas = @SMatrix [0 0 0 0; 0 0 0 0; 0 0 0 0]
    Jysa = @SMatrix [0 0; 0 0]
    Jyss = @SMatrix [0 0 N_hddot N_θddot; 0 0 M_hddot M_θddot]
    # assemble rate jacobian
    return [Jyaa Jyas; Jysa Jyss]
end

function get_coupling_state_jacobian(aero::Wagner, stru::TypicalSection, dx, x, p, t)
    # extract state variables
    λ1, λ2, h, θ, hdot, θdot = x
    # extract parameters
    a, b, a0, α0, cd0, cm0, kh, kθ, m, Sθ, Iθ, U, ρ = p
    # extract model constants
    C1 = aero.C1
    C2 = aero.C2
    # local freestream velocity components
    u, v, ω = section_velocities(U, θ, hdot, θdot)
    u_θ, v_θ, ω_θ = section_velocities_θ(U)
    u_hdot, v_hdot, ω_hdot = section_velocities_hdot()
    u_θdot, v_θdot, ω_θdot = section_velocities_θdot()
    # calculate loads
    r_λ = wagner_loads_λ(a, b, ρ, a0, α0, C1, C2, u, v, ω, λ1, λ2)
    N_λ, A_λ, M_λ = r_λ[1,:], r_λ[2,:], r_λ[3,:]
    N_θ, M_θ = wagner_loads_θ(a, b, ρ, a0, C1, C2, U)
    N_hdot, A_hdot, M_hdot = wagner_loads_v(a, b, ρ, a0, α0, C1, C2, u, v, ω, λ1, λ2)
    N_θdot, A_θdot, M_θdot = wagner_loads_ω(a, b, ρ, a0, α0, C1, C2, u, v, ω, λ1, λ2)
    # compute jacobian sub-matrices
    Jyaa = @SMatrix [0 0; 0 0; 0 0]
    Jyas = @SMatrix [0 u_θ u_hdot u_θdot; 0 v_θ v_hdot v_θdot; 0 ω_θ ω_hdot ω_θdot]
    Jysa = vcat(N_λ', M_λ')
    Jyss = @SMatrix [0 N_θ N_hdot N_θdot; 0 M_θ M_hdot M_θdot]
    # return jacobian
    return [Jyaa Jyas; Jysa Jyss]
end

function get_coupling_parameter_jacobian(aero::Wagner, stru::TypicalSection, dx, x, p, t)
    # extract rate variables
    dλ1, dλ2, dh, dθ, dhdot, dθdot = dx
    # extract state variables
    λ1, λ2, h, θ, hdot, θdot = x
    # extract parameters
    a, b, a0, α0, cd0, cm0, kh, kθ, m, Sθ, Iθ, U, ρ = p
    # extract model constants
    C1 = aero.C1
    C2 = aero.C2
    # local freestream velocity components
    u, v, ω = section_velocities(U, θ, hdot, θdot)
    udot, vdot, ωdot = section_accelerations(dhdot, dθdot)
    u_U, v_U, ω_U = section_velocities_U(θ)
    # calculate loads
    N_a, A_a, M_a = wagner_loads_a(a, b, ρ, a0, α0, C1, C2, u, v, ω, vdot, ωdot, λ1, λ2)
    N_b, A_b, M_b = wagner_loads_b(a, b, ρ, a0, α0, cd0, cm0, C1, C2, u, v, ω, vdot, ωdot, λ1, λ2)
    N_a0, A_a0, M_a0 = wagner_loads_a0(a, b, ρ, α0, C1, C2, u, v, ω, λ1, λ2)
    N_α0, A_α0, M_α0 = wagner_loads_α0(a, b, ρ, a0, α0, C1, C2, u, v, ω, λ1, λ2)
    N_cd0, A_cd0, M_cd0 = wagner_loads_cd0(b, ρ, u)
    N_cm0, A_cm0, M_cm0 = wagner_loads_cm0(b, ρ, u)

    N_u, A_u, M_u = wagner_loads_u(a, b, ρ, a0, α0, cd0, cm0, C1, C2, u, v, ω, λ1, λ2)
    N_v, A_v, M_v = wagner_loads_v(a, b, ρ, a0, α0, C1, C2, u, v, ω, λ1, λ2)
    N_U = N_u * u_U + N_v * v_U
    M_U = M_u * u_U + M_v * v_U

    N_ρ, A_ρ, M_ρ = wagner_loads_ρ(a, b, a0, α0, cd0, cm0, C1, C2, u, v, ω, vdot, ωdot, λ1, λ2)
        # compute jacobian sub-matrices
    Jyaa = @SMatrix [0 0 0 0 0 0; 0 0 0 0 0 0; 0 0 0 0 0 0]
    Jyas = @SMatrix [0 0 0 0 0; 0 0 0 0 0; 0 0 0 0 0]
    Jyap = @SMatrix [u_U 0; v_U 0; ω_U 0]
    Jysa = @SMatrix [N_a N_b N_a0 N_α0 N_cd0 N_cm0; M_a M_b M_a0 M_α0 M_cd0 M_cm0]
    Jyss = @SMatrix [0 0 0 0 0; 0 0 0 0 0]
    Jysp = @SMatrix [N_U N_ρ; M_U M_ρ]
    # return jacobian
    return [Jyaa Jyas Jyap; Jysa Jyss Jysp]
end

# --- Convenience Methods --- #

function set_additional_parameters!(padd, aero::Wagner, stru::TypicalSection; U, rho)

    padd[1] = U
    padd[2] = rho

    return padd
end

function separate_additional_parameters(aero::Wagner, stru::TypicalSection, padd)

    return (U = padd[1], rho = padd[2])
end

# --- Plotting --- #

@recipe function f(aero::Wagner, stru::TypicalSection, dx, x, y, p, t)

    framestyle --> :origin
    grid --> false
    xlims --> (-1.0, 1.0)
    ylims --> (-1.5, 1.5)
    label --> @sprintf("t = %6.3f", t)

    # extract state variables
    λ1, λ2, h, θ, hdot, θdot = x

    # extract parameters
    a, b, a0, α0, cd0, cm0, kh, kθ, m, Sθ, Iθ, U, ρ = p

    xplot = [-(0.5 + a*b)*cos(θ),    (0.5 - a*b)*cos(θ)]
    yplot = [ (0.5 + a*b)*sin(θ)-h, -(0.5 - a*b)*sin(θ)-h]

    return xplot, yplot
end

# --- Internal Methods --- #

function wagner_loads_θ(a, b, ρ, a0, C1, C2, U)
    ϕ0 = 1 - C1 - C2
    N_θ = a0*ρ*b*U^2*ϕ0
    M_θ = (b/2 + a*b)*N_θ
    return SVector(N_θ, M_θ)
end