├── .gitignore ├── EXTERNALTESTS ├── EXTERNALTESTSreadme.md ├── LICENSE ├── README.md ├── TESTCONFIG ├── TESTS ├── gpkitmodels ├── GP │ ├── __init__.py │ ├── aircraft │ │ ├── __init__.py │ │ ├── engine │ │ │ ├── DF70 │ │ │ │ ├── DF35_maxPvh.csv │ │ │ │ ├── Dataset_BSFC_kgKwh.csv │ │ │ │ ├── Dataset_Power_Kw.csv │ │ │ │ ├── Dataset_Torque_Nm.csv │ │ │ │ ├── fitDF70.py │ │ │ │ └── genericbsfcpower.csv │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── df70.py │ │ │ ├── gas_engine.py │ │ │ ├── powerBSFCfit.csv │ │ │ ├── power_lawfit.csv │ │ │ ├── powertobsfcfit.pdf │ │ │ ├── powertobsfcfit.png │ │ │ ├── powervsweightfit.pdf │ │ │ └── powervsweightfit.png │ │ ├── fuselage │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── cyl_fuse_drawing.ipe │ │ │ ├── cyl_fuse_drawing.pdf │ │ │ ├── cylindrical_fuselage.py │ │ │ ├── ellipsoid_fuselage.pdf │ │ │ ├── elliptical_fuselage.py │ │ │ ├── fuel_tank.py │ │ │ ├── fuselage.pdf │ │ │ ├── fuselage_profile_drag │ │ │ │ ├── fusedrag.csv │ │ │ │ └── fusedragfit.py │ │ │ ├── fuselage_skin.py │ │ │ └── test_fuselage.py │ │ ├── mission │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── breguet_endurance.py │ │ │ └── mission.pdf │ │ ├── motor │ │ │ ├── __init__.py │ │ │ ├── motor.py │ │ │ └── motor_test.py │ │ ├── prop │ │ │ ├── __init__.py │ │ │ ├── arccos_fit.py │ │ │ ├── prop_test.py │ │ │ └── propeller.py │ │ ├── tail │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── empennage.py │ │ │ ├── horizontal_tail.py │ │ │ ├── tail_aero.py │ │ │ ├── tail_boom.py │ │ │ ├── tail_dragfit.csv │ │ │ ├── tail_tests.py │ │ │ ├── taildragpolar.pdf │ │ │ ├── tailpolars │ │ │ │ ├── genpolar.sh │ │ │ │ ├── naca_cl0fits.py │ │ │ │ └── nacasweep.sh │ │ │ ├── tube_spar.py │ │ │ └── vertical_tail.py │ │ └── wing │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── arctan_fit.csv │ │ │ ├── arctan_fit.py │ │ │ ├── boxspar.py │ │ │ ├── capspar.py │ │ │ ├── constant_taper_chord.py │ │ │ ├── gustloaddiagram.pdf │ │ │ ├── gustloading.py │ │ │ ├── jho1polarfit1.pdf │ │ │ ├── jho1polars │ │ │ ├── genpolar.sh │ │ │ ├── jho1.dat │ │ │ ├── jho1_polarfits.py │ │ │ └── jho1polarsweep.sh │ │ │ ├── jho_fitdata.csv │ │ │ ├── sparloading.py │ │ │ ├── tube_spar.py │ │ │ ├── wing.py │ │ │ ├── wing_core.py │ │ │ ├── wing_skin.py │ │ │ └── wing_test.py │ ├── beam │ │ ├── README.md │ │ ├── __init__.py │ │ ├── beam.pdf │ │ └── beam.py │ └── materials │ │ ├── __init__.py │ │ ├── composite.py │ │ └── foam.py ├── SP │ ├── SimPleAC │ │ ├── README.md │ │ ├── SimPleAC.py │ │ ├── SimPleAC_mission.py │ │ ├── SimPleAC_multimission.py │ │ ├── __init__.py │ │ └── simpleac.pdf │ ├── __init__.py │ ├── aircraft │ │ ├── __init__.py │ │ ├── prop │ │ │ ├── __init__.py │ │ │ ├── dae51_fitdata.csv │ │ │ ├── dae51polars │ │ │ │ ├── dae51.dat │ │ │ │ ├── dae51.txt │ │ │ │ ├── dae51_polarfits.py │ │ │ │ ├── dae51polarfit1.eps │ │ │ │ └── polarstest.pdf │ │ │ └── propeller.py │ │ ├── tail │ │ │ ├── __init__.py │ │ │ └── tail_boom_flex.py │ │ └── wing │ │ │ ├── __init__.py │ │ │ ├── boxspar.py │ │ │ └── wing.py │ └── atmosphere │ │ ├── __init__.py │ │ └── atmosphere.py ├── __init__.py ├── misc │ ├── Economic Order Quantity │ │ ├── README.md │ │ ├── __init__.py │ │ ├── eoq.pdf │ │ └── qsweep.pdf │ ├── Moment of Inertia (cylindrical beam) │ │ ├── MoICylinder.PNG │ │ ├── README.md │ │ ├── moi.pdf │ │ ├── moi.py │ │ └── tightness.png │ ├── Net Present Value │ │ ├── README.md │ │ ├── __init__.py │ │ ├── npv.pdf │ │ └── npv.py │ ├── README.md │ ├── Raymer Weights │ │ ├── Blended Wing-Body │ │ │ ├── MTOW_breakdown.eps │ │ │ ├── OEW_breakdown.eps │ │ │ ├── Raymer_Weights_Model.py │ │ │ └── XFOIL_BWB.ipynb │ │ └── Raymer_Exact.py │ ├── default.latex │ └── make_gpmd └── tools │ ├── __init__.py │ ├── fit_constraintset.py │ ├── ipynb2module.py │ ├── summing_constraintset.py │ └── xfoilWrapper.py ├── researchmodeltests.sh ├── runtests.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # iPython checkpoint folders 2 | .ipynb_checkpoints/ 3 | 4 | # MOSEK CLI folder 5 | gpkit_tmp/ 6 | 7 | # gpkit markdown generated files 8 | *.generated.tex 9 | *.md.py 10 | *.md.tex.md 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | env/ 22 | bin/ 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | 48 | # Translations 49 | *.mo 50 | 51 | # Mr Developer 52 | .mr.developer.cfg 53 | .project 54 | .pydevproject 55 | 56 | # Rope 57 | .ropeproject 58 | 59 | # Django stuff: 60 | *.log 61 | *.pot 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # MATLAB autosave 67 | *.asv 68 | 69 | # CI test xmloutput 70 | test_reports/ 71 | test_reports_nounits/ 72 | 73 | # MacOSX 74 | *.DS_Store 75 | 76 | # vim 77 | *.swp 78 | 79 | # OSX 80 | .DS_Store 81 | 82 | # latex 83 | *.aux 84 | *.log 85 | *.synctex.gz 86 | 87 | # md files support files 88 | *.generated.tex 89 | *.tex.md 90 | *.md.py 91 | 92 | # xfoil polars 93 | *.pol 94 | -------------------------------------------------------------------------------- /EXTERNALTESTS: -------------------------------------------------------------------------------- 1 | SPaircraft 2 | robust 3 | shopping 4 | gassolar 5 | jho 6 | turbofan 7 | solar 8 | gplibrary 9 | eVTOL 10 | -------------------------------------------------------------------------------- /EXTERNALTESTSreadme.md: -------------------------------------------------------------------------------- 1 | Testing of external repositories 2 | ================================ 3 | 4 | Directions 5 | ---------- 6 | 7 | 2. In your repository's top level, create the file `TESTS`. 8 | Each line should be the local path to a `.py` file containing a `test()` function, like this: 9 | 10 | mission.py 11 | aircraft/wing/wing_spar.py 12 | 13 | 3. (optional) create a `TESTCONFIG` file in the same folder. The following options are supported: 14 | `pip install` specifies packages your repository requires, 15 | `gpkit-models` the branch of gpkit-models you're working on, 16 | and `skipsolvers` the solvers that cannot solve your code. 17 | Format the file like this: 18 | 19 | pip install : pandas 20 | gpkit-models branch : 1682 21 | skipsolvers : cvxopt 22 | 23 | 4. Test your repository locally by running `python -c "from gpkit.tests.from_paths import run; run()"` 24 | (consider saving this line as `runtests.sh` for easy access) 25 | 26 | 1. Submit a PR to add the name of your repository to the `EXTERNALTESTS` file in `gpkit-models` 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Convex Engineering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gplibrary 2 | 3 | [![Build Status](https://acdl.mit.edu/csi/buildStatus/icon?job=CE_gplibrary_Push_research_models)](https://acdl.mit.edu/csi/job/CE_gplibrary_Push_research_models/) 4 | 5 | This repository contains those GP-/SP-compatible models that we consider well documented and general enough to be useful to multiple projects. 6 | 7 | * **Simple models with in-depth explanations** (good for learning GPkit) 8 | * [SimPleAC](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/SP/SimPleAC/simpleac.pdf): a basic aircraft model that captures the fundamental design tradeoffs 9 | * [Economic Order Quantity](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/misc/Economic%20Order%20Quantity/eoq.pdf): tradeoff between setup and holding costs 10 | * [Cylindrical Beam Moment of Inertia](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/misc/Moment%20of%20Inertia%20(cylindrical%20beam)/moi.pdf): GP approximation of cylindrical beam MOI 11 | * [Net Present Value](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/misc/Net%20Present%20Value/npv.pdf): financial tradeoff between cash and equipment 12 | * [Raymer Weights](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/misc/Raymer%20Weights): rule-of-thumb weight relations for aircraft design 13 | * **GP models** 14 | * Aircraft 15 | * [Wing Structural and Aero Models](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/aircraft/wing) 16 | * [Empennage](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/aircraft/tail): TailBoom, HorizontalTail, and VerticalTail inherit from the Wing model 17 | * [Mission](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/aircraft/mission): models that unify subsystems and flight profiles 18 | * [Fuselage](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/aircraft/fuselage): elliptical and cylindrical fuselage models 19 | * [IC Gas Engine Model](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/aircraft/engine) 20 | * [Bending Beam](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/GP/beam): discretized beam for distributed loads 21 | * **SP models** 22 | * Aircraft 23 | * [Tail Boom Flexibility](https://github.com/convexengineering/gplibrary/tree/master/gpkitmodels/SP/aircraft/tail/tail_boom_flex.py) 24 | * [Wing Spanwise Effectiveness](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/SP/aircraft/wing/wing.py) 25 | * Atmosphere 26 | * [Tony Tao's fits as (efficient) signomial equalities](https://github.com/convexengineering/gplibrary/blob/master/gpkitmodels/SP/atmosphere/atmosphere.py). Valid until 10,000m of altitude. 27 | 28 | -------------------------------------------------------------------------------- /TESTCONFIG: -------------------------------------------------------------------------------- 1 | pip install : pandas, git+https://github.com/hoburg/gpfit.git 2 | -------------------------------------------------------------------------------- /TESTS: -------------------------------------------------------------------------------- 1 | gpkitmodels/GP/aircraft/wing/wing_test.py 2 | gpkitmodels/GP/aircraft/tail/tail_tests.py 3 | gpkitmodels/GP/aircraft/fuselage/test_fuselage.py 4 | gpkitmodels/GP/aircraft/prop/prop_test.py 5 | gpkitmodels/GP/aircraft/motor/motor_test.py 6 | gpkitmodels/SP/SimPleAC/SimPleAC.py 7 | gpkitmodels/SP/SimPleAC/SimPleAC_mission.py 8 | gpkitmodels/SP/SimPleAC/SimPleAC_multimission.py -------------------------------------------------------------------------------- /gpkitmodels/GP/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/DF35_maxPvh.csv: -------------------------------------------------------------------------------- 1 | ft,kW,,RPM,P,BSFC,Q(nm) 2 | 0,2.25,,4000,0.2,0.62,0.477707006 3 | 3280.839895,1.97,,5000,0.38,0.51,0.72611465 4 | 6561.67979,1.69,,6000,0.64,0.405,1.01910828 5 | 9842.519685,1.44,,7000,1.02,0.34,1.392174704 6 | 13123.35958,1.2,,8000,1.5,0.32,1.791401274 7 | 16404.19948,0.99,,9000,2.13,0.32,2.261146497 8 | 19685.03937,0.79,,,,, 9 | 22965.87927,0.61,,,,, 10 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/Dataset_BSFC_kgKwh.csv: -------------------------------------------------------------------------------- 1 | RPM,BSFC,Power 3000.669344,0.680239521,0.062323205 3081.659973,0.668263473,0.067406877 3192.10174,0.653892216,0.07476995 3302.543507,0.637125749,0.082645353 3405.62249,0.622754491,0.090472716 3493.975904,0.610778443,0.097558981 3619.14324,0.594011976,0.108211161 3714.859438,0.582035928,0.116855572 3810.575636,0.57005988,0.125944049 3898.92905,0.558083832,0.134736888 3994.645248,0.546107784,0.144709665 4060.910308,0.536526946,0.151891842 4171.352075,0.522155689,0.164378363 4267.068273,0.510179641,0.17573295 4399.598394,0.493413174,0.192292545 4495.314592,0.481437126,0.20487178 4591.03079,0.469461078,0.217982687 4664.658635,0.45988024,0.2284365 4775.100402,0.445508982,0.244729568 4841.365462,0.438323353,0.254864062 4937.08166,0.426347305,0.269986208 5040.160643,0.414371257,0.286921518 5157.965194,0.40239521,0.307118096 5275.769746,0.390419162,0.328231705 5364.123159,0.380838323,0.344680602 5452.476573,0.371257485,0.361664775 5570.281124,0.361676647,0.385157371 5688.085676,0.354491018,0.409636063 5769.076305,0.349700599,0.427047404 5879.518072,0.34251497,0.451567804 5989.959839,0.335329341,0.477000251 6070.950469,0.332934132,0.496239732 6210.843373,0.328143713,0.530665943 6306.559572,0.325748503,0.555106662 6387.550201,0.323353293,0.57635854 6468.54083,0.320958084,0.598140821 6593.708166,0.318562874,0.63286237 6696.787149,0.318562874,0.662435732 6851.405622,0.316167665,0.708484326 6954.484605,0.316167665,0.740328118 7109.103079,0.316167665,0.789843487 7248.995984,0.318562874,0.836484757 7396.251673,0.318562874,0.887507917 7506.69344,0.320958084,0.927094736 7617.135207,0.323353293,0.967830251 7720.21419,0.325748503,1.006900515 -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/Dataset_Power_Kw.csv: -------------------------------------------------------------------------------- 1 | RPM,P, 2 | 3641.231593,0.431137725,0.421407002 3 | 3678.045515,0.443113772,0.434056247 4 | 3714.859438,0.45508982,0.446953511 5 | 3751.67336,0.467065868,0.460101126 6 | 3788.487282,0.479041916,0.473501425 7 | 3840.026774,0.502994012,0.492690787 8 | 3876.840696,0.51497006,0.506707029 9 | 3913.654618,0.526946108,0.520983871 10 | 3943.105756,0.538922156,0.532594539 11 | 3979.919679,0.550898204,0.54734632 12 | 4009.370817,0.562874251,0.559339951 13 | 4068.273092,0.586826347,0.583844815 14 | 4119.812584,0.610778443,0.605858871 15 | 4178.714859,0.634730539,0.631680379 16 | 4237.617135,0.658682635,0.658217742 17 | 4296.519411,0.682634731,0.68548044 18 | 4348.058902,0.706586826,0.709937751 19 | 4399.598394,0.730538922,0.734963991 20 | 4465.863454,0.754491018,0.76798676 21 | 4524.76573,0.778443114,0.798149542 22 | 4561.579652,0.80239521,0.81739243 23 | 4598.393574,0.826347305,0.836938969 24 | 4642.570281,0.850299401,0.860798875 25 | 4686.746988,0.874251497,0.885103332 26 | 4760.374833,0.922155689,0.926609443 27 | 4826.639893,0.958083832,0.965045388 28 | 4929.718876,1.017964072,1.026901696 29 | 5003.34672,1.065868263,1.072649666 30 | 5091.700134,1.125748503,1.129297655 31 | 5157.965194,1.173652695,1.173054679 32 | 5216.86747,1.209580838,1.212876448 33 | 5275.769746,1.25748503,1.253580097 34 | 5327.309237,1.293413174,1.289926564 35 | 5364.123159,1.317365269,1.316309563 36 | 5437.751004,1.365269461,1.370138695 37 | 5533.467202,1.437125749,1.442261197 38 | 5621.820616,1.508982036,1.511019508 39 | 5688.085676,1.568862275,1.563983472 40 | 5791.164659,1.652694611,1.648783088 41 | 5864.792503,1.71257485,1.711175424 42 | 5953.145917,1.784431138,1.788078291 43 | 6019.410977,1.844311377,1.847227933 44 | 6100.401606,1.928143713,1.921257226 45 | 6188.75502,2.011976048,2.004220115 46 | 6262.382865,2.083832335,2.075134301 47 | 6350.736278,2.167664671,2.162392518 48 | 6431.726908,2.251497006,2.24447551 49 | 6505.354752,2.323353293,2.320857277 50 | 6556.894244,2.383233533,2.37533236 51 | 6608.433735,2.443113772,2.430644504 52 | 6659.973226,2.502994012,2.486799893 53 | 6704.149933,2.550898204,2.535608922 54 | 6748.32664,2.610778443,2.585045909 55 | 6799.866131,2.670658683,2.64352125 56 | 6866.131191,2.74251497,2.71997746 57 | 6925.033467,2.814371257,2.789151189 58 | 7006.024096,2.910179641,2.886146165 59 | 7064.926372,2.982035928,2.958068326 60 | 7138.554217,3.077844311,3.049621286 61 | 7219.544846,3.185628743,3.152467409 62 | 7285.809906,3.281437126,3.238296248 63 | 7366.800535,3.389221557,3.345274326 64 | 7455.153949,3.497005988,3.46460869 65 | 7499.330656,3.556886228,3.525314741 66 | 7565.595716,3.652694611,3.617683272 67 | 7609.772423,3.724550898,3.680140895 68 | 7661.311914,3.796407186,3.75390245 69 | 7705.488621,3.856287425,3.817897622 70 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/Dataset_Torque_Nm.csv: -------------------------------------------------------------------------------- 1 | RPM,Q, 2 | 3000.669344,0.754491018,0.753469916 3 | 3125.83668,0.826347305,0.815971129 4 | 3265.729585,0.898203593,0.888693433 5 | 3412.985274,0.982035928,0.968506744 6 | 3545.515395,1.053892216,1.043194582 7 | 3663.319946,1.125748503,1.111850766 8 | 3759.036145,1.185628743,1.169202456 9 | 3847.389558,1.245508982,1.223388959 10 | 3906.291834,1.281437126,1.260177375 11 | 4024.096386,1.353293413,1.335346188 12 | 4127.175368,1.413173653,1.402858088 13 | 4208.165997,1.461077844,1.457040499 14 | 4289.156627,1.508982036,1.512222713 15 | 4362.784471,1.556886228,1.563255158 16 | 4458.500669,1.604790419,1.630830298 17 | 4546.854083,1.664670659,1.694443235 18 | 4605.756359,1.71257485,1.737510403 19 | 4664.658635,1.760479042,1.781104011 20 | 4708.835341,1.796407186,1.81414449 21 | 4797.188755,1.868263473,1.881112695 22 | 4870.8166,1.928143713,1.937822556 23 | 4929.718876,1.976047904,1.983781064 24 | 5010.709505,2.035928144,2.047830609 25 | 5091.700134,2.107784431,2.112871268 26 | 5216.86747,2.215568862,2.215336273 27 | 5319.946452,2.299401198,2.301493219 28 | 5408.299866,2.371257485,2.376616069 29 | 5518.741633,2.467065868,2.472171883 30 | 5607.095047,2.550898204,2.549937177 31 | 5688.085676,2.622754491,2.622252408 32 | 5754.350736,2.682634731,2.68215198 33 | 5842.70415,2.766467066,2.763042964 34 | 5908.96921,2.826347305,2.82447938 35 | 5975.23427,2.886227545,2.886573821 36 | 6048.862115,2.958083832,2.956338956 37 | 6122.48996,3.02994012,3.026915519 38 | 6232.931727,3.137724551,3.134300717 39 | 6343.373494,3.245508982,3.243508893 40 | 6439.089692,3.341317365,3.339629327 41 | 6549.531459,3.461077844,3.452236136 42 | 6652.610442,3.568862275,3.558976517 43 | 6748.32664,3.676646707,3.659509908 44 | 6836.680054,3.77245509,3.753520482 45 | 6932.396252,3.880239521,3.856675771 46 | 7020.749665,3.976047904,3.95310493 47 | 7101.740295,4.071856287,4.042517181 48 | 7182.730924,4.167664671,4.132903412 49 | 7278.447122,4.275449102,4.24097846 50 | 7381.526104,4.395209581,4.35888642 51 | 7469.879518,4.502994012,4.461203715 52 | 7572.958501,4.622754491,4.582035143 53 | 7639.223561,4.706586826,4.660543001 54 | 7698.125837,4.778443114,4.73087314 55 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/fitDF70.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import pandas as pd 3 | import numpy as np 4 | from numpy import logspace, log, log10 5 | import matplotlib.pyplot as plt 6 | from gpfit.fit import fit 7 | from scipy import interpolate 8 | plt.rcParams.update({'font.size':19}) 9 | 10 | np.random.seed(0) 11 | 12 | # Fitting BSFC vs. Power 13 | df = pd.read_csv('Dataset_Power_Kw.csv') 14 | p = df['P'] 15 | rpm = df["RPM"] 16 | f = interpolate.interp1d(rpm, p) 17 | df = pd.read_csv('Dataset_BSFC_kgKwh.csv') 18 | df = df[(df["RPM"] > min(rpm)) & (df["RPM"] < max(rpm))] 19 | rpmnew = df["RPM"] 20 | u = f(rpmnew)/max(p) 21 | w = df['BSFC']/min(df["BSFC"]) 22 | x = np.array(log(u)) 23 | y = np.array(log(w)) 24 | Type = 'SMA' 25 | K = 2 26 | 27 | cstrt, rmserror = fit(x, y, K, Type) 28 | print("RMS error = %.4f" % rmserror) 29 | yfit = cstrt.evaluate(x) 30 | 31 | fig, ax = plt.subplots() 32 | ax.plot(u, w*min(df["BSFC"]), "o", mfc="None", ms=7, mew=1.5) 33 | ax.plot(u, np.exp(yfit)*min(df["BSFC"]), linewidth=2) 34 | ax.set_xlabel("Percent Power") 35 | ax.set_ylabel("$BSFC$ [kg/kW/hr]") 36 | ax.legend(["RCV Engine Ltd. Data", "GP approximation"], fontsize=15) 37 | ax.set_xlim([0, 1]) 38 | ax.set_ylim([0, 1]) 39 | ax.grid() 40 | fig.savefig("powertobsfcfit.pdf", bbox_inches="tight") 41 | 42 | # Fitting BSFC vs. RPM 43 | df = pd.read_csv('Dataset_BSFC_kgKwh.csv') 44 | RPM = df['RPM'] 45 | RPMmax = np.amax(RPM) 46 | BSFC = df['BSFC'] 47 | BSFCmin = np.amin(BSFC) 48 | x = np.array(log(RPM/RPMmax)) 49 | y = np.array(log(BSFC/BSFCmin)) 50 | Type = 'SMA' 51 | K = 2 52 | 53 | cstrt, rmserror = fit(x,y,K,Type) 54 | print("RMS error = %.4f" % rmserror) 55 | yfit = cstrt.evaluate(x) 56 | 57 | fig, ax = plt.subplots() 58 | ax.plot(RPM, BSFC, "o", markerfacecolor="None") 59 | ax.plot(RPM, np.exp(yfit)*BSFCmin) 60 | ax.set_xlabel("$RPM$") 61 | ax.set_ylabel("$BSFC$ [lb/hp/hr]") 62 | ax.legend(["Manufacture Data", "GP approximation"]) 63 | ax.grid() 64 | fig.savefig("rpmtobsfcfit.pdf", bbox_inches="tight") 65 | 66 | # Fitting Power vs. RPM 67 | df = pd.read_csv('Dataset_Power_Kw.csv') 68 | RPM = df['RPM'] 69 | RPM_max = np.amax(RPM) 70 | P = df['P'] 71 | P_max = np.amax(P) 72 | y = np.array(log(P/P_max)) 73 | x = np.array(log(RPM/RPM_max)) 74 | Type = 'SMA' 75 | K = 1 76 | cstrt, rmserror = fit(x,y,K,Type) 77 | print("RMS error = %.4f" % rmserror) 78 | yfit = cstrt.evaluate(x) 79 | 80 | fig, ax = plt.subplots() 81 | ax.plot(RPM, P, "o", markerfacecolor="None") 82 | ax.plot(RPM, np.exp(yfit)*P_max) 83 | ax.set_xlabel("$RPM$") 84 | ax.set_ylabel("Shaft Power [kW]") 85 | ax.legend(["Manufacture Data", "GP approximation"]) 86 | ax.grid() 87 | fig.savefig("rpmtopowerfit.pdf", bbox_inches="tight") 88 | 89 | # Fitting Torque vs. RPM 90 | # df = pd.read_csv('Dataset_Torque_Nm.csv') 91 | # RPM = df['RPM'] 92 | # RPM_max = np.amax(RPM) 93 | # Q = df['Q'] 94 | # Q_max = np.amax(Q) 95 | # logRPM = log(RPM/RPM_max) 96 | # logQ = log(Q/Q_max) 97 | # Type = 'SMA' 98 | # K = 1 99 | # cstrt, rms_error = fit(logRPM,logQ,K,Type) 100 | 101 | # fit lapse rate 102 | df = pd.read_csv("DF35_maxPvh.csv") 103 | u = df["ft"] 104 | w = df["kW"]/max(df["kW"]) 105 | x = np.array(u) 106 | y = np.array(w) 107 | 108 | A = np.vstack([x, np.ones(len(x))]).T 109 | m, c = np.linalg.lstsq(A, y)[0] 110 | print("Equation: y = %.4gx + %.4f" % (m, c)) 111 | fig, ax = plt.subplots() 112 | ax.plot(x, y, 'o', label='RCV Engine Data', markerfacecolor="None") 113 | ax.plot(x, m*x + c, label='Fitted Line') 114 | ax.set_ylabel("Engine Lapse Rate") 115 | ax.set_xlabel("Altitude [ft]") 116 | ax.legend() 117 | ax.grid() 118 | fig.savefig("lapseline.pdf", bbox_inches="tight") 119 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/DF70/genericbsfcpower.csv: -------------------------------------------------------------------------------- 1 | 0.9997540049876337, 0.1938783468970966 2 | 0.9011534159132191, 0.19713908929506052 3 | 0.7998372348395438, 0.20585359345655307 4 | 0.6985355240607136, 0.20910386798165048 5 | 0.5999252881230693, 0.21600743013721113 6 | 0.49995484446998684, 0.22654857811393547 7 | 0.3999699305220594, 0.2425539557270553 8 | 0.29995607598444185, 0.26948779261296585 9 | 0.19986986997259884, 0.323742777680853 10 | 0.10085446577929211, 0.48364476965548375 11 | 0.07902843772128779, 0.5655244712184808 12 | 0.058413090998655604, 0.7002302932030664 13 | 0.040137108609312355, 0.9715523239704025 14 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/README.md: -------------------------------------------------------------------------------- 1 | # IC Gas Engine Model 2 | 3 | The models in the this folder aim to capture both engine weight and performance for small IC engines. The DF70 from RCV engines is also modeled with the correct weight and specified BSFC, RPM and Power values and ranges. The general IC engine model is `gas_engine.py` and the DF70 specific model is `df70.py`. 4 | 5 | ## Weight 6 | 7 | The weight is modeled by using a power law derived from data given by the [University of North Dakota](http://media.aero.und.edu/uasresearch.org/documents/195-197 Reference-Section Engines.pdf). Using the fitting techniques in [gpfit](https://github.com/hoburg/gpfit) an equation was derived mapping weight to maximum power and is shown below: 8 | 9 | ![Power to weight law for IC engines](powervsweightfit.png) 10 | 11 | # Performance 12 | 13 | One common way predict engine performance is to assumed a constant $BSFC$ value, or that power is directly proportional to fuel burn. This model accounts for changes in $BSFC$ due to throttle setting. The lower the throttle setting the higher the $BSFC$. Using the DF70 engine, a $BSFC$ to power setting mapping is found using gpfit. 14 | 15 | ![Power to BSFC mapping](powertobsfcfit.png) 16 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/engine/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/df70.py: -------------------------------------------------------------------------------- 1 | " engine_model.py " 2 | from builtins import zip 3 | from gpkit import Model, Variable, units 4 | 5 | class DF70(Model): 6 | "engine model" 7 | def setup(self): 8 | 9 | W = Variable("W", "lbf", "Installed/Total engine weight") 10 | mfac = Variable("m_{fac}", 1.0, "-", "Engine weight margin factor") 11 | bsfc_min = Variable("BSFC_{min}", 0.3162, "kg/kW/hr", "minimum BSFC") 12 | Wdf70 = Variable("W_{DF70}", 7.76, "lbf", 13 | "Installed/Total DF70 engine weight") 14 | Pslmax = Variable("P_{sl-max}", 5.17, "hp", 15 | "Max shaft power at sea level") 16 | h = Variable("h", 12, "in", "engine height") 17 | 18 | constraints = [W/mfac >= Wdf70, 19 | Pslmax == Pslmax] 20 | 21 | return constraints 22 | 23 | def flight_model(self, state): 24 | return DF70Perf(self, state) 25 | 26 | class DF70Perf(Model): 27 | "engine performance model" 28 | def setup(self, static, state): 29 | 30 | Pshaft = Variable("P_{shaft}", "hp", "Shaft power") 31 | bsfc = Variable("BSFC", "kg/kW/hr", "Brake specific fuel consumption") 32 | Pavn = Variable("P_{avn}", 40, "watts", "Avionics power") 33 | Ptotal = Variable("P_{total}", "hp", "Total power, avionics included") 34 | eta_alternator = Variable("\\eta_{alternator}", 0.8, "-", 35 | "alternator efficiency") 36 | href = Variable("h_{ref}", 1000, "ft", "reference altitude") 37 | h_vals = state.substitutions("h") 38 | if len(href) == 1: 39 | h_vals = [h_vals] 40 | lfac = [-0.035*(v/hr.value) + 1.0 41 | for v, hr in zip(h_vals, href)] 42 | Leng = Variable("L_{eng}", lfac, "-", "shaft power loss factor") 43 | Pshaftmax = Variable("P_{shaft-max}", 44 | "hp", "Max shaft power at altitude") 45 | mfac = Variable("m_{fac}", 1.0, "-", "BSFC margin factor") 46 | rpm = Variable("RPM", "rpm", "Engine operating RPM") 47 | rpm_max = Variable("RPM_{max}", 7698, "rpm", "Maximum RPM") 48 | 49 | constraints = [ 50 | (bsfc/mfac/static["BSFC_{min}"])**36.2209 >= ( 51 | 2.31541*(rpm/rpm_max)**8.06517 52 | + 0.00103364*(rpm/rpm_max)**-38.8545), 53 | (Ptotal/Pshaftmax)**0.1 == 0.999495*(rpm/rpm_max)**0.294421, 54 | rpm <= rpm_max, 55 | Pshaftmax/static["P_{sl-max}"] == Leng, 56 | Pshaftmax >= Ptotal, 57 | Ptotal >= Pshaft + Pavn/eta_alternator 58 | ] 59 | 60 | return constraints 61 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/gas_engine.py: -------------------------------------------------------------------------------- 1 | " engine_model.py " 2 | from builtins import zip 3 | from gpkit import Model, Variable, units 4 | import os 5 | import pandas as pd 6 | # from gpkitmodels.tools.fit_constraintset import FitCS 7 | from gpfit.fit_constraintset import FitCS 8 | 9 | 10 | class Engine(Model): 11 | "engine model" 12 | def setup(self, DF70=False): 13 | 14 | self.DF70 = DF70 15 | 16 | W = Variable("W", "lbf", "Installed/Total engine weight") 17 | mfac = Variable("m_{fac}", 1.0, "-", "Engine weight margin factor") 18 | bsfc_min = Variable("BSFC_{min}", 0.3162, "kg/kW/hr", "minimum BSFC") 19 | Pref = Variable("P_{ref}", 10.0, "hp", "Reference shaft power") 20 | Wengref = Variable("W_{eng-ref}", 10.0, "lbf", 21 | "Reference engine weight") 22 | Weng = Variable("W_{eng}", "lbf", "engine weight") 23 | Pslmax = Variable("P_{sl-max}", "hp", 24 | "Max shaft power at sea level") 25 | 26 | path = os.path.dirname(__file__) 27 | df = pd.read_csv(path + os.sep + "power_lawfit.csv").to_dict( 28 | orient="records")[0] 29 | 30 | constraints = [ 31 | FitCS(df, Weng/Wengref, [Pslmax/Pref]), 32 | W/mfac >= 2.572*Weng**0.922*units("lbf")**0.078] 33 | 34 | return constraints 35 | 36 | def flight_model(self, state): 37 | return EnginePerf(self, state) 38 | 39 | 40 | class EnginePerf(Model): 41 | "engine performance model" 42 | def setup(self, static, state): 43 | 44 | Pshaft = Variable("P_{shaft}", "hp", "Shaft power") 45 | bsfc = Variable("BSFC", "kg/kW/hr", "Brake specific fuel consumption") 46 | Pavn = Variable("P_{avn}", 40, "watts", "Avionics power") 47 | Ptotal = Variable("P_{total}", "hp", "Total power, avionics included") 48 | eta_alternator = Variable("\\eta_{alternator}", 0.8, "-", 49 | "alternator efficiency") 50 | href = Variable("h_{ref}", 1000, "ft", "reference altitude") 51 | h_vals = state.substitutions("h") 52 | if len(href) == 1: 53 | h_vals = [h_vals] 54 | lfac = [-0.035*(v/hr.value) + 1.0 55 | for v, hr in zip(h_vals, href)] 56 | Leng = Variable("L_{eng}", lfac, "-", "shaft power loss factor") 57 | Pshaftmax = Variable("P_{shaft-max}", 58 | "hp", "Max shaft power at altitude") 59 | mfac = Variable("m_{fac}", 1.0, "-", "BSFC margin factor") 60 | 61 | path = os.path.dirname(__file__) 62 | df = pd.read_csv(path + os.sep + "powerBSFCfit.csv").to_dict( 63 | orient="records")[0] 64 | 65 | constraints = [ 66 | FitCS(df, bsfc/mfac/static["BSFC_{min}"], [Ptotal/Pshaftmax]), 67 | Pshaftmax/static["P_{sl-max}"] == Leng, 68 | Pshaftmax >= Ptotal, 69 | Ptotal >= Pshaft + Pavn/eta_alternator 70 | ] 71 | 72 | return constraints 73 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/powerBSFCfit.csv: -------------------------------------------------------------------------------- 1 | lb0,ub0,d,e10,K,e00,a1,ftype,max_err,c1,c0,rms_err 2 | 0.11801242227166193,0.96850044350687503,1,1.1292113161811266,2,-7.7018739569394059,18.556942322046762,SMA,0.020769498868956959,1.3862822190984452,0.0086632097847674488,0.0069998230620234763 3 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/power_lawfit.csv: -------------------------------------------------------------------------------- 1 | lb0,ub0,d,K,e00,a1,ftype,max_err,c0,rms_err 2 | 0.29552034789999998,33.742248679999996,1,1,0.7723915754684576,1,MA,1.0718652137941009,1.2784683664089935,0.34279129123926194 3 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/powertobsfcfit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/engine/powertobsfcfit.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/powertobsfcfit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/engine/powertobsfcfit.png -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/powervsweightfit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/engine/powervsweightfit.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/engine/powervsweightfit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/engine/powervsweightfit.png -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/README.md: -------------------------------------------------------------------------------- 1 | # Fuselage 2 | 3 | Included in this folder are two separate fuselage optimization models. One is an ellipsoid shaped fuselage in `elliptical_fuselage.py` the other is a cylindrical shaped fusealge with an ellipctical nose cone and an "o-jive" profile for the rear bulkhead. 4 | 5 | ## Elliptical Fuselage 6 | 7 | The elliptical fuselage assumes a constant skin thickness. Drag model is based off of skin friction drag. No loading cases are assumed. A detailed write up can be found in `./ellipsoid_fuselage.pdf`. 8 | 9 | ## Cylindrical Fusealge 10 | 11 | The cylindrical fuselage has 3 sections, nose, body and bulkhead, whose shapes and surface area are described in the diagram below: 12 | 13 | ![Cylindrical Fuselage](cyl_fuse_drawing.pdf) 14 | 15 | It is assumed that all of the aircraft fuel goes into the cylindrical, middle section. This middle section must have the volume capacity then to store all of the fuel 16 | 17 | $$ V_{body} \geq V_{fuel} $$ 18 | 19 | The fuselage weight is comprised of the skin weight. The fuselage skin is a separate model created when the fuselage model is created. 20 | 21 | The aerodynamic model of the fuselage is based off of a fit to CFD runs in Solidworks for various length to radius ratios of the 3 parts: nose, body, and bulkhead. The model is called FuselageAero. 22 | 23 | ### Fuselage Skin 24 | 25 | The fuselage skin assumes that the body section of fuselage takes all of the tosional and bending loads. The thickness is determined by the loads or the minimum gague thickness. A constant thickness is assumed for all 3 sections 26 | 27 | \begin{align*} 28 | S \geq S_{nose} + S_{body} + S_{bulk} \\ 29 | W >= S*rho_{Kevlar}*t*g\\ 30 | \end{align*} 31 | 32 | The skin is subjected to 2 loads. The first is a pull up load constraining the thickness in either stress or deflection. The second is a landing load. 33 | 34 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/fuselage/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/cyl_fuse_drawing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/fuselage/cyl_fuse_drawing.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/cylindrical_fuselage.py: -------------------------------------------------------------------------------- 1 | " cylindrical fuselage.py " 2 | import numpy as np 3 | from gpkit import Variable, Model 4 | from .fuel_tank import FuelTank 5 | from .fuselage_skin import FuselageSkin 6 | 7 | class Fuselage(Model): 8 | "The thing that carries the fuel, engine, and payload" 9 | def setup(self, Wfueltot): 10 | 11 | R = Variable("R", "ft", "fuselage radius") 12 | l = Variable("l", "ft", "fuselage length") 13 | S = Variable("S", "ft^2", "fuselage cross sectional area") 14 | W = Variable("W", "lbf", "Fuselage weight") 15 | mfac = Variable("m_{fac}", 2.1, "-", "Fuselage weight margin factor") 16 | lbody = Variable("l_{body}", "ft", "center body length") 17 | kbody = Variable("k_{body}", "-", 18 | "fuselage body length to radius ratio") 19 | knose = Variable("k_{nose}", "-", 20 | "fuselage nose length to radius ratio") 21 | kbulk = Variable("k_{bulk}", "-", 22 | "fuselage bulk length to radius ratio") 23 | Swet = Variable("S_{wet}", "ft**2", "fuselage wetted area") 24 | Sbody = Variable("S_{body}", "ft**2", "wetted surface area of body") 25 | Snose = Variable("S_{nose}", "ft**2", "wetted surface area of nose") 26 | Sbulk = Variable("S_{bulk}", "ft**2", "wetted surface area of bulk") 27 | Volbody = Variable("\\mathcal{V}_{body}", "ft**3", "volume of body") 28 | 29 | self.fueltank = FuelTank(Wfueltot) 30 | self.skin = FuselageSkin(Swet, R, lbody) 31 | self.components = [self.fueltank, self.skin] 32 | 33 | constraints = [ 34 | kbody == lbody/R, 35 | Swet >= Sbody + Snose + Sbulk, 36 | Sbody >= 2*np.pi*R*lbody, 37 | Snose**(8./5.) >= ( 38 | (2*np.pi*R**2)**(8./5.)*(1./3. + 2./3.*(knose)**(8./5.))), 39 | Sbulk >= R**2*(0.012322*kbulk**2 + 1.524925*kbulk + 0.502498), 40 | Volbody <= np.pi*R**2*lbody, 41 | l <= 3*R*(kbody*knose*kbulk)**(1./3), 42 | S >= np.pi*R**2, 43 | Volbody >= self.fueltank["\\mathcal{V}"], 44 | W/mfac >= self.fueltank["W"] + self.skin["W"], 45 | ] 46 | 47 | return self.components, constraints 48 | 49 | def loading(self, Wcent): 50 | return FuselageLoading(self, Wcent) 51 | 52 | def flight_model(self, state): 53 | return FuselageAero(self, state) 54 | 55 | class FuselageLoading(Model): 56 | "fuselage loading cases" 57 | def setup(self, fuselage, Wcent): 58 | 59 | loading = [fuselage.skin.loading(Wcent)] 60 | loading.append(fuselage.skin.landing(Wcent)) 61 | 62 | return loading 63 | 64 | class FuselageAero(Model): 65 | "fuselage drag model" 66 | def setup(self, static, state): 67 | 68 | Cf = Variable("C_f", "-", "fuselage skin friction coefficient") 69 | Re = Variable("Re", "-", "fuselage reynolds number") 70 | Reref = Variable("Re_{ref}", 1e6, "-", "reference Reynolds number") 71 | Cfref = Variable("C_{r_{ref}}", "-", 72 | "reference skin friction coefficient") 73 | Cd = Variable("C_d", "-", "fuselage drag coefficient") 74 | 75 | constraints = [ 76 | Re == state["V"]*state["\\rho"]*static["l"]/state["\\mu"], 77 | Cf >= 0.455/Re**0.3, 78 | Cfref == 0.455/Reref**0.3, 79 | Cd**0.996232 >= Cf/Cfref*( 80 | 0.00243049*static["k_{body}"]**0.033607 81 | * static["k_{nose}"]**1.21682 * static["k_{bulk}"]**0.306251 82 | + 0.00255095*static["k_{body}"]**-0.0316887 83 | * static["k_{nose}"]**-0.585489 * static["k_{bulk}"]**1.15394 84 | + 0.0436011 * static["k_{body}"]**0.0545722 85 | * static["k_{nose}"]**0.258228 * static["k_{bulk}"]**-1.42664 86 | + 0.00970479 * static["k_{body}"]**0.8661 87 | * static["k_{nose}"]**-0.209136 * static["k_{bulk}"]**-0.156166) 88 | ] 89 | 90 | return constraints 91 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/ellipsoid_fuselage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/fuselage/ellipsoid_fuselage.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/elliptical_fuselage.py: -------------------------------------------------------------------------------- 1 | " elliptical fuselage.py " 2 | import numpy as np 3 | from gpkit import Variable, Model, parse_variables 4 | from gpkitmodels.GP.materials import cfrpfabric 5 | from gpkitmodels import g 6 | 7 | class FuselageAero(Model): 8 | """ Fuselage Aerodyanmic Model 9 | 10 | Variables 11 | --------- 12 | Cf [-] fuselage skin friction coefficient 13 | Re [-] fuselage reynolds number 14 | Cd [-] fuselage drag coefficient 15 | mfac 1.0 [-] fuselage drag margin 16 | 17 | """ 18 | @parse_variables(__doc__, globals()) 19 | def setup(self, static, state): 20 | V = state.V 21 | rho = state.rho 22 | l = static.l 23 | mu = state.mu 24 | k = static.k 25 | 26 | constraints = [ 27 | Re == V*rho*l/mu, 28 | Cf >= 0.455/Re**0.3, 29 | Cd/mfac >= Cf*k 30 | ] 31 | 32 | return constraints 33 | 34 | class Fuselage(Model): 35 | """ Fuselage Model 36 | 37 | Variables 38 | --------- 39 | R [ft] fuselage radius 40 | l [ft] fuselage length 41 | S [ft^2] wetted fuselage area 42 | W [lbf] fuselage weight 43 | mfac 2.0 [-] fuselage weight margin factor 44 | f [-] fineness ratio of lenth to diameter 45 | k [-] fuselage form factor 46 | Vol [ft^3] fuselae volume 47 | rhofuel 6.01 [lbf/gallon] density of 100LL 48 | rhocfrp 1.6 [g/cm^3] density of CFRP 49 | t [in] fuselage skin thickness 50 | nply 2 [-] number of plys 51 | 52 | """ 53 | material = cfrpfabric 54 | flight_model = FuselageAero 55 | 56 | @parse_variables(__doc__, globals()) 57 | def setup(self): 58 | rhocfrp = self.material.rho 59 | tmin = self.material.tmin 60 | 61 | constraints = [ 62 | f == l/R/2, 63 | k >= 1 + 60/f**3 + f/400, 64 | 3*(S/np.pi)**1.6075 >= 2*(l*R*2)**1.6075 + (2*R)**(2*1.6075), 65 | Vol <= 4*np.pi/3*(l/2)*R**2, 66 | W/mfac >= S*rhocfrp*t*g, 67 | t >= nply*tmin, 68 | ] 69 | 70 | return constraints 71 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/fuel_tank.py: -------------------------------------------------------------------------------- 1 | " fuel tank " 2 | from gpkit import Model, Variable 3 | 4 | class FuelTank(Model): 5 | """ 6 | Returns the weight of the fuel tank. Assumes a cylinder shape with some 7 | fineness ratio 8 | """ 9 | def setup(self, Wfueltot): 10 | 11 | W = Variable("W", "lbf", "fuel tank weight") 12 | f = Variable("f", 0.03, "-", "fraction fuel tank weight to fuel weight") 13 | mfac = Variable("m_{fac}", 1.1, "-", "fuel volume margin factor") 14 | rhofuel = Variable("\\rho_{fuel}", 6.01, "lbf/gallon", 15 | "density of 100LL") 16 | Vol = Variable("\\mathcal{V}", "ft^3", "fuel tank volume") 17 | 18 | constraints = [W >= f*Wfueltot, 19 | Vol/mfac >= Wfueltot/rhofuel, 20 | ] 21 | 22 | return constraints 23 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/fuselage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/fuselage/fuselage.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/fuselage_profile_drag/fusedrag.csv: -------------------------------------------------------------------------------- 1 | tubelr,noselr,taillr,drag,cd_front 1,1,1,1035.305,0.054924636 1,1,2,628.942,0.033366409 1,1,3,484.06,0.025680181 1,1,4,523.018,0.027746967 1,1,5,546.125,0.028972831 1,1,6,615.724,0.032665173 1,1,8,912.273,0.048397586 1,1,10,993.842,0.052724956 1,1,15,1253.929,0.066523 1,2,1,1354.212,0.071843178 1,2,2,698.697,0.037067027 1,2,3,570.088,0.030244108 1,2,4,562.26,0.029828819 1,2,5,576.009,0.030558227 1,2,6,616.17,0.032688834 1,2,8,753.805,0.039990597 1,2,10,803.671,0.042636071 1,2,15,1016.955,0.053951138 1,3,1,1349.822,0.071610281 1,3,2,882.647,0.046825878 1,3,3,681.326,0.036145467 1,3,4,657.46,0.034879336 1,3,5,661.495,0.0350934 1,3,6,722.119,0.038309603 1,3,8,835.784,0.044339718 1,3,10,889.588,0.047194109 1,3,15,1101.411,0.058431668 1,5,1,1751.288,0.092908714 1,5,2,1043.149,0.055340773 1,5,3,873.062,0.046317378 1,5,4,862.476,0.045755773 1,5,5,867.8,0.04603822 1,5,6,918.192,0.048711599 1,5,8,965.876,0.051241313 1,5,10,1039.941,0.055170584 1,5,15,1245.72,0.066087499 3,1,1,1412.136,0.074916142 3,1,2,937.053,0.049712206 3,1,3,761.712,0.040410077 3,1,4,717.539,0.038066626 3,1,5,774.43,0.041084788 3,1,6,1009.313,0.053545718 3,1,8,1073.001,0.056924471 3,1,10,1138.328,0.060390176 3,1,15,1427.87,0.075750856 3,2,1,1677.009,0.088968091 3,2,2,971.614,0.051545724 3,2,3,745.06,0.039526661 3,2,4,724.946,0.03845958 3,2,5,838.682,0.044493462 3,2,6,840.405,0.04458487 3,2,8,881.551,0.046767733 3,2,10,964.773,0.051182797 3,2,15,1184.484,0.062838828 3,3,1,1689.709,0.089641847 3,3,2,1061.085,0.056292308 3,3,3,876.277,0.046487939 3,3,4,874.08,0.046371384 3,3,5,860.193,0.045634656 3,3,6,893.371,0.047394804 3,3,8,963.363,0.051107994 3,3,10,1038.846,0.055112492 3,3,15,1241.806,0.065879854 3,5,1,1861.434,0.098752141 3,5,2,1344.49,0.07132741 3,5,3,1080.079,0.057299971 3,5,4,1028.981,0.054589137 3,5,5,990.455,0.05254527 3,5,6,1028.758,0.054577307 3,5,8,1112.913,0.059041868 3,5,10,1169.812,0.062060454 3,5,15,1386.483,0.073555208 5,1,1,1788.728,0.094894968 5,1,2,1163.367,0.061718536 5,1,3,996.039,0.05284151 5,1,4,1090.061,0.057829532 5,1,5,1125.31,0.05969955 5,1,6,1169.837,0.06206178 5,1,8,1207.535,0.064061721 5,1,10,1326.058,0.070349562 5,1,15,1557.048,0.082603962 5,2,1,1788.143,0.094863932 5,2,2,1161.068,0.06159657 5,2,3,1034.13,0.0548623 5,2,4,946.827,0.050230732 5,2,5,949.72,0.050384211 5,2,6,970.72,0.051498295 5,2,8,1036.778,0.055002781 5,2,10,1089.987,0.057825606 5,2,15,1307.506,0.069365348 5,3,1,1888.519,0.100189045 5,3,2,1328.993,0.070505268 5,3,3,1075.567,0.057060602 5,3,4,1028.461,0.054561551 5,3,5,1005.752,0.053356801 5,3,6,1023.171,0.054280907 5,3,8,1112.382,0.059013698 5,3,10,1165.087,0.061809785 5,3,15,1388.393,0.073656536 5,5,1,2164.614,0.114836339 5,5,2,1531.067,0.081225627 5,5,3,1222.183,0.064838822 5,5,4,1137.749,0.060359459 5,5,5,1194.024,0.063344941 5,5,6,1195.987,0.063449081 5,5,8,1241.198,0.065847599 5,5,10,1329.098,0.070510839 5,5,15,1522.546,0.080773574 10,1,1,2574.053,0.136557753 10,1,2,1856.29,0.098479243 10,1,3,1579.383,0.08378887 10,1,4,1488.625,0.078974009 10,1,5,1548.464,0.082148567 10,1,6,1552.098,0.082341356 10,1,8,1637.829,0.086889527 10,1,10,1694.605,0.089901587 10,1,15,1927.926,0.102279651 10,2,1,2553.915,0.135489399 10,2,2,1732.495,0.091911714 10,2,3,1403.615,0.074464088 10,2,4,1343.743,0.07128778 10,2,5,1264.998,0.067110228 10,2,6,1317.244,0.069881965 10,2,8,1400.165,0.07428106 10,2,10,1450.112,0.076930831 10,2,15,1662.582,0.088202715 10,3,1,2539.403,0.134719513 10,3,2,1828.237,0.096990985 10,3,3,1478.233,0.078422696 10,3,4,1345.505,0.071381257 10,3,5,1342.065,0.07119876 10,3,6,1391.456,0.073819033 10,3,8,1454.841,0.077181712 10,3,10,1528.788,0.081104722 10,3,15,1754.559,0.093082246 10,5,1,2581.016,0.136927152 10,5,2,1974.068,0.10472756 10,5,3,1622.004,0.086049985 10,5,4,1524.516,0.080878086 10,5,5,1547.127,0.082077637 10,5,6,1535.669,0.081469771 10,5,8,1598.099,0.084781785 10,5,10,1660.352,0.088084409 10,5,15,1871.798,0.099301968 20,1,1,3373.827,0.178987081 20,1,2,2810.082,0.14907948 20,1,3,2403.816,0.1275264 20,1,4,2340.528,0.124168867 20,1,5,2306.57,0.122367339 20,1,6,2281.424,0.121033302 20,1,8,2355.985,0.124988886 20,1,10,2411.924,0.127956542 20,1,15,2632.213,0.139643237 20,2,1,3122.014,0.165627987 20,2,2,2585.664,0.137173736 20,2,3,2166.146,0.114917614 20,2,4,1975.77,0.104817854 20,2,5,2068.303,0.109726882 20,2,6,2043.235,0.108396983 20,2,8,2082.744,0.110493001 20,2,10,2151.441,0.11413749 20,2,15,2353.945,0.124880661 20,3,1,3167.898,0.168062209 20,3,2,2639.043,0.140005579 20,3,3,2253.647,0.119559687 20,3,4,2058.463,0.109204854 20,3,5,2154.367,0.114292719 20,3,6,2086.292,0.110681228 20,3,8,2140.522,0.113558219 20,3,10,2200.414,0.116735588 20,3,15,2405.294,0.12760481 20,5,1,3422.578,0.181573402 20,5,2,2793.204,0.148184075 20,5,3,2394.765,0.127046229 20,5,4,2233.341,0.11848242 20,5,5,2157.161,0.114440945 20,5,6,2245.555,0.119130393 20,5,8,2287.977,0.12138095 20,5,10,2343.273,0.124314494 20,5,15,2545.617,0.135049176 -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/fuselage_profile_drag/fusedragfit.py: -------------------------------------------------------------------------------- 1 | " fuselage drag fits " 2 | from builtins import zip 3 | import pandas as pd 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | def fit_setup(filename): 8 | "set up fitting variables" 9 | 10 | df = pd.read_csv(filename) 11 | 12 | u1 = np.array(df["tubelr"]) 13 | u2 = np.array(df["noselr"]) 14 | u3 = np.array(df["taillr"]) 15 | 16 | w = np.array(df["cd_front"]) 17 | 18 | u1 = u1.astype(np.float) 19 | u2 = u2.astype(np.float) 20 | u3 = u3.astype(np.float) 21 | w = w.astype(np.float) 22 | 23 | u = [u1, u2, u3] 24 | x = np.log(u) 25 | y = np.log(w) 26 | 27 | return x, y 28 | 29 | def return_fit(u_1, u_2, u_3): 30 | "fit using SMA, K = 4, RMS = 0.0479" 31 | w = ( 32 | 0.00243049 * (u_1)**0.033607 * (u_2)**1.21682 * (u_3)**0.306251 33 | + 0.00255095 * (u_1)**-0.0316887 * (u_2)**-0.585489 * (u_3)**1.15394 34 | + 0.0436011 * (u_1)**0.0545722 * (u_2)**0.258228 * (u_3)**-1.42664 35 | + 0.00970479 * (u_1)**0.8661 * (u_2)**-0.209136 * (u_3)**-0.156166) \ 36 | ** (1/0.996232) 37 | return w 38 | 39 | def plot_fits(filename): 40 | "plot fit against data" 41 | 42 | df = pd.read_csv(filename) 43 | u1 = np.array(df["tubelr"]) 44 | u2 = np.array(df["noselr"]) 45 | u3 = np.array(df["taillr"]) 46 | body = np.unique(u1) 47 | nose = np.unique(u2) 48 | tail = np.unique(u3) 49 | 50 | figs = [] 51 | 52 | colors = ["k", "m", "b", "g", "y"] 53 | assert len(colors) == len(body) 54 | 55 | for n in nose: 56 | fig, ax = plt.subplots() 57 | datan = df[(df["noselr"] == n)] 58 | for b, clr in zip(body, colors): 59 | datab = datan[(datan["tubelr"] == b)] 60 | ax.plot(datab["taillr"], datab["cd_front"], "o", c=clr) 61 | trl = np.array(datab["taillr"]) 62 | taillr = np.linspace(trl[0], trl[-1], 20) 63 | cd = return_fit(b, n, taillr) 64 | ax.plot(taillr, cd, c=clr, label="body finess ratio = %d" % b) 65 | ax.legend() 66 | ax.grid() 67 | ax.set_title("nose finess ratio = %d" % n) 68 | ax.set_xlabel("tail finess ratio") 69 | ax.set_ylabel("fuselage $C_{dp}$") 70 | fig.savefig("fuse_drag_nose%d.pdf" % n) 71 | figs.append(fig) 72 | 73 | return figs 74 | 75 | if __name__ == "__main__": 76 | X, Y = fit_setup("fusedrag.csv") 77 | df = pd.read_csv("fusedrag.csv") 78 | F = plot_fits("fusedrag.csv") 79 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/fuselage_skin.py: -------------------------------------------------------------------------------- 1 | " fuselage skin " 2 | import numpy as np 3 | from gpkit import Model, Variable 4 | 5 | class FuselageSkin(Model): 6 | "fuselage skin model" 7 | def setup(self, S, R, l): 8 | 9 | W = Variable("W", "lbf", "fuselage skin weight") 10 | m = Variable("m", "kg", "fuselage skin mass") 11 | g = Variable("g", 9.81, "m/s^2", "Gravitational acceleration") 12 | rhokevlar = Variable("\\rho_{kevlar}", 1.3629, "g/cm**3", 13 | "kevlar density") 14 | t = Variable("t", "in", "skin thickness") 15 | tmin = Variable("t_{min}", 0.03, "in", "minimum skin thickness") 16 | I = Variable("I", "m**4", "wing skin moment of inertia") 17 | Ig = Variable("I_G", "kg*m**2", "mass moment of inertia") 18 | E = Variable("E", 30, "GPa", "Young's Modulus of Kevlar") 19 | 20 | constraints = [m >= S*rhokevlar*t, 21 | W >= m*g, 22 | t >= tmin, 23 | I <= np.pi*R**3*t, 24 | Ig >= m*(4*R**2 + 4*R*t + t**2), 25 | l == l, 26 | E == E] 27 | 28 | return constraints 29 | 30 | def loading(self, Wcent): 31 | return FuselageSkinL(self, Wcent) 32 | 33 | def landing(self, Wcent): 34 | return FuselageLanding(self, Wcent) 35 | 36 | class FuselageSkinL(Model): 37 | "fuselage skin loading" 38 | def setup(self, static, Wcent): 39 | 40 | Mh = Variable("M_h", "N*m", "horizontal axis center fuselage moment") 41 | Nmax = Variable("N_{max}", 5, "-", "max loading") 42 | sigmakevlar = Variable("\\sigma_{Kevlar}", 190, "MPa", 43 | "stress strength of Kevlar") 44 | q = Variable("q", "N/m", "distributed load") 45 | kappa = Variable("\\kappa", 0.05, "-", "maximum tip deflection ratio") 46 | 47 | constraints = [ 48 | Mh >= Nmax*Wcent/4*static["l_{body}"], 49 | sigmakevlar >= Mh*static["R"]/static["I"], 50 | q >= Wcent*Nmax/static["l_{body}"], 51 | kappa*static["l_{body}"]/2 >= ( 52 | q*(static["l_{body}"]/2)**4/(8*static["E"]*static["I"])) 53 | ] 54 | 55 | return constraints 56 | 57 | class FuselageLanding(Model): 58 | "fuselage loading case" 59 | def setup(self, static, Wcent): 60 | 61 | F = Variable("F", "lbf", "maximum landing force") 62 | Nmax = Variable("N_{max}", 5, "-", "maximum landing load factor") 63 | a = Variable("a", "m/s**2", "landing vertical acceleration") 64 | omegadot = Variable("\\dot{\\omega}", "1/s**2", 65 | "angular acceleration about rear fuselage") 66 | Mg = Variable("M_G", "N*m", "landing moment about center of mass") 67 | sigmakevlar = Variable("\\sigma_{Kevlar}", 190, "MPa", 68 | "stress strength of Kevlar") 69 | 70 | constraints = [F >= Wcent*Nmax, 71 | a >= F/static["m"], 72 | omegadot >= a/(static["l_{body}"]/2), 73 | Mg >= static["I_G"]*omegadot, 74 | sigmakevlar >= Mg*static["R"]/static["I"] 75 | ] 76 | 77 | return constraints 78 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/fuselage/test_fuselage.py: -------------------------------------------------------------------------------- 1 | from gpkit import Model 2 | from gpkitmodels.GP.aircraft.fuselage.elliptical_fuselage import Fuselage 3 | from gpkitmodels.GP.aircraft.wing.wing_test import FlightState 4 | 5 | def test_ellp(): 6 | "elliptical fuselage test" 7 | f = Fuselage() 8 | fs = FlightState() 9 | faero = f.flight_model(f, fs) 10 | f.substitutions[f.Vol] = 1.33 11 | 12 | m = Model(f.W*faero.Cd, [f, fs, faero]) 13 | m.solve() 14 | 15 | def test(): 16 | "tests" 17 | test_ellp() 18 | 19 | if __name__ == "__main__": 20 | test() 21 | 22 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/mission/README.md: -------------------------------------------------------------------------------- 1 | # Mission 2 | 3 | This folder contains various ''mission`` models that can be used for a given aircraft. 4 | 5 | ## BreguetEndurance 6 | 7 | This model predicts how much fuel is needed to fly for a certain time period. It needs as an input a performance model that has the following variables: 8 | 9 | \begin{table}[] 10 | \centering 11 | \begin{tabular}{ll} 12 | Variable & Description \\ 13 | \hline 14 | $P_{total}$ & Total power used by the aircraft \\ 15 | $BSFC$ & Break specific fuel consumption \\ 16 | $W_{end}$ & End of flight segment weight \\ 17 | $W_{start}$ & Start of flight segment weight 18 | \end{tabular} 19 | \end{table} 20 | 21 | The following is a derivation of the form of the Breguet Range equation that is used and a description of the Taylor expanison used to make this GP-compatible. 22 | 23 | \begin{equation} 24 | \label{e:breguetendurance} 25 | t = \frac{W_{\text{ave}}}{P_{\text{shaft}}\text{BSFC}g} \ln{\left( \frac{W_{\text{initial}}}{W_{\text{final}}}\right)}. 26 | \end{equation} 27 | 28 | The derivation begins with the differential form of Breguet Range,\cite{br2} 29 | 30 | \begin{equation} 31 | \label{e:breguetdiff} 32 | -\frac{dW}{dt} = g\dot{m}_{\text{fuel}}. 33 | \end{equation} 34 | 35 | Using the definition of $\text{BSFC}$ 36 | 37 | \begin{equation} 38 | \label{e:brBSFC} 39 | \text{BSFC} = \frac{\dot{m}_{\text{fuel}}}{P_{\text{shaft}}}, 40 | \end{equation} 41 | 42 | Equation~\eqref{e:breguetdiff} can be written as 43 | 44 | \begin{equation} 45 | \label{e:brdiff2} 46 | -dW = g P_{\text{shaft}} \text{BSFC} dt. 47 | \end{equation} 48 | 49 | This version comes from assuming that $\text{BSFC}$ and the power to weight ratio, $(P_{\text{shaft}}/W)$, are constant during the considered flight segment. 50 | One way to obtain a constant power to weight ratio is a constant velocity and constant lift coefficient.\cite{br2} 51 | $W_{\text{ave}}$ is assumed to be the geometric mean, defined as 52 | 53 | Dividing by $W$, 54 | 55 | \begin{equation} 56 | \label{e:brdiff2} 57 | -\frac{dW}{W} = \frac{g P_{\text{shaft}}\text{BSFC} }{W} dt, 58 | \end{equation} 59 | 60 | and integrating, the Breguet Range equation can be expressed as 61 | 62 | \begin{equation} 63 | \label{e:be1} 64 | \ln{\left( \frac{W_{\text{initial}}}{W_{\text{final}}} \right)} = \frac{gP_{\text{shaft}}\text{BSFC}}{W_{\text{ave}}} t, 65 | \end{equation} 66 | 67 | where $W_{\text{ave}}$ is the average weight of the aircraft during the flight segment. 68 | 69 | \begin{equation} 70 | \label{e:gpmean} 71 | W_{\text{ave}} = \sqrt{W_{\text{initial}}W_{\text{final}}}. 72 | \end{equation} 73 | 74 | To make Equation~\eqref{e:breguetendurance} GP compatible, a Taylor expansion is used,\cite{hoburgthesis} 75 | 76 | \begin{align} 77 | \label{e:brzbre} 78 | z_{\text{bre}} &\geq \frac{P_{\text{shaft}}t \text{BSFC} g}{W}\\ 79 | \label{e:brtaylor} 80 | \frac{W_{\text{fuel}}}{W_\text{final}} &\geq z_{\text{bre}} + \frac{z_{\text{bre}}^2}{2} + \frac{z_{\text{bre}}^3}{6} + \frac{z_{\text{bre}}^4}{24} + \dots 81 | \end{align} 82 | 83 | Equations~\eqref{e:brzbre} and~\eqref{e:brtaylor} are monomial and posynomial respectively and therefore GP compatible. For long-endurance aircraft, missions can last days, causing the power to weight ratio $(P_{\text{shaft}}/W)$ to vary significantly during the course of the flight. 84 | Equations~\eqref{e:brzbre},~\eqref{e:brtaylor}, and~\eqref{e:slfweight} can be discretized to account for this. 85 | 86 | \begin{align} 87 | \label{e:slfweightd} 88 | \sqrt{W_i W_{i+1}} &= \frac{1}{2} \rho_i V_i^2 C_{L_i} S \\ 89 | \label{e:brzbred} 90 | z_{bre_i} &\geq \frac{P_{\text{shaft}_i}t_i \text{BSFC} g}{\sqrt{W_i W_{i+1}}}\\ 91 | \label{e:brtaylord} 92 | \frac{W_{\text{fuel}_i}}{W_{i+1}} &\geq z_{bre_i} + \frac{z_{bre_i}^2}{2} + \frac{z_{bre_i}^3}{6} + \frac{z_{bre_i}^3}{24} 93 | \end{align} 94 | 95 | For evaluation of long-endurance, gas-powered aircraft a discretization of $N=5$ was used. 96 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/mission/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/mission/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/mission/breguet_endurance.py: -------------------------------------------------------------------------------- 1 | " breguet_endurance.py " 2 | from gpkit import Model, Variable 3 | from gpkit.tools import te_exp_minus1 4 | from gpkit.constraints.tight import Tight as TCS 5 | 6 | class BreguetEndurance(Model): 7 | "breguet endurance model" 8 | def setup(self, perf): 9 | z_bre = Variable("z_{bre}", "-", "Breguet coefficient") 10 | t = Variable("t", "days", "Time per flight segment") 11 | f_fueloil = Variable("f_{(fuel/oil)}", 0.98, "-", "Fuel-oil fraction") 12 | Wfuel = Variable("W_{fuel}", "lbf", "Segment-fuel weight") 13 | g = Variable("g", 9.81, "m/s^2", "gravitational acceleration") 14 | 15 | constraints = [ 16 | TCS([z_bre >= (perf["P_{total}"]*t*perf["BSFC"]*g 17 | / (perf["W_{end}"]*perf["W_{start}"])**0.5)]), 18 | f_fueloil*Wfuel/perf["W_{end}"] >= te_exp_minus1(z_bre, 3), 19 | perf["W_{start}"] >= perf["W_{end}"] + Wfuel 20 | ] 21 | 22 | return constraints 23 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/mission/mission.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/mission/mission.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/motor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/motor/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/motor/motor.py: -------------------------------------------------------------------------------- 1 | "Electric motor model " 2 | from gpkit import Model, parse_variables 3 | from gpkit.constraints.tight import Tight as TCS 4 | from gpkitmodels.GP.aircraft.prop.propeller import Propeller, ActuatorProp 5 | from gpkitmodels import g 6 | 7 | class MotorPerf(Model): 8 | """ Electric Motor Performance Model 9 | 10 | Note: The last two constraints may not be tight if there is a motor energy 11 | constraint that does not size the motor. TCS removed to prevent 12 | unnecessary warnings - for most normal useage they will be tight. 13 | 14 | Variables 15 | --------- 16 | Pshaft [kW] motor output shaft power 17 | Pelec [kW] motor input shaft power 18 | etam [-] motor efficiency 19 | Q [N*m] torque 20 | omega [rpm] propeller rotation rate 21 | i [amp] current 22 | v [V] woltage 23 | """ 24 | @parse_variables(__doc__, globals()) 25 | def setup(self, static, state): 26 | Kv = static.Kv 27 | R = static.R 28 | i0 = static.i0 29 | V_max = static.V_max 30 | 31 | return [Pshaft == Q*omega, 32 | Pelec == v*i, 33 | etam == Pshaft/Pelec, 34 | static.Qmax >= Q, 35 | v <= V_max, 36 | i >= Q*Kv+i0, 37 | v >= omega/Kv + i*R] 38 | 39 | class Motor(Model): 40 | """ Electric Motor Model 41 | 42 | Variables 43 | --------- 44 | Qstar .8 [kg/(N*m)] motor specific torque 45 | W [lbf] motor weight 46 | Qmax [N*m] motor max. torque 47 | V_max 300 [V] motor max voltage 48 | Kv_min 1 [rpm/V] min motor voltage constant 49 | Kv_max 1000 [rpm/V] max motor voltage constant 50 | Kv [rpm/V] motor voltage constant 51 | i0 4.5 [amp] zero-load current 52 | R .033 [ohms] internal resistance 53 | """ 54 | 55 | flight_model = MotorPerf 56 | 57 | @parse_variables(__doc__, globals()) 58 | def setup(self): 59 | constraints = [W >= Qstar*Qmax*g, 60 | Kv >= Kv_min, 61 | Kv <= Kv_max] 62 | 63 | return constraints 64 | 65 | class PropulsorPerf(Model): 66 | "Propulsor Performance Model" 67 | 68 | @parse_variables(__doc__, globals()) 69 | def setup(self, static, state): 70 | self.prop = static.prop.flight_model(static.prop, state) 71 | self.motor = static.motor.flight_model(static.motor, state) 72 | 73 | self.components = [self.prop, self.motor] 74 | 75 | constraints = [self.prop.Q == self.motor.Q, 76 | self.prop.omega == self.motor.omega 77 | ] 78 | 79 | return constraints, self.components 80 | 81 | class Propulsor(Model): 82 | """Propulsor model 83 | 84 | Variables 85 | --------- 86 | W [lbf] propulsor weight 87 | 88 | """ 89 | flight_model = PropulsorPerf 90 | prop_flight_model = ActuatorProp 91 | 92 | @parse_variables(__doc__, globals()) 93 | def setup(self): 94 | Propeller.flight_model = self.prop_flight_model 95 | self.prop = Propeller() 96 | self.motor = Motor() 97 | 98 | components = [self.prop, self.motor] 99 | 100 | return [self.W >= self.prop.W + self.motor.W], components 101 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/motor/motor_test.py: -------------------------------------------------------------------------------- 1 | from gpkit import Model, parse_variables, SignomialsEnabled, SignomialEquality, units 2 | from gpkitmodels.GP.aircraft.motor.motor import Propulsor, Motor, MotorPerf 3 | from gpkitmodels.GP.aircraft.prop.propeller import Propeller, ActuatorProp 4 | from gpkitmodels.GP.aircraft.wing.wing_test import FlightState 5 | from gpkitmodels.SP.aircraft.prop.propeller import BladeElementProp 6 | 7 | class Propulsor_Test(Model): 8 | """Propulsor Test Model 9 | """ 10 | 11 | def setup(self): 12 | fs = FlightState() 13 | p = Propulsor() 14 | pp = p.flight_model(p,fs) 15 | pp.substitutions[pp.prop.T] = 100 16 | self.cost = 1./pp.motor.etam + p.W/(1000*units('lbf')) + 1./pp.prop.eta 17 | 18 | return fs,p,pp 19 | 20 | class Actuator_Propulsor_Test(Model): 21 | """Propulsor Test Model w/ Actuator Disk Propeller 22 | """ 23 | 24 | def setup(self): 25 | fs = FlightState() 26 | Propulsor.prop_flight_model = ActuatorProp 27 | p = Propulsor() 28 | pp = p.flight_model(p,fs) 29 | pp.substitutions[pp.prop.T] = 100 30 | self.cost = pp.motor.Pelec/(1000*units('W')) + p.W/(1000*units('lbf')) 31 | 32 | return fs,p,pp 33 | class BladeElement_Propulsor_Test(Model): 34 | """Propulsor Test Model w/ Blade Element Propeller 35 | """ 36 | 37 | def setup(self): 38 | fs = FlightState() 39 | Propulsor.prop_flight_model = BladeElementProp 40 | p = Propulsor() 41 | pp = p.flight_model(p,fs) 42 | pp.substitutions[pp.prop.T] = 100 43 | self.cost = pp.motor.Pelec/(1000*units('W')) + p.W/(1000*units('lbf')) 44 | 45 | return fs,p,pp 46 | 47 | def actuator_propulsor_test(): 48 | test = Actuator_Propulsor_Test() 49 | test.solve() 50 | 51 | def ME_propulsor_test(): 52 | test = BladeElement_Propulsor_Test() 53 | sol = test.localsolve(use_leqs=False) # cvxopt gets singular with leqs 54 | 55 | def propulsor_test(): 56 | test = Propulsor_Test() 57 | sol = test.solve() 58 | 59 | class Motor_P_Test(Model): 60 | def setup(self): 61 | fs = FlightState() 62 | m = Motor() 63 | mp = MotorPerf(m,fs) 64 | self.mp = mp 65 | mp.substitutions[m.Qmax] = 100 66 | mp.substitutions[mp.Q] = 10 67 | self.cost = 1./mp.etam + m.W/(100.*units('lbf')) 68 | return self.mp, fs, m 69 | 70 | class speed_280_motor(Model): 71 | def setup(self): 72 | fs = FlightState() 73 | m = Motor() 74 | mp = MotorPerf(m,fs) 75 | self.mp = mp 76 | mp.substitutions[m.Qmax] = 100 77 | mp.substitutions[mp.R] = .7 78 | mp.substitutions[mp.i0] = .16 79 | mp.substitutions[mp.Kv] = 3800 80 | mp.substitutions[mp.v] = 6 81 | self.cost = 1./mp.etam 82 | return self.mp, fs 83 | class hacker_q150_45_motor(Model): 84 | def setup(self): 85 | fs = FlightState() 86 | m = Motor() 87 | mp = MotorPerf(m,fs) 88 | self.mp = mp 89 | mp.substitutions[m.Qmax] = 10000 90 | mp.substitutions[mp.R] = .033 91 | mp.substitutions[mp.i0] = 4.5 92 | mp.substitutions[mp.Kv] = 29 93 | self.cost = 1./mp.etam 94 | return self.mp, fs 95 | 96 | def motor_test(): 97 | test = Motor_P_Test() 98 | test.solve() 99 | 100 | def test(): 101 | motor_test() 102 | actuator_propulsor_test() 103 | propulsor_test() 104 | ME_propulsor_test() 105 | 106 | if __name__ == "__main__": 107 | test() 108 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/prop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/prop/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/prop/arccos_fit.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import unittest 3 | from numpy import log, exp, log10, vstack 4 | from numpy import arccos,arange 5 | from gpfit.fit import fit 6 | from numpy.random import random_sample 7 | i = arange(0.0001,3,.001) 8 | j = arccos(exp(-i)) 9 | x = log(i) 10 | y = log(j) 11 | K = 1 12 | 13 | cstrt, rmsErr = fit(x,y,K,"SMA") 14 | print(rmsErr) -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/prop/prop_test.py: -------------------------------------------------------------------------------- 1 | " propeller tests " 2 | from gpkitmodels.GP.aircraft.prop.propeller import Propeller, ActuatorProp 3 | from gpkitmodels.SP.aircraft.prop.propeller import BladeElementProp 4 | 5 | from gpkitmodels.GP.aircraft.wing.wing_test import FlightState 6 | from gpkit import units, Model 7 | 8 | def simpleprop_test(): 9 | " test simple propeller model " 10 | fs = FlightState() 11 | Propeller.flight_model = ActuatorProp 12 | p = Propeller() 13 | pp = p.flight_model(p, fs) 14 | m = Model(1/pp.eta + p.W/(100.*units("lbf"))+ pp.Q/(100.*units("N*m")), 15 | [fs, p, pp]) 16 | m.substitutions.update({"rho": 1.225, "V": 50, "T": 100, "omega":1000}) 17 | m.solve() 18 | 19 | def ME_eta_test(): 20 | 21 | fs = FlightState() 22 | Propeller.flight_model = BladeElementProp 23 | p = Propeller() 24 | pp = p.flight_model(p,fs) 25 | pp.substitutions[pp.T] = 100 26 | pp.cost = 1./pp.eta + pp.Q/(1000.*units("N*m")) + p.T_m/(1000*units('N')) 27 | sol = pp.localsolve(iteration_limit = 400) 28 | 29 | 30 | def test(): 31 | "tests" 32 | simpleprop_test() 33 | ME_eta_test() 34 | if __name__ == "__main__": 35 | test() 36 | 37 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/prop/propeller.py: -------------------------------------------------------------------------------- 1 | " propeller model " 2 | from numpy import pi 3 | from gpkit import Model, Variable,Vectorize,parse_variables, SignomialsEnabled, SignomialEquality 4 | from gpkit.constraints.tight import Tight as TCS 5 | from gpfit.fit_constraintset import XfoilFit 6 | import os 7 | import pandas as pd 8 | 9 | class ActuatorProp(Model): 10 | """ Propeller Model 11 | 12 | Variables 13 | --------- 14 | T [lbf] thrust 15 | Tc [-] coefficient of thrust 16 | etaadd .7 [-] swirl and nonuniformity losses 17 | etav .85 [-] viscous losses 18 | etai [-] inviscid losses 19 | eta [-] overall efficiency 20 | z1 self.helper [-] efficiency helper 1 21 | z2 [-] efficiency helper 2 22 | lam [-] advance ratio 23 | CT [-] thrust coefficient 24 | CP [-] power coefficient 25 | Q [N*m] torque 26 | omega [rpm] propeller rotation rate 27 | omega_max 10000 [rpm] max rotation rate 28 | P_shaft [kW] shaft power 29 | M_tip .5 [-] Tip mach number 30 | a 295 [m/s] Speed of sound at altitude 31 | """ 32 | 33 | def helper(self, c): 34 | return 2. - 1./c(self.etaadd) 35 | 36 | @parse_variables(__doc__, globals()) 37 | def setup(self, static, state): 38 | V = state.V 39 | rho = state.rho 40 | R = static.R 41 | 42 | constraints = [eta <= etav*etai, 43 | Tc >= T/(0.5*rho*V**2*pi*R**2), 44 | z2 >= Tc + 1, 45 | etai*(z1 + z2**0.5/etaadd) <= 2, 46 | lam >= V/(omega*R), 47 | CT >= Tc*lam**2, 48 | CP <= Q*omega/(.5*rho*(omega*R)**3*pi*R**2), 49 | eta >= CT*lam/CP, 50 | omega <= omega_max, 51 | P_shaft == Q*omega, 52 | (M_tip*a)**2 >= (omega*R)**2 + V**2, 53 | static.T_m >= T 54 | ] 55 | return constraints 56 | 57 | 58 | class Propeller(Model): 59 | """ Propeller Model 60 | 61 | Variables 62 | --------- 63 | R [ft] prop radius 64 | W [lbf] prop weight 65 | K 4e-4 [1/ft^2] prop weight scaling factor 66 | T_m [lbf] prop max static thrust 67 | 68 | Variables of length N 69 | --------------------- 70 | c [ft] prop chord 71 | """ 72 | 73 | flight_model = ActuatorProp 74 | 75 | @parse_variables(__doc__, globals()) 76 | def setup(self, N=5): 77 | self.N = N 78 | return [W >= K*T_m*R**2] 79 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/README.md: -------------------------------------------------------------------------------- 1 | # Aircraft Empennage 2 | 3 | The empennage adds both weight and drag to each aircraft. The Empennage model in `empennage.py` creates 3 models: TailBoom, HorizontalTail, and VerticalTail. Each component has its own separate bending and aerodynamic models. 4 | 5 | ## TailBoom 6 | 7 | The tail boom has a diameter $d$, root wall thickness $t_0$, root moment of inertia $I_0$, modulus $E$, and density $\rho_{\text{cfrp}} = 1.6$ [g/cm$^3$], and length $l_{\text{h}}$. 8 | The total mass and root bending inertia are imposed in the optimization model as 9 | \begin{align} 10 | m &\geq \pi \rho_{\text{cfrp}} t_0 d l_{\text{h}} \left( 1 - \frac{1}{2} k\right) \\ 11 | I_0 &\leq \pi t_0 d^3/8 12 | \end{align} 13 | 14 | where the index $k=0$ corresponds to a uniform wall thickness and stiffness, and $k=1$ corresponds to a linear drop-off to zero. For both the solar-electric and gas powered aircraft $k=0.8$ is assumed. 15 | When the tail boom is loaded at the endpoint $x=l_{\text{h}}$, by the horizontal tail lift $L_{\text{h}}$, the end deflection angle follows from standard beam analysis 16 | 17 | \begin{align} 18 | \label{e:boomdefl} 19 | \theta &\geq \frac{L_{\text{h}} l_{\text{h}}^2}{EI_0} \frac{1+k}{2} \\ 20 | L_{\text{h}} &= \frac{1}{2} C_{L_{\text{h}}} \rho V^2 S_{\text{h}}. 21 | \end{align} 22 | 23 | ## HorizontalTail 24 | 25 | The horizontal tail can be sized to satisfy a horizontal tail volume coefficient condition, $V_{\text{h}} = 0.45$, 26 | 27 | \begin{equation} 28 | V_{\text{h}} = \frac{S_{\text{h}}l_{\text{h}}}{Sc} 29 | \end{equation} 30 | 31 | ## VerticalTail 32 | 33 | The vertical tail is sized to meet a conservative tail volume coefficient, $V_{\text{v}}= 0.04$, 34 | 35 | \begin{equation} 36 | \label{e:vtv} 37 | V_{\text{v}} = \frac{S_{\text{v}}}{S} \frac{l_{\text{v}}}{b} 38 | \end{equation} 39 | 40 | where $l_{\text{v}}$ is the vertical tail moment arm, assumed to be equal to the horizontal tail moment arm, $l_{\text{v}} = l_{\text{h}}$. 41 | 42 | ## Both Tails 43 | 44 | Both the horizontal and vertical tails are assumed to have a carbon fiber skin and solid foam interior where their respective densities are $\rho_{A_{\text{cfrp}}} = 0.049$ [g/cm$^2$], $\rho_{\text{foam}} = 1.5$ [lbf/ft$^3$]. 45 | The weight of the horizontal and vertical tails is 46 | 47 | \begin{align} 48 | \label{e:htweight} 49 | W_{\text{h}}/m_{\text{fac}} &= \rho_{\text{foam}} \frac{S_{\text{h}}^2}{b_{\text{h}}} \bar{A} + g\rho_{A_{\text{cfrp}}} S_{\text{h}} \\ 50 | \label{e:vtweight} 51 | W_{\text{v}}/m_{\text{fac}} &= \rho_{\text{foam}} \frac{S_{\text{v}}^2}{b_{\text{v}}} \bar{A} + g\rho_{A_{\text{cfrp}}} S_{\text{v}} 52 | \end{align} 53 | 54 | where $b_{\text{h}}$ and $b_{\text{v}}$ are the spans of the horizontal and vertical tails respectively and $\bar{A}$ is the cross sectional area of the NACA 0008 airfoil. The margin factor $m_{\text{fac}}=1.1$, is included to account for control surfaces, attachment joints, actuators, etc. 55 | 56 | ## TailBoomAero 57 | 58 | The drag of the tail boom is calculated using a turbulent flat plate model, 59 | 60 | \begin{align} 61 | \label{e:boomdrag} 62 | D_{\text{boom}} &\geq \frac{1}{2} C_f \rho V^2 l_{\text{h}}\pi d \\ 63 | C_f &\geq \frac{0.445}{Re_{\text{boom}}^{0.3}} \\ 64 | Re_{\text{boom}} &= \frac{V\rho l_{\text{h}}}{\mu} 65 | \end{align} 66 | 67 | ## TailAero 68 | 69 | The drag of the horizontal and vertical tails is computed using a GP-compatible fit of XFOIL data for a range of Reynolds numbers and NACA airfoil thicknesses, 70 | 71 | \begin{align} 72 | D_{\text{h}} &\geq \frac{1}{2} c_{d_{\text{h}}} \rho V^2 S_{\text{h}} \\ 73 | D_{\text{v}} &\geq \frac{1}{2} c_{d_{\text{v}}} \rho V^2 S_{\text{v}} \\ 74 | \label{e:taildrag} 75 | c_{d_{\text{(v,h)}}}^{70.5599} &\geq \num{7.42688d-90} \left( \frac{Re_{\text{(v,h)}}}{\num{1d3}} \right)^{-33.0637}(100 \tau_{\text{(v,h)}})^{18.0419} \nonumber \\ 76 | & + \num{5.02826d-163}\left(\frac{Re_{\text{(v,h)}}}{\num{1d3}}\right)^{-18.7959} (100\tau_{\text{(v,h)}})^{53.1879} \\ 77 | &+ \num{4.22901d-77}\left(\frac{Re_{\text{(v,h)}}}{\num{1d3}}\right)^{-41.1704} (100\tau_{\text{(v,h)}})^{28.4609} \nonumber \\ 78 | Re_{\text{v}} &= \frac{V\rho S_{\text{v}}/b_{\text{v}}}{\mu} \\ 79 | Re_{\text{h}} &= \frac{V\rho S_{\text{h}}/b_{\text{h}}}{\mu} 80 | \end{align} 81 | 82 | where the selected airfoil is the NACA 0008 for both the horizontal and vertical tails (i.e. $\tau_{\text{(v,h)}} = 0.08$). 83 | The XFOIL data was generated for a zero angle of attack, based upon steady level flight where neither surface is generating lift. 84 | A comparison of the XFOIL data and Equation~\eqref{e:taildrag} is shown in Figure~\ref{f:taildragpolar}. 85 | 86 | ![Thickness and Re fit for CL=0](taildragpolar.pdf) 87 | 88 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/tail/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/empennage.py: -------------------------------------------------------------------------------- 1 | " empennage.py " 2 | from gpkit import Model, parse_variables 3 | from .horizontal_tail import HorizontalTail 4 | from .vertical_tail import VerticalTail 5 | from .tail_boom import TailBoom, TailBoomState 6 | 7 | #pylint: disable=attribute-defined-outside-init, no-member, exec-used 8 | #pylint: disable=too-many-instance-attributes, invalid-name, undefined-variable 9 | class Empennage(Model): 10 | """empennage model, consisting of vertical, horizontal and tailboom 11 | 12 | Variables 13 | --------- 14 | mfac 1.0 [-] tail weight margin factor 15 | W [lbf] empennage weight 16 | 17 | SKIP VERIFICATION 18 | 19 | Upper Unbounded 20 | --------------- 21 | W, vtail.Vv, htail.Vh, tailboom.cave (if not tbSecWeight) 22 | htail.planform.tau (if not hSparModel) 23 | vtail.planform.tau (if not vSparModel) 24 | 25 | Lower Unbounded 26 | --------------- 27 | htail.lh, htail.Vh, htail.planform.b, htail.mh 28 | vtail.lv, vtail.Vv, vtail.planform.b 29 | htail.planform.tau (if not hSparModel) 30 | vtail.planform.tau (if not vSparModel) 31 | htail.spar.Sy (if hSparModel), htail.spar.J (if hSparModel) 32 | vtail.spar.Sy (if vSparModel), vtail.spar.J (if vSparModel) 33 | tailboom.Sy, tailboom.cave (if not tbSecWeight), tailboom.J (if tbSecWeight) 34 | 35 | LaTex Strings 36 | ------------- 37 | mfac m_{\\mathrm{fac}} 38 | 39 | """ 40 | @parse_variables(__doc__, globals()) 41 | def setup(self, N=2): 42 | self.htail = HorizontalTail() 43 | self.hSparModel = self.htail.sparModel 44 | self.htail.substitutions.update({self.htail.mfac: 1.1}) 45 | lh = self.lh = self.htail.lh 46 | self.vtail = VerticalTail() 47 | self.vSparModel = self.vtail.sparModel 48 | self.vtail.substitutions.update({self.vtail.mfac: 1.1}) 49 | lv = self.lv = self.vtail.lv 50 | self.tailboom = TailBoom(N=N) 51 | self.tbSecWeight = self.tailboom.secondaryWeight 52 | self.components = [self.htail, self.vtail, self.tailboom] 53 | l = self.l = self.tailboom.l 54 | 55 | constraints = [ 56 | W/mfac >= sum(c.W for c in self.components), 57 | l >= lh, l >= lv, 58 | ] 59 | 60 | return self.components, constraints 61 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/horizontal_tail.py: -------------------------------------------------------------------------------- 1 | " horizontal tail " 2 | import numpy as np 3 | from gpkit import parse_variables 4 | from .tail_aero import TailAero 5 | from gpkitmodels.GP.aircraft.wing.wing import Wing 6 | from gpkitmodels.GP.aircraft.wing.wing_skin import WingSkin 7 | from gpkitmodels.GP.aircraft.wing.wing_core import WingCore 8 | 9 | #pylint: disable=attribute-defined-outside-init, no-member 10 | #pylint: disable=exec-used, undefined-variable 11 | 12 | class HorizontalTail(Wing): 13 | """ Horizontal Tail Model 14 | 15 | Variables 16 | --------- 17 | Vh [-] horizontal tail volume coefficient 18 | lh [ft] horizontal tail moment arm 19 | CLhmin 0.75 [-] max downlift coefficient 20 | mh [-] horizontal tail span effectiveness 21 | 22 | Upper Unbounded 23 | --------------- 24 | lh, Vh, W, planform.tau (if not sparModel) 25 | 26 | Lower Unbounded 27 | --------------- 28 | lh, Vh, planform.b, mh, planform.tau (if not sparModel) 29 | spar.Sy (if sparModel), spar.J (if sparJ) 30 | 31 | LaTex Strings 32 | ------------- 33 | Vh V_{\\mathrm{h}} 34 | lh l_{\\mathrm{h}} 35 | CLmin C_{L_{\\mathrm{min}}} 36 | mh m_{\\mathrm{h}} 37 | 38 | """ 39 | flight_model = TailAero 40 | fillModel = WingCore 41 | sparModel = None 42 | 43 | @parse_variables(__doc__, globals()) 44 | def setup(self, N=3): 45 | self.ascs = Wing.setup(self, N) 46 | self.planform.substitutions.update( 47 | {self.planform.AR: 4, self.planform.lam: 0.8}) 48 | if self.fillModel: 49 | self.foam.substitutions.update({self.foam.Abar: 0.0548, 50 | self.foam.material.rho: 0.024}) 51 | 52 | return self.ascs, mh*(1+2.0/self.planform["AR"]) <= 2*np.pi 53 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tail_aero.py: -------------------------------------------------------------------------------- 1 | " tail aerodynamics " 2 | import os 3 | import pandas as pd 4 | from gpkit import Model, parse_variables 5 | from gpfit.fit_constraintset import XfoilFit 6 | 7 | #pylint: disable=exec-used, attribute-defined-outside-init, undefined-variable 8 | #pylint: disable=no-member 9 | 10 | class TailAero(Model): 11 | """Tail Aero Model 12 | 13 | Variables 14 | --------- 15 | Re [-] Reynolds number 16 | Cd [-] drag coefficient 17 | 18 | Upper Unbounded 19 | --------------- 20 | Cd, Re, S, V, b, rho 21 | 22 | Lower Unbounded 23 | --------------- 24 | S, tau, V, b, rho 25 | 26 | LaTex Strings 27 | ------------- 28 | Cd C_d 29 | 30 | """ 31 | @parse_variables(__doc__, globals()) 32 | def setup(self, static, state): 33 | self.state = state 34 | 35 | cmac = self.cmac = static.planform.cmac 36 | b = self.b = static.planform.b 37 | S = self.S = static.planform.S 38 | tau = self.tau = static.planform.tau 39 | rho = self.rho = state.rho 40 | V = self.V = state.V 41 | mu = self.mu = state.mu 42 | path = os.path.dirname(__file__) 43 | fd = pd.read_csv(path + os.sep + "tail_dragfit.csv").to_dict( 44 | orient="records")[0] 45 | 46 | constraints = [ 47 | Re == V*rho*S/b/mu, 48 | # XfoilFit(fd, Cd, [Re, static["\\tau"]], 49 | # err_margin="RMS", airfoil="naca 0008") 50 | XfoilFit(fd, Cd, [Re, tau], err_margin="RMS") 51 | ] 52 | 53 | return constraints 54 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tail_boom.py: -------------------------------------------------------------------------------- 1 | " tail boom model " 2 | from numpy import pi 3 | from gpkit import Model, parse_variables, Variable, VectorVariable, units 4 | from .tube_spar import TubeSpar 5 | from gpkitmodels.GP.beam.beam import Beam 6 | from gpkitmodels import g 7 | 8 | #pylint: disable=exec-used, undefined-variable, invalid-name 9 | #pylint: disable=attribute-defined-outside-init 10 | 11 | class TailBoomAero(Model): 12 | """ Tail Boom Aero Model 13 | 14 | Variables 15 | --------- 16 | Cf [-] tail boom skin friction coefficient 17 | Re [-] tail boom reynolds number 18 | 19 | Upper Unbounded 20 | --------------- 21 | Re, Cf, l, V, rho 22 | 23 | Lower Unbounded 24 | --------------- 25 | l, V, rho 26 | 27 | LaTex Strings 28 | ------------- 29 | Cf C_f 30 | 31 | """ 32 | @parse_variables(__doc__, globals()) 33 | def setup(self, static, state): 34 | self.state = state 35 | 36 | l = self.l = static.l 37 | rho = self.rho = state.rho 38 | V = self.V = state.V 39 | mu = self.mu = state.mu 40 | 41 | return [Re == V*rho*l/mu, 42 | Cf >= 0.455/Re**0.3, 43 | ] 44 | 45 | class TailBoomState(Model): 46 | """ Tail Boom Loading State 47 | 48 | Variables 49 | --------- 50 | rhosl 1.225 [kg/m^3] air density at sea level 51 | Vne 40 [m/s] never exceed vehicle speed 52 | 53 | LaTex Strings 54 | ------------- 55 | rhosl \\rho_{\\mathrm{sl}} 56 | Vne V_{\\mathrm{NE}} 57 | 58 | """ 59 | @parse_variables(__doc__, globals()) 60 | def setup(self): 61 | pass 62 | 63 | 64 | class VerticalBoomTorsion(Model): 65 | """ Tail Boom Torsion from Vertical Tail 66 | 67 | Variables 68 | --------- 69 | T [N*m] vertical tail moment 70 | taucfrp 210 [MPa] torsional stress limit of carbon 71 | 72 | Upper Unbounded 73 | --------------- 74 | J 75 | 76 | Lower Unbounded 77 | --------------- 78 | d0, b, S 79 | 80 | LaTex Strings 81 | ------------- 82 | taucfrp \\tau_{\\mathrm{CFRP}} 83 | 84 | """ 85 | @parse_variables(__doc__, globals()) 86 | def setup(self, tailboom, vtail, state): 87 | J = self.J = tailboom.J 88 | d0 = self.d0 = tailboom.d 89 | b = self.b = vtail.planform.b 90 | S = self.S = vtail.planform.S 91 | rhosl = self.rhosl = state.rhosl 92 | Vne = self.Vne = state.Vne 93 | CLmax = vtail.planform.CLmax 94 | 95 | return [T >= 0.5*rhosl*Vne**2*S*CLmax*b, 96 | taucfrp >= T*d0/2/J 97 | ] 98 | 99 | class TailBoomBending(Model): 100 | """ Tail Boom Bending 101 | 102 | Variables 103 | --------- 104 | F [N] tail force 105 | th [-] tail boom deflection angle 106 | kappa 0.1 [-] max tail boom deflection 107 | Nsafety 1.0 [-] safety load factor 108 | 109 | Variables of length tailboom.N-1 110 | -------------------------------- 111 | Mr [N*m] section root moment 112 | 113 | 114 | Upper Unbounded 115 | --------------- 116 | tailboom.I0, tailboom.Sy 117 | tailboom.J (if tailboomJ), tailboom.I 118 | 119 | Lower Unbounded 120 | --------------- 121 | htail.planform.S, htail.planform.CLmax 122 | tailboom.l, tailboom.deta 123 | state.qne 124 | 125 | LaTex Strings 126 | ------------- 127 | th \\theta 128 | thmax \\theta_{\\mathrm{max}} 129 | 130 | """ 131 | @parse_variables(__doc__, globals()) 132 | def setup(self, tailboom, htail, state): 133 | N = self.N = tailboom.N 134 | self.state = state 135 | self.htail = htail 136 | self.tailboom = tailboom 137 | 138 | Beam.qbarFun = [1e-10]*N 139 | Beam.SbarFun = [1.]*N 140 | beam = self.beam = Beam(N) 141 | 142 | I = tailboom.I 143 | tailboom.I0 = I[0] 144 | l = tailboom.l 145 | S = htail.planform.S 146 | E = tailboom.material.E 147 | Sy = tailboom.Sy 148 | qne = state.qne 149 | CLmax = htail.planform.CLmax 150 | deta = tailboom.deta 151 | sigma = tailboom.material.sigma 152 | 153 | constraints = [beam.dx == deta, 154 | F >= qne*S, 155 | beam["\\bar{EI}"] <= E*I/F/l**2/2, 156 | Mr >= beam["\\bar{M}"][:-1]*F*l, 157 | sigma >= Mr/Sy, 158 | th == beam["\\theta"][-1], 159 | beam["\\bar{\\delta}"][-1]*CLmax*Nsafety <= kappa] 160 | 161 | self.tailboomJ = hasattr(tailboom, "J") 162 | if self.tailboomJ: 163 | constraints.append(tailboom.J >= 1e-10*units("m^4")) 164 | 165 | return constraints, beam 166 | 167 | class TailBoom(TubeSpar): 168 | """ Tail Boom Model 169 | 170 | Variables 171 | --------- 172 | l [ft] tail boom length 173 | S [ft^2] tail boom surface area 174 | b [ft] twice tail boom length 175 | deta 1./(N-1) [-] normalized segment length 176 | tau 1.0 [-] thickness to width ratio 177 | rhoA 0.15 [kg/m^2] total aerial density 178 | 179 | Variables of length N-1 180 | ----------------------- 181 | cave [in] average segment width 182 | 183 | """ 184 | 185 | flight_model = TailBoomAero 186 | tailLoad = TailBoomBending 187 | secondaryWeight = None 188 | 189 | @parse_variables(__doc__, globals()) 190 | def setup(self, N=5): 191 | self.N = N 192 | self.spar = super(TailBoom, self).setup(N, self) 193 | 194 | if self.secondaryWeight: 195 | self.weight.right += rhoA*g*S 196 | 197 | d0 = self.d0 = self.d[0] 198 | 199 | return self.spar, [S == l*pi*d0, b == 2*l] 200 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tail_dragfit.csv: -------------------------------------------------------------------------------- 1 | ub0,ub1,rms_err,e31,e30,e11,e10,ftype,c4,lb1,lb0,K,a1,c3,c2,c1,c0,e21,e40,e41,e20,d,e00,e01,max_err 2 | 999999.99999999953,0.14999999999999999,0.040701846058073914,0.46746638912526689,-0.4823469575592283,0.2463415216530887,-0.48486661931969488,MA,218.73655005090146,0.05000000000000001,19999.999999999982,5,1,9.5091938064942685,16.259467895292175,5.4468646580249409,0.33993775619145372,0.46638445457840788,-0.60387089537858818,1.312443752168466,-0.53994320248315575,2,-0.18199062784771394,0.77460393316522413,0.19620270014169217 3 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tail_tests.py: -------------------------------------------------------------------------------- 1 | " test tail models " 2 | from gpkitmodels.GP.aircraft.tail.horizontal_tail import HorizontalTail 3 | from gpkitmodels.GP.aircraft.tail.vertical_tail import VerticalTail 4 | from gpkitmodels.GP.aircraft.tail.empennage import Empennage 5 | from gpkitmodels.GP.aircraft.wing.wing_test import FlightState 6 | from gpkitmodels.GP.aircraft.wing.boxspar import BoxSpar 7 | from gpkitmodels.GP.aircraft.tail.tail_boom import TailBoom 8 | from gpkit import Model, Variable, units 9 | 10 | #pylint: disable=no-member 11 | 12 | def test_htail(): 13 | 14 | Sw = Variable("S_w", 50, "ft**2", "wing area") 15 | cmac = Variable("cmac", 15, "in", "wing MAC") 16 | ht = HorizontalTail() 17 | fs = FlightState() 18 | ht.substitutions.update({ht.W: 5, ht.mh: 0.01, ht.planform.AR: 4, 19 | ht.Vh: 0.5, ht.lh: 10, ht.planform.tau: 0.08}) 20 | perf = ht.flight_model(ht, fs) 21 | 22 | m = Model(perf.Cd, 23 | [ht.Vh <= ht.planform.S*ht.lh/Sw/cmac, ht, fs, perf]) 24 | m.solve(verbosity=0) 25 | 26 | def test_vtail(): 27 | 28 | Sw = Variable("S_w", 50, "ft**2", "wing area") 29 | bw = Variable("b_w", 20, "ft", "wing span") 30 | vt = VerticalTail() 31 | fs = FlightState() 32 | vt.substitutions.update({vt.W: 5, vt.planform.AR: 3, vt.Vv: 0.04, 33 | vt.lv: 10, vt.planform.tau: 0.08}) 34 | perf = vt.flight_model(vt, fs) 35 | 36 | m = Model(perf.Cd, [vt.Vv <= vt.planform.S*vt.lv/Sw/bw, vt, fs, perf]) 37 | m.solve(verbosity=0) 38 | 39 | def test_emp(): 40 | 41 | Sw = Variable("S_w", 50, "ft**2", "wing area") 42 | bw = Variable("b_w", 20, "ft", "wing span") 43 | cmac = Variable("cmac", 15, "in", "wing MAC") 44 | emp = Empennage() 45 | fs = FlightState() 46 | emp.substitutions.update({emp.W: 10, emp.tailboom.l: 5, 47 | emp.htail.planform.AR: 4, 48 | emp.vtail.planform.AR: 4, 49 | emp.htail.planform.tau: 0.08, 50 | emp.vtail.planform.tau: 0.08, 51 | emp.vtail.Vv: 0.04, 52 | emp.htail.Vh: 0.4, 53 | emp.htail.mh: 0.01}) 54 | htperf = emp.htail.flight_model(emp.htail, fs) 55 | vtperf = emp.vtail.flight_model(emp.vtail, fs) 56 | tbperf = emp.tailboom.flight_model(emp.tailboom, fs) 57 | hbend = emp.tailboom.tailLoad(emp.tailboom, emp.htail, fs) 58 | vbend = emp.tailboom.tailLoad(emp.tailboom, emp.vtail, fs) 59 | 60 | m = Model(htperf.Cd + vtperf.Cd + tbperf.Cf, 61 | [emp.vtail.lv == emp.tailboom.l, emp.htail.lh == emp.tailboom.l, 62 | emp.htail.Vh <= emp.htail.planform.S*emp.htail.lh/Sw/cmac, 63 | emp.vtail.Vv <= emp.vtail.planform.S*emp.vtail.lv/Sw/bw, 64 | fs, emp, fs, htperf, vtperf, tbperf, hbend, vbend]) 65 | 66 | from gpkit import settings 67 | if settings["default_solver"] == "cvxopt": 68 | for l in [hbend, vbend]: 69 | for v in ["\\bar{M}_{tip}", "\\bar{\\delta}_{root}", 70 | "\\theta_{root}"]: 71 | m.substitutions[l[v]] = 1e-3 72 | 73 | m.solve(verbosity=0, use_leqs=False) # cvxopt gets singular with leqs 74 | 75 | def test_tailboom_mod(): 76 | 77 | Sw = Variable("S_w", 50, "ft**2", "wing area") 78 | bw = Variable("b_w", 20, "ft", "wing span") 79 | cmac = Variable("cmac", 15, "in", "wing MAC") 80 | cmax = Variable("cmax", 5, "in", "max width") 81 | TailBoom.__bases__ = (BoxSpar,) 82 | TailBoom.secondaryWeight = True 83 | emp = Empennage(N=5) 84 | fs = FlightState() 85 | emp.substitutions.update({emp.W: 10, emp.tailboom.l: 5, 86 | emp.htail.planform.AR: 4, 87 | emp.vtail.planform.AR: 4, 88 | emp.htail.planform.tau: 0.08, 89 | emp.vtail.planform.tau: 0.08, 90 | emp.vtail.Vv: 0.04, 91 | emp.htail.Vh: 0.4, 92 | emp.htail.mh: 0.01, 93 | emp.tailboom.wlim: 1}) 94 | htperf = emp.htail.flight_model(emp.htail, fs) 95 | vtperf = emp.vtail.flight_model(emp.vtail, fs) 96 | tbperf = emp.tailboom.flight_model(emp.tailboom, fs) 97 | hbend = emp.tailboom.tailLoad(emp.tailboom, emp.htail, fs) 98 | vbend = emp.tailboom.tailLoad(emp.tailboom, emp.vtail, fs) 99 | 100 | m = Model(htperf.Cd + vtperf.Cd + tbperf.Cf, 101 | [emp.vtail.lv == emp.tailboom.l, emp.htail.lh == emp.tailboom.l, 102 | emp.htail.Vh <= emp.htail.planform.S*emp.htail.lh/Sw/cmac, 103 | emp.vtail.Vv <= emp.vtail.planform.S*emp.vtail.lv/Sw/bw, 104 | emp.tailboom.cave <= cmax, 105 | emp, fs, htperf, vtperf, tbperf, hbend, vbend]) 106 | 107 | from gpkit import settings 108 | if settings["default_solver"] == "cvxopt": 109 | for l in [hbend, vbend]: 110 | for v in ["\\bar{M}_{tip}", "\\bar{\\delta}_{root}", 111 | "\\theta_{root}"]: 112 | m.substitutions[l[v]] = 1e-3 113 | 114 | m.solve(verbosity=0) 115 | 116 | def test(): 117 | test_htail() 118 | test_vtail() 119 | test_emp() 120 | test_tailboom_mod() 121 | 122 | if __name__ == "__main__": 123 | test() 124 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/taildragpolar.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/tail/taildragpolar.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tailpolars/genpolar.sh: -------------------------------------------------------------------------------- 1 | NACA=$1 2 | POLARFILE=naca$1.cl0.Re$2k.pol 3 | 4 | if [ -f $POLARFILE ] ; then 5 | echo "yes" 6 | rm $POLARFILE 7 | fi 8 | 9 | xfoil << EOF 10 | naca $1 11 | oper 12 | v $2e3 13 | pacc 14 | $POLARFILE 15 | 16 | iter 200 17 | cl 0.0 18 | 19 | quit 20 | EOF 21 | 22 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tailpolars/naca_cl0fits.py: -------------------------------------------------------------------------------- 1 | "naca_polarfits.py" 2 | from builtins import zip 3 | from builtins import range 4 | import numpy as np 5 | import pandas as pd 6 | import matplotlib.pyplot as plt 7 | plt.rcParams.update({'font.size':15}) 8 | 9 | def text_to_df(filename): 10 | "parse XFOIL polars and concatente data in DataFrame" 11 | lines = list(open(filename)) 12 | for i, l in enumerate(lines): 13 | lines[i] = l.split("\n")[0] 14 | for j in 10-np.arange(9): 15 | if " "*j in lines[i]: 16 | lines[i] = lines[i].replace(" "*j, " ") 17 | if "---" in lines[i]: 18 | start = i 19 | data = {} 20 | titles = lines[start-1].split(" ")[1:] 21 | for t in titles: 22 | data[t] = [] 23 | 24 | for l in lines[start+1:]: 25 | for i, v in enumerate(l.split(" ")[1:]): 26 | data[titles[i]].append(v) 27 | 28 | df = pd.DataFrame(data) 29 | df = df.astype(float) 30 | return df 31 | 32 | def fit_setup(naca_range, re_range): 33 | "set up x and y parameters for gp fitting" 34 | tau = [[float(n)]*len(re_range) for n in naca_range] 35 | re = [re_range]*len(naca_range) 36 | cd = [] 37 | for n in naca_range: 38 | for r in re_range: 39 | dataf = text_to_df("naca%s.cl0.Re%dk.pol" % (n, r)) 40 | cd.append(dataf["CD"]) 41 | 42 | 43 | u1 = np.hstack(re) 44 | u2 = np.hstack(tau) 45 | w = np.hstack(cd) 46 | u1 = u1.astype(np.float) 47 | u2 = u2.astype(np.float) 48 | w = w.astype(np.float) 49 | u = [u1, u2] 50 | x = np.log(u) 51 | y = np.log(w) 52 | return x, y 53 | 54 | def return_fit(u_1, u_2): 55 | "naca tau and reynolds fit" 56 | w = (7.42688e-90 * (u_1)**-33.0637 * (u_2)**18.0419 57 | + 5.02826e-163 * (u_1)**-18.7959 * (u_2)**53.1879 58 | + 4.22901e-77 * (u_1)**-41.1704 * (u_2)**28.4609)**(1/70.5599) 59 | # SMA function, K=3, max RMS error = 0.0173 60 | return w 61 | 62 | def plot_fits(naca_range, re_range): 63 | "plot fit compared to data" 64 | 65 | fig, ax = plt.subplots() 66 | colors = ["k", "m", "b", "g", "y", "r"] 67 | assert len(colors) == len(naca_range) 68 | res = np.linspace(re_range[0], re_range[-1], 50) 69 | for n, col in zip(naca_range, colors): 70 | cd = [] 71 | for r in re_range: 72 | dataf = text_to_df("naca%s.cl0.Re%dk.pol" % (n, r)) 73 | cd.append(dataf["CD"]) 74 | if True in [c.empty for c in cd]: 75 | i = [c.empty for c in cd].index(True) 76 | cd[i] = (cd[i-1] + cd[i+1])/2 77 | ax.plot(re_range, cd, "o", mec=col, mfc="None", mew=1.5) 78 | w = return_fit(res, float(n)) 79 | ax.plot(res, w, c=col, label="NACA %s" % n, lw=2) 80 | ax.legend(fontsize=15) 81 | labels = ["k" + item.get_text() for item in ax.get_xticklabels()] 82 | labels = ["%dk" % l for l in np.linspace(200, 900, len(labels))] 83 | ax.set_xticklabels(labels) 84 | ax.set_xlabel("$Re$") 85 | ax.set_ylabel("$c_{dp}$") 86 | ax.grid() 87 | return fig, ax 88 | 89 | if __name__ == "__main__": 90 | Re = list(range(200, 950, 50)) 91 | NACA = ["0005", "0008", "0009", "0010", "0015", "0020"] 92 | X, Y = fit_setup(NACA, Re) # call fit(X, Y, 4, "SMA") to get fit 93 | F, A = plot_fits(NACA, Re) 94 | F.savefig("taildragpolar.pdf", bbox_inches="tight") 95 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tailpolars/nacasweep.sh: -------------------------------------------------------------------------------- 1 | NACA="0005 0008 0009 0010 0015 0020" 2 | Re="200 250 300 350 400 450 500 550 600 650 700 750 800 850 900" 3 | 4 | for r in $Re 5 | do 6 | for n in $NACA 7 | do 8 | ./genpolar.sh $n $r 9 | done 10 | done 11 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/tube_spar.py: -------------------------------------------------------------------------------- 1 | " tube spar " 2 | from numpy import pi 3 | from gpkitmodels.GP.materials import cfrpfabric 4 | from gpkitmodels import g 5 | from gpkit import Model, parse_variables 6 | 7 | class TubeSpar(Model): 8 | """ Tail Boom Model 9 | 10 | Variables 11 | --------- 12 | mfac 1.0 [-] weight margin factor 13 | k 0.8 [-] taper index 14 | kfac self.minusk2 [-] (1-k/2) 15 | W [lbf] spar weight 16 | 17 | Variables of length N-1 18 | ----------------------- 19 | I [m^4] moment of inertia 20 | d [in] diameter 21 | t [in] thickness 22 | dm [kg] segment mass 23 | Sy [m^3] section modulus 24 | 25 | Upper Unbounded 26 | --------------- 27 | W 28 | 29 | Lower Unbounded 30 | --------------- 31 | J, l, I0 32 | 33 | LaTex Strings 34 | ------------- 35 | kfac (1-k/2) 36 | mfac m_{\\mathrm{fac}} 37 | 38 | """ 39 | 40 | minusk2 = lambda self, c: 1-c(self.k)/2. 41 | material = cfrpfabric 42 | 43 | @parse_variables(__doc__, globals()) 44 | def setup(self, N, surface): 45 | deta = surface.deta 46 | tmin = self.material.tmin 47 | rho = self.material.rho 48 | l = surface.l 49 | 50 | self.weight = W/mfac >= g*dm.sum() 51 | 52 | return [I <= pi*t*d**3/8.0, 53 | Sy <= 2*I/d, 54 | dm >= pi*rho*d*deta*t*kfac*l, 55 | self.weight, 56 | t >= tmin] 57 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/tail/vertical_tail.py: -------------------------------------------------------------------------------- 1 | " vertical tail " 2 | from gpkit import parse_variables 3 | from .tail_aero import TailAero 4 | from gpkitmodels.GP.aircraft.wing.wing import Wing 5 | from gpkitmodels.GP.aircraft.wing.wing_core import WingCore 6 | from gpkitmodels.GP.aircraft.wing.wing_skin import WingSkin 7 | 8 | #pylint: disable=attribute-defined-outside-init, no-member, exec-used 9 | 10 | class VerticalTail(Wing): 11 | """ Vertical Tail Model 12 | 13 | Variables 14 | --------- 15 | Vv [-] vertical tail volume coefficient 16 | lv [ft] vertical tail moment arm 17 | 18 | Upper Unbounded 19 | --------------- 20 | lv, Vv, W, planform.tau (if not sparModel) 21 | 22 | Lower Unbounded 23 | --------------- 24 | lv, Vv, planform.b, planform.tau (if not sparModel) 25 | spar.Sy (if sparModel), spar.J (if sparJ) 26 | 27 | LaTex Strings 28 | ------------- 29 | Vv V_{\\mathrm{v}} 30 | lv l_{\\mathrm{v}} 31 | 32 | """ 33 | 34 | flight_model = TailAero 35 | fillModel = WingCore 36 | sparModel = None 37 | 38 | @parse_variables(__doc__, globals()) 39 | def setup(self, N=3): 40 | self.ascs = Wing.setup(self, N) 41 | self.planform.substitutions.update( 42 | {self.planform.lam: 0.8, self.planform.AR: 4}) 43 | if self.fillModel: 44 | self.foam.substitutions.update({self.foam.Abar: 0.0548, 45 | self.foam.material.rho: 0.024}) 46 | 47 | return self.ascs 48 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/README.md: -------------------------------------------------------------------------------- 1 | # Wing Structural and Aero Models 2 | 3 | The wing GP model is mostly based off of Mark Drela's wing bending notes from MIT's [OpenCourseWare](https://ocw.mit.edu/courses/aeronautics-and-astronautics/16-01-unified-engineering-i-ii-iii-iv-fall-2005-spring-2006/systems-labs-06/spl10.pdf) site. Basic assumptions are a constant tapered wing, no sweep, bending loads are taken by the spar, torsional loads are taken by the skin. 4 | 5 | The usage is as follows: `Wing` in `wing.py` is created either by itself or in an aircraft model. It has two functions that return models to be created separately from `Wing`, `WingAero` and `WingLoading` (also in `wing.py`). `WingAero` is an erodynamic model of the JHO airfoil. $C_d$ is to be used in an overall aircraft aerodyanmic model. `WingLoading` creates loading models that govern the bending loads of the spar and the torsional loads of the skin. Inside `Wing` 2 objects are created `CapSpar` or `TubeSpar` and `WingSkin` with the option to create `WingInterior` if `hollow=True`. Their weights are summed to give an overall weight of the wing. Each submodel is described in detail. 6 | 7 | ## Wing 8 | 9 | Constrains basic shape of wing using 10 | 11 | $$ b^2 = SAR$$ 12 | $$\bar{c}(y) \equiv \frac{c(y)}{S/b} = \frac{2}{1+\lambda} \left( 1 + (\lambda - 1) \frac{2y}{b} \right) $$ 13 | 14 | ## WingAero 15 | 16 | Calculates the wing drag from induced drag and wing profile drag. Wing profile drag assumes the JHO airfoil and is calculated using a posynomial fit to XFOIL data at various reynolds numbers. 17 | 18 | ![JHO drag polars](jho1polarfit.py) 19 | 20 | ## CapSpar 21 | 22 | Set up constraints for size of spar. Spar thickness is assumed to be no greater than the max thickness of the airfoil. Width is no greater than %10 of the chord. Moment of inerita is calculated using first order approximation. 23 | 24 | ## ChordSparL 25 | 26 | Basic assumption is that the distributed load of lift and weight is proportional to the local chord. 27 | 28 | $$ q(y) \approx K_q c(y) $$ 29 | $$ K_q = \frac{N_{\text{max}}W_{\text{cent}}}{S} $$ 30 | 31 | This model takes as an input `CapSpar` and `Wcent`, or the center weight of the aircaft. Uses discretized beam theory to predict moment and deflection. Stress and overall wing deflection are constrainted. 32 | 33 | ## GustSparL 34 | 35 | The main assumption is the the loading is the same as the `ChordSparL` case but there is an additional gust term that varies as $1-\cos$ along the span. So the distributed load that is used as an input to the discretized beam model is 36 | 37 | $$\bar{q}(y) = \frac{q(y)b}{W_{\text{cent}}N_{\text{max}}} &\geq \bar{c}(y) \left[1 + \frac{c_{l_{\alpha}}}{C_L} \alpha_{\text{gust}} (y) \left(1 + \frac{W_{\text{wing}}}{W_{\text{cent}}} \right) \right] $$ 38 | 39 | where $\alpha_{\text{gust}}$ is 40 | 41 | $$ \alpha_{\text{gust}}(y) = \tan^{-1}\left(\frac{V_{\text{gust}}(y)}{V} 42 | \right). $$ 43 | 44 | and $V_{\text{gust}}$ is: 45 | 46 | $$V_{\text{gust}}(y) = V_{\text{ref}} \left(1-\cos\left(\frac{2y}{b} \frac{\pi}{2} \right) \right) $$ 47 | 48 | $\alpha_{\text{gust}}$ is approximated by a monomial fit on the range of gust velocities $V/V_{\text{gust}} \in [0, 0.7]$. 49 | 50 | ![gust loading diagram](gustloaddiagram.pdf) 51 | 52 | 53 | ## WingSkin 54 | 55 | The wing skin calculates the weight of the wing based off of the surface aera of the wing 56 | 57 | $$ W_{\text{skin}} \geq 2 \rho_{\text{cfrp}}} t S g. $$ 58 | 59 | ## WingSkinL 60 | 61 | The main assumptions here are that the wing has a constant thickness skin, the torsional loads are taken by the skin, and the highest torison occurs at the root. This model only has one constraint 62 | 63 | $$ \tau_{CFRP} \geq \frac{C_{m_w}S\rhoV_{NE}^2}{\bar{J/t} c_{root}^2 t} $$ 64 | 65 | The $\bar{J/t}$ is taken from the JHO airfoil. 66 | 67 | ## WingInterior 68 | 69 | If this model is enabled, the wing is assummed to be filled with foam. The cross sectional aera of the wing is assumed to be the JHO airfoil. 70 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/wing/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/arctan_fit.csv: -------------------------------------------------------------------------------- 1 | lb0,ub0,d,K,e00,a1,ftype,max_err,c0,rms_err 2 | 1.0000000000000013e-15,0.69999999999999996,1,1,0.99602497577104232,1,MA,0.082380875848525215,0.94604144923634659,0.039722989129247634 3 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/arctan_fit.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from gpfit.fit import fit 3 | import os 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import sys 7 | plt.rcParams.update({'font.size':15}) 8 | GENERATE = True 9 | 10 | def arctanfit(): 11 | u = np.linspace(1e-15, 0.7, 100) 12 | w = np.arctan(u) 13 | 14 | x = np.log(u) 15 | y = np.log(w) 16 | 17 | cn, err = fit(x, y, 1, "MA") 18 | rm = err 19 | print("RMS error: %.4f" % rm) 20 | 21 | yfit = cn.evaluate(x) 22 | df = cn.get_dataframe() 23 | fig, ax = plt.subplots() 24 | ax.plot(u, w, lw=2) 25 | ax.plot(u, np.exp(yfit), "--", lw=2) 26 | ax.set_xlim([0, 0.7]) 27 | ax.grid() 28 | ax.set_xlabel("$V_{\\mathrm{gust}}/V$") 29 | ax.set_ylabel("$\\alpha_{\\mathrm{gust}}$") 30 | ax.legend(["$\\arctan{(V_{\\mathrm{gust}}/V)}$", 31 | "$0.905 (V_{\\mathrm{gust}}/V)^{0.961}$"], loc=2, fontsize=15) 32 | return df, fig, ax 33 | 34 | if __name__ == "__main__": 35 | df, fig, ax = arctanfit() 36 | df.to_csv("arctan_fit.csv") 37 | fig.savefig("arctanfit.pdf", bbox_inches="tight") 38 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/boxspar.py: -------------------------------------------------------------------------------- 1 | " box spar " 2 | from gpkit import Model, parse_variables, SignomialsEnabled 3 | from .sparloading import SparLoading 4 | from .gustloading import GustL 5 | from gpkitmodels.GP.materials import cfrpud, cfrpfabric, foamhd 6 | from gpkitmodels import g 7 | 8 | #pylint: disable=exec-used, undefined-variable, unused-argument, invalid-name 9 | 10 | class BoxSpar(Model): 11 | """ Box Spar Model 12 | 13 | Scalar Variables 14 | ---------------- 15 | W [lbf] spar weight 16 | wlim 0.15 [-] spar width to chord ratio 17 | mfac 0.97 [-] curvature knockdown factor 18 | tcoret 0.02 [-] core to thickness ratio 19 | 20 | Variables of length N-1 21 | ----------------------- 22 | hin [in] height between caps 23 | I [m^4] spar x moment of inertia 24 | Sy [m^3] section modulus 25 | dm [kg] segment spar mass 26 | w [in] spar width 27 | d [in] cross sectional diameter 28 | t [in] spar cap thickness 29 | tshear [in] shear web thickness 30 | tcore [in] core thickness 31 | 32 | SKIP VERIFICATION 33 | 34 | Upper Unbounded 35 | --------------- 36 | W 37 | 38 | Lower Unbounded 39 | --------------- 40 | Sy, b, J, surface.deta 41 | 42 | LaTex Strings 43 | ------------- 44 | wlim w_{\\mathrm{lim}} 45 | mfac m_{\\mathrm{fac}} 46 | hin h_{\\mathrm{in}_i} 47 | I I_i 48 | Sy S_{y_i} 49 | dm \\Delta{m} 50 | w w_i 51 | t t_i 52 | tshear t_{\\mathrm{shear}_i} 53 | tcoret (t_{\\mathrm{core}}/t) 54 | 55 | """ 56 | loading = SparLoading 57 | gustloading = GustL 58 | material = cfrpud 59 | shearMaterial = cfrpfabric 60 | coreMaterial = foamhd 61 | 62 | @parse_variables(__doc__, globals()) 63 | def setup(self, N, surface): 64 | self.surface = surface 65 | 66 | b = self.b = surface.b 67 | cave = self.cave = surface.cave 68 | tau = self.tau = surface.tau 69 | deta = surface.deta 70 | rho = self.material.rho 71 | rhoshear = self.shearMaterial.rho 72 | rhocore = self.coreMaterial.rho 73 | tshearmin = self.shearMaterial.tmin 74 | tmin = self.material.tmin 75 | 76 | self.weight = W >= 2*dm.sum()*g 77 | 78 | constraints = [I/mfac <= w*t*hin**2, 79 | dm >= (rho*4*w*t + 4*tshear*rhoshear*(hin + w) 80 | + 2*rhocore*tcore*(w + hin))*b/2*deta, 81 | w <= wlim*cave, 82 | cave*tau >= hin + 4*t + 2*tcore, 83 | self.weight, 84 | t >= tmin, 85 | Sy*(hin/2 + 2*t + tcore) <= I, 86 | tshear >= tshearmin, 87 | tcore >= tcoret*cave*tau, 88 | d == w, 89 | ] 90 | 91 | return constraints 92 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/capspar.py: -------------------------------------------------------------------------------- 1 | " cap spar " 2 | from gpkit import Model, parse_variables 3 | from .sparloading import SparLoading 4 | from .gustloading import GustL 5 | from gpkitmodels.GP.materials import cfrpud, cfrpfabric, foamhd 6 | from gpkitmodels import g 7 | 8 | #pylint: disable=exec-used, undefined-variable, unused-argument, invalid-name 9 | 10 | class CapSpar(Model): 11 | """ Cap Spar Model 12 | 13 | Scalar Variables 14 | ---------------- 15 | E 2e7 [psi] Young modulus of CFRP 16 | W [lbf] spar weight 17 | wlim 0.15 [-] spar width to chord ratio 18 | mfac 0.97 [-] curvature knockdown factor 19 | 20 | Variables of length N-1 21 | ----------------------- 22 | hin [in] height between caps 23 | I [m^4] spar x moment of inertia 24 | Sy [m^3] section modulus 25 | dm [kg] segment spar mass 26 | w [in] spar width 27 | t [in] spar cap thickness 28 | tshear [in] shear web thickness 29 | 30 | Upper Unbounded 31 | --------------- 32 | W, cave, tau 33 | 34 | Lower Unbounded 35 | --------------- 36 | Sy, b, surface.deta 37 | 38 | LaTex Strings 39 | ------------- 40 | wlim w_{\\mathrm{lim}} 41 | mfac m_{\\mathrm{fac}} 42 | hin h_{\\mathrm{in}_i} 43 | I I_i 44 | Sy S_{y_i} 45 | dm \\Delta{m} 46 | w w_i 47 | t t_i 48 | tshear t_{\\mathrm{shear}_i} 49 | 50 | """ 51 | loading = SparLoading 52 | gustloading = GustL 53 | material = cfrpud 54 | shearMaterial = cfrpfabric 55 | coreMaterial = foamhd 56 | 57 | @parse_variables(__doc__, globals()) 58 | def setup(self, N, surface): 59 | self.surface = surface 60 | 61 | cave = self.cave = surface.cave 62 | b = self.b = surface.b 63 | deta = surface.deta 64 | tau = self.tau = surface.tau 65 | rho = self.material.rho 66 | rhoshear = self.shearMaterial.rho 67 | rhocore = self.coreMaterial.rho 68 | tshearmin = self.shearMaterial.tmin 69 | 70 | return [I/mfac <= 2*w*t*(hin/2)**2, 71 | dm >= (rho*(2*w*t) + 2*tshear*rhoshear*(hin + 2*t) 72 | + rhocore*w*hin)*b/2*deta, 73 | W >= 2*dm.sum()*g, 74 | w <= wlim*cave, 75 | cave*tau >= hin + 2*t, 76 | Sy*(hin/2 + t) <= I, 77 | tshear >= tshearmin 78 | ] 79 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/constant_taper_chord.py: -------------------------------------------------------------------------------- 1 | " constant taper chord " 2 | import numpy as np 3 | 4 | def c_bar(lam, N): 5 | "returns wing chord lengths for constant taper wing" 6 | eta = np.linspace(0, 1, N) 7 | c = 2/(1+lam)*(1+(lam-1)*eta) 8 | cbarmac = 2./3*(1+lam+lam**2)/(1+lam) 9 | deta = np.diff(eta) 10 | return c, eta, deta, cbarmac 11 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/gustloaddiagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/wing/gustloaddiagram.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/gustloading.py: -------------------------------------------------------------------------------- 1 | " spar loading for gust case " 2 | import os 3 | from numpy import pi, hstack, array 4 | from ad.admath import cos 5 | import pandas as pd 6 | from gpkit import parse_variables 7 | from gpfit.fit_constraintset import FitCS 8 | from .sparloading import SparLoading 9 | 10 | #pylint: disable=invalid-name, no-member, arguments-differ, exec-used 11 | #pylint: disable=attribute-defined-outside-init, undefined-variable 12 | 13 | class GustL(SparLoading): 14 | """ Gust Loading Model 15 | 16 | Variables 17 | --------- 18 | vgust 10 [m/s] gust velocity 19 | Ww [lbf] wing weight 20 | v [m/s] vehicle speed 21 | cl [-] wing lift coefficient 22 | 23 | Variables of length wing.N 24 | -------------------------- 25 | agust [-] gust angle of attack 26 | cosminus1 self.return_cosm1 [-] 1 minus cosine factor 27 | 28 | LaTex Strings 29 | ------------- 30 | vgust V_{\\mathrm{gust}} 31 | Ww W_{\\mathrm{w}} 32 | cl c_l 33 | agust \\alpha_{\\mathrm{gust}} 34 | cosminus1 (cos(x)-1) 35 | 36 | """ 37 | new_qbarFun = None 38 | new_SbarFun = None 39 | 40 | def return_cosm1(self, c): 41 | eta = c(self.wing.planform.eta).to("dimensionless").magnitude 42 | return hstack([1e-10, 1-array(cos(eta[1:]*pi/2))]) 43 | 44 | @parse_variables(__doc__, globals()) 45 | def setup(self, wing, state, out=False): 46 | self.load = SparLoading.setup(self, wing, state, out=out) 47 | 48 | cbar = self.wing.planform.cbar 49 | W = self.W # from SparLoading 50 | q = self.q 51 | N = self.N 52 | b = self.b 53 | 54 | path = os.path.dirname(os.path.abspath(__file__)) 55 | df = pd.read_csv(path + os.sep + "arctan_fit.csv").to_dict( 56 | orient="records")[0] 57 | 58 | constraints = [ 59 | # fit for arctan from 0 to 1, RMS = 0.044 60 | FitCS(df, agust, [cosminus1*vgust/v]), 61 | q >= W*N/b*cbar*(1 + 2*pi*agust/cl*(1+Ww/W)), 62 | ] 63 | 64 | return self.load, constraints 65 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/jho1polarfit1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/aircraft/wing/jho1polarfit1.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/jho1polars/genpolar.sh: -------------------------------------------------------------------------------- 1 | AIRFOIL=$1 2 | POLARFILE=$1.ncrit09.Re$2k.pol 3 | 4 | if [ -f $POLARFILE ] ; then 5 | echo "yes" 6 | rm $POLARFILE 7 | fi 8 | 9 | xfoil << EOF 10 | load $1.dat 11 | oper 12 | v $2e3 13 | pacc 14 | $POLARFILE 15 | 16 | iter 200 17 | cseq 0.2 1.2 .05 18 | 19 | quit 20 | EOF 21 | 22 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/jho1polars/jho1_polarfits.py: -------------------------------------------------------------------------------- 1 | "jho1_polarfits.py" 2 | from builtins import zip 3 | from builtins import range 4 | import numpy as np 5 | import pandas as pd 6 | import matplotlib.pyplot as plt 7 | plt.rcParams.update({'font.size':15}) 8 | 9 | def text_to_df(filename): 10 | "parse XFOIL polars and concatente data in DataFrame" 11 | lines = list(open(filename)) 12 | for i, l in enumerate(lines): 13 | lines[i] = l.split("\n")[0] 14 | for j in 10-np.arange(9): 15 | if " "*j in lines[i]: 16 | lines[i] = lines[i].replace(" "*j, " ") 17 | if "---" in lines[i]: 18 | start = i 19 | data = {} 20 | titles = lines[start-1].split(" ")[1:] 21 | for t in titles: 22 | data[t] = [] 23 | 24 | for l in lines[start+1:]: 25 | for i, v in enumerate(l.split(" ")[1:]): 26 | data[titles[i]].append(v) 27 | 28 | df = pd.DataFrame(data) 29 | return df 30 | 31 | def fit_setup(Re_range): 32 | "set up x and y parameters for gp fitting" 33 | CL = [] 34 | CD = [] 35 | RE = [] 36 | for r in Re_range: 37 | dataf = text_to_df("jho1.ncrit09.Re%dk.pol" % r) 38 | CL.append(dataf["CL"]) 39 | CD.append(dataf["CD"]) 40 | RE.append([r*1000.0]*len(dataf["CL"])) 41 | 42 | u1 = np.hstack(CL) 43 | u2 = np.hstack(RE) 44 | w = np.hstack(CD) 45 | u1 = u1.astype(np.float) 46 | u2 = u2.astype(np.float) 47 | w = w.astype(np.float) 48 | u = [u1, u2] 49 | x = np.log(u) 50 | y = np.log(w) 51 | return x, y 52 | 53 | def return_fit(cl, re): 54 | "polar fit for the JHO1 airfoil" 55 | cd = (0.0247*cl**2.49*re**-1.11 + 2.03e-7*cl**12.7*re**-0.338 + 56 | 6.35e10*cl**-0.243*re**-3.43 + 6.49e-6*cl**-1.9*re**-0.681)**(1/3.72) 57 | # cd = (0.0247*cl**2.5*re**-1.1 + 2.03e-7*cl**13*re**-0.34 + 58 | # 6.35e10*cl**-0.24*re**-3.4 + 6.49e-6*cl**-1.9*re**-0.68)**(1/3.7) 59 | # SMA function, K=3, max RMS error = 0.00489 60 | return cd 61 | 62 | def plot_fits(re): 63 | "plot fit compared to data" 64 | colors = ["k", "m", "b", "g", "y"] 65 | assert len(re) == len(colors) 66 | fig, ax = plt.subplots() 67 | fig1, ax1 = plt.subplots() 68 | cls = np.linspace(0.2, 1.3, 20) 69 | for r, col in zip(re, colors): 70 | dataf = text_to_df("jho1.ncrit09.Re%dk.pol" % r) 71 | ax.plot(dataf["CL"], dataf["CD"], "o", mec=col, mfc="none", mew=1.5) 72 | cd = return_fit(cls, r*1000.) 73 | ax.plot(cls, cd, c=col, label="Re = %dk" % r, lw=2) 74 | ax1.plot(cls, cls**1.5/cd) 75 | ax.legend(loc=2, fontsize=15) 76 | ax.set_xlabel("$C_L$") 77 | ax.set_ylabel("$c_{d_p}$") 78 | ax.grid() 79 | return fig, ax, fig1 80 | 81 | if __name__ == "__main__": 82 | Re = list(range(200, 750, 50)) 83 | X, Y = fit_setup(Re) # call fit(X, Y, 4, "SMA") to get fit 84 | F, A, F1 = plot_fits([300, 350, 400, 450, 500]) 85 | F.savefig("jho1polarfit1.pdf", bbox_inches="tight") 86 | F1.savefig("jho1cl32cd.pdf", bbox_inches="tight") 87 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/jho1polars/jho1polarsweep.sh: -------------------------------------------------------------------------------- 1 | AIRFOIL=jho1 2 | Re="200 250 300 350 400 450 500 550 600 650 700" 3 | for r in $Re 4 | do 5 | ./genpolar.sh $AIRFOIL $r 6 | done 7 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/jho_fitdata.csv: -------------------------------------------------------------------------------- 1 | ub0,ub1,rms_err,e31,e30,e11,e10,ftype,lb1,lb0,K,a1,c3,c2,c1,c0,e21,e20,d,e00,e01,max_err 2 | 1.3,700000.00000000023,0.0065776630393203682,-2.7130075007872931,-0.17866429593832706,-0.66747689316035896,2.9630718142366268,SMA,149999.99999999994,0.20000000000000001,4,3.0904265782626554,175843713.87100798,2.2831557182654378e-06,0.0016819467416519377,1.0237722536072613e-07,-0.34964098451136472,-1.6533259069045529,2,18.8561449416522,-0.18331956404932631,0.018001147362385339 3 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/sparloading.py: -------------------------------------------------------------------------------- 1 | " spar loading " 2 | from gpkit import Model, parse_variables 3 | from numpy import pi 4 | 5 | #pylint: disable=no-member, unused-argument, exec-used, invalid-name 6 | #pylint: disable=undefined-variable, attribute-defined-outside-init 7 | 8 | class SparLoading(Model): 9 | """ Spar Loading Model 10 | 11 | Variables 12 | --------- 13 | Nmax 5 [-] max loading 14 | Nsafety 1.0 [-] safety load factor 15 | kappa 0.2 [-] max tip deflection ratio 16 | W [lbf] loading weight 17 | N [-] loading factor 18 | twmax 15.*pi/180 [-] max tip twist 19 | Stip 1e-10 [N] tip loading 20 | Mtip 1e-10 [N*m] tip moment 21 | throot 1e-10 [-] root deflection angle 22 | wroot 1e-10 [m] root deflection 23 | 24 | Variables of length wing.N 25 | -------------------------- 26 | q [N/m] distributed wing loading 27 | S [N] shear along wing 28 | M [N*m] wing section root moment 29 | th [-] deflection angle 30 | w [m] wing deflection 31 | 32 | Variables of length wing.N-1 33 | ---------------------------- 34 | Mtw [N*m] local moment due to twisting 35 | theta [-] twist deflection 36 | EIbar [-] EIbar 37 | Sout [-] outboard variable 38 | 39 | LaTex Strings 40 | ------------- 41 | Nmax N_{\\mathrm{max}} 42 | kappa \\kappa 43 | Mr M_r 44 | 45 | """ 46 | def new_qbarFun(self, c): 47 | " define qbar model for chord loading " 48 | barc = self.wing.planform.cbar 49 | return [f(c) for f in self.wing.substitutions[barc]] 50 | 51 | new_SbarFun = None 52 | 53 | @parse_variables(__doc__, globals()) 54 | def setup(self, wing, state, out=False): 55 | self.wing = wing 56 | 57 | b = self.b = self.wing.planform.b 58 | I = self.I = self.wing.spar.I 59 | Sy = self.Sy = self.wing.spar.Sy 60 | cave = self.cave = self.wing.planform.cave 61 | cbar = self.cbar = self.wing.planform.cbar 62 | E = self.wing.spar.material.E 63 | sigma = self.wing.spar.material.sigma 64 | deta = self.wing.planform.deta 65 | 66 | constraints = [] 67 | if not out: 68 | constraints.extend([ 69 | S[:-1] >= S[1:] + 0.5*deta*(b/2.)*(q[:-1] + q[1:]), 70 | M[:-1] >= M[1:] + 0.5*deta*(b/2)*(S[:-1] + S[1:])]) 71 | 72 | constraints.extend([ 73 | N == Nsafety*Nmax, q >= N*W/b*cbar, 74 | S[-1] >= Stip, M[-1] >= Mtip, th[0] >= throot, 75 | th[1:] >= th[:-1] + 0.5*deta*(b/2)*(M[1:] + M[:-1])/E/I, 76 | w[0] >= wroot, w[1:] >= w[:-1] + 0.5*deta*(b/2)*(th[1:] + th[:-1]), 77 | sigma >= M[:-1]/Sy, w[-1]/(b/2) <= kappa, 78 | ]) 79 | 80 | self.wingSparJ = hasattr(self.wing.spar, "J") 81 | 82 | if self.wingSparJ: 83 | qne = self.qne = state.qne 84 | J = self.J = self.wing.spar.J 85 | G = self.wing.spar.shearMaterial.G 86 | cm = self.wing.planform.CM 87 | constraints.extend([ 88 | Mtw >= cm*cave**2*qne*deta*b/2*Nsafety, 89 | theta[0] >= Mtw[0]/G/J[0]*deta[0]*b/2, 90 | theta[1:] >= theta[:-1] + Mtw[1:]/G/J[1:]*deta[1:]*b/2, 91 | twmax >= theta[-1] 92 | ]) 93 | return constraints 94 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/tube_spar.py: -------------------------------------------------------------------------------- 1 | " tube spar for wing " 2 | from numpy import pi 3 | from gpkit import Variable, Model, Vectorize 4 | from chord_spar_loading import ChordSparL 5 | 6 | class TubeSpar(Model): 7 | " tube spar model " 8 | def setup(self, b, cave, tau, N=5): 9 | self.N = N 10 | 11 | rho_cfrp = Variable("\\rho_{CFRP}", 1.6, "g/cm^3", "density of CFRP") 12 | E = Variable("E", 2e7, "psi", "Youngs modulus of CF") 13 | 14 | with Vectorize(self.N-1): 15 | d = Variable("d", "in", "spar diameter") 16 | I = Variable("I", "m^4", "spar x moment of inertia") 17 | Sy = Variable("S_y", "m**3", "section modulous") 18 | A = Variable("A", "in**2", "spar cross sectional area") 19 | dm = Variable("dm", "kg", "segment spar mass") 20 | 21 | W = Variable("W", "lbf", "tube spar weight") 22 | g = Variable("g", 9.81, "m/s**2", "gravitational constant") 23 | 24 | constraints = [ 25 | dm >= rho_cfrp*A*b/(N-1), 26 | W >= 2*dm.sum()*g, 27 | cave*tau >= d, 28 | 4*I**2/A**2/(d/2)**2 + A/pi <= (d/2)**2, 29 | Sy*(d/2) <= I, 30 | E == E 31 | ] 32 | 33 | return constraints 34 | 35 | def loading(self, Wcent): 36 | return ChordSparL(self, Wcent) 37 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/wing.py: -------------------------------------------------------------------------------- 1 | " wing.py " 2 | from builtins import range 3 | from os import sep 4 | from os.path import abspath, dirname 5 | import numpy as np 6 | import pandas as pd 7 | from gpkit import Model, parse_variables 8 | from .wing_core import WingCore 9 | from .wing_skin import WingSkin 10 | from .capspar import CapSpar 11 | from gpfit.fit_constraintset import XfoilFit 12 | 13 | #pylint: disable=no-member, invalid-name, unused-argument, exec-used 14 | #pylint: disable=undefined-variable, attribute-defined-outside-init 15 | #pylint: disable=too-many-instance-attributes 16 | 17 | class Planform(Model): 18 | """ Planform Area Definition 19 | 20 | Scalar Variables 21 | --------- 22 | S [ft^2] surface area 23 | AR [-] aspect ratio 24 | b [ft] span 25 | tau [-] airfoil thickness ratio 26 | CLmax 1.39 [-] maximum lift coefficient 27 | CM 0.14 [-] wing moment coefficient 28 | croot [ft] root chord 29 | cmac [ft] mean aerodynamic chord 30 | lam 0.5 [-] taper ratio 31 | cbarmac self.return_cmac [-] non-dim MAC 32 | 33 | Variables of length N 34 | --------------------- 35 | eta np.linspace(0,1,N) [-] (2y/b) 36 | cbar self.return_c [-] non-dim chord at nodes 37 | 38 | Variables of length N-1 39 | ----------------------- 40 | cave [ft] mid section chord 41 | cbave self.return_avg [-] non-dim mid section chord 42 | deta self.return_deta [-] \\Delta (2y/b) 43 | 44 | Upper Unbounded 45 | --------------- # bounding any pair of variables will work 46 | cave, b, tau 47 | 48 | Lower Unbounded 49 | --------------- 50 | cave, b, tau 51 | 52 | LaTex Strings 53 | ------------- 54 | tau \\tau 55 | CLmax C_{L_{\\mathrm{max}}} 56 | CM C_M 57 | croot c_{\\mathrm{root}} 58 | cmac c_{\\mathrm{MAC}} 59 | lam \\lambda 60 | cbarmac \\bar{c}_{\\mathrm{MAC}} 61 | 62 | """ 63 | def return_c(self, c): 64 | " return normalized chord distribution " 65 | lam = c(self.lam).to("dimensionless").magnitude 66 | eta = c(self.eta).to("dimensionless").magnitude 67 | return np.array([2./(1+lam)*(1+(lam-1)*e) for e in eta]) 68 | 69 | def return_cmac(self, c): 70 | " return normalized MAC " 71 | cbar = self.return_c(c) 72 | lam = cbar[1:]/cbar[:-1] 73 | maci = 2./3*cbar[:-1]*(1 + lam + lam**2)/(1 + lam) 74 | deta = np.diff(c(self.eta)) 75 | num = sum([(cbar[i] + cbar[i+1])/2*maci[i]*deta[i] for i 76 | in range(len(deta))]) 77 | den = sum([(cbar[i] + cbar[i+1])/2*deta[i] for i in range(len(deta))]) 78 | return num/den/cbar[0] 79 | 80 | return_avg = lambda self, c: (self.return_c(c)[:-1] 81 | + self.return_c(c)[1:])/2. 82 | return_deta = lambda self, c: np.diff(c(self.eta)) 83 | 84 | @parse_variables(__doc__, globals()) 85 | def setup(self, N): 86 | return [b**2 == S*AR, 87 | cave == cbave*S/b, 88 | croot == S/b*cbar[0], 89 | cmac == croot*cbarmac] 90 | 91 | class WingAero(Model): 92 | """ Wing Aero Model 93 | 94 | Variables 95 | --------- 96 | Cd [-] wing drag coefficient 97 | CL [-] lift coefficient 98 | CLstall 1.3 [-] stall CL 99 | e 0.9 [-] span efficiency 100 | Re [-] reynolds number 101 | cdp [-] wing profile drag coefficient 102 | 103 | Upper Unbounded 104 | --------------- 105 | Cd, Re, static.planform.AR 106 | state.V, state.mu (if not muValue), state.rho (if not rhoValue) 107 | 108 | Lower Unbounded 109 | --------------- 110 | state.V, state.mu (if not muValue), state.rho (if not rhoValue) 111 | 112 | LaTex Strings 113 | ------------- 114 | Cd C_d 115 | CL C_L 116 | CLstall C_{L_{\\mathrm{stall}}} 117 | cdp c_{d_p} 118 | 119 | """ 120 | @parse_variables(__doc__, globals()) 121 | def setup(self, static, state, 122 | fitdata=dirname(abspath(__file__)) + sep + "jho_fitdata.csv"): 123 | self.state = state 124 | self.static = static 125 | 126 | df = pd.read_csv(fitdata) 127 | fd = df.to_dict(orient="records")[0] 128 | 129 | AR = static.planform.AR 130 | cmac = static.planform.cmac 131 | rho = state.rho 132 | V = state.V 133 | mu = state.mu 134 | # needed for Climb model in solar 135 | self.rhoValue = bool(rho.key.value) 136 | self.muValue = bool(mu.key.value) 137 | 138 | if fd["d"] == 2: 139 | independentvars = [CL, Re] 140 | elif fd["d"] == 3: 141 | independentvars = [CL, Re, static.planform.tau] 142 | 143 | return [Cd >= cdp + CL**2/np.pi/AR/e, 144 | Re == rho*V*cmac/mu, 145 | # XfoilFit(fd, cdp, [CL, Re], airfoil="jho1.dat"), 146 | XfoilFit(fd, cdp, independentvars, name="polar"), 147 | CL <= CLstall 148 | ] 149 | 150 | class Wing(Model): 151 | """ 152 | Wing Model 153 | 154 | Variables 155 | --------- 156 | W [lbf] wing weight 157 | mfac 1.2 [-] wing weight margin factor 158 | 159 | SKIP VERIFICATION 160 | 161 | Upper Unbounded 162 | --------------- 163 | W, planform.tau (if not sparJ) 164 | 165 | Lower Unbounded 166 | --------------- 167 | planform.b, spar.Sy (if sparModel), spar.J (if sparJ) 168 | 169 | LaTex Strings 170 | ------------- 171 | mfac m_{\\mathrm{fac}} 172 | 173 | """ 174 | 175 | sparModel = CapSpar 176 | fillModel = WingCore 177 | flight_model = WingAero 178 | skinModel = WingSkin 179 | sparJ = False 180 | 181 | @parse_variables(__doc__, globals()) 182 | def setup(self, N=5): 183 | self.N = N 184 | self.planform = Planform(N) 185 | self.components = [] 186 | 187 | if self.skinModel: 188 | self.skin = self.skinModel(self.planform) 189 | self.components.extend([self.skin]) 190 | if self.sparModel: 191 | self.spar = self.sparModel(N, self.planform) 192 | self.components.extend([self.spar]) 193 | self.sparJ = hasattr(self.spar, "J") 194 | if self.fillModel: 195 | self.foam = self.fillModel(self.planform) 196 | self.components.extend([self.foam]) 197 | 198 | constraints = [W/mfac >= sum(c["W"] for c in self.components)] 199 | 200 | return constraints, self.planform, self.components 201 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/wing_core.py: -------------------------------------------------------------------------------- 1 | " wing interior " 2 | from gpkit import Model, parse_variables 3 | from gpkitmodels.GP.materials import foamhd 4 | from gpkitmodels import g 5 | 6 | #pylint: disable=exec-used, no-member, undefined-variable 7 | 8 | class WingCore(Model): 9 | """ Wing Core Model 10 | 11 | Variables 12 | --------- 13 | W [lbf] wing core weight 14 | Abar 0.0753449 [-] normalized cross section area 15 | 16 | Upper Unbounded 17 | --------------- 18 | W 19 | 20 | Lower Unbounded 21 | --------------- 22 | cave, b, surface.deta 23 | 24 | LaTex Strings 25 | ------------- 26 | rhocore \\rho_{\\mathrm{core}} 27 | Abar \\bar{A} 28 | 29 | """ 30 | material = foamhd 31 | 32 | @parse_variables(__doc__, globals()) 33 | def setup(self, surface): 34 | self.surface = surface 35 | 36 | cave = self.cave = surface.cave 37 | b = self.b = surface.b 38 | deta = surface.deta 39 | rho = self.material.rho 40 | 41 | return [W >= 2*(g*rho*Abar*cave**2*b/2*deta).sum()] 42 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/wing_skin.py: -------------------------------------------------------------------------------- 1 | " wing skin " 2 | from gpkit import Model, parse_variables 3 | from gpkitmodels.GP.materials import cfrpfabric 4 | from gpkitmodels import g 5 | 6 | class WingSkin(Model): 7 | """ Wing Skin model 8 | 9 | Variables 10 | --------- 11 | W [lbf] wing skin weight 12 | t [in] wing skin thickness 13 | Jtbar 0.01114 [1/mm] torsional moment of inertia 14 | Cmw 0.121 [-] negative wing moment coeff 15 | rhosl 1.225 [kg/m^3] sea level air density 16 | Vne 45 [m/s] never exceed vehicle speed 17 | 18 | Upper Unbounded 19 | --------------- 20 | W, surface.croot 21 | 22 | Lower Unbounded 23 | --------------- 24 | surface.S 25 | 26 | LaTex Strings 27 | ------------- 28 | W W_{\\mathrm{skin}} 29 | t t_{\\mathrm{skin}} 30 | Jtbar \\bar{J/t} 31 | Cmw C_{m_w} 32 | rhosl \\rho_{\\mathrm{SL}} 33 | Vne V_{\\mathrm{NE}} 34 | 35 | """ 36 | material = cfrpfabric 37 | 38 | @parse_variables(__doc__, globals()) 39 | def setup(self, surface): 40 | self.surface = surface 41 | 42 | croot = surface.croot 43 | S = surface.S 44 | rho = self.material.rho 45 | tau = self.material.tau 46 | tmin = self.material.tmin 47 | 48 | return [W >= rho*S*2*t*g, 49 | t >= tmin, 50 | tau >= 1/Jtbar/croot**2/t*Cmw*S*rhosl*Vne**2] 51 | 52 | class WingSecondStruct(Model): 53 | """ Wing Skin model 54 | 55 | Variables 56 | --------- 57 | W [lbf] wing skin weight 58 | rhoA 0.35 [kg/m^2] total aerial density 59 | 60 | Upper Unbounded 61 | --------------- 62 | W 63 | 64 | Lower Unbounded 65 | --------------- 66 | S 67 | 68 | LaTex Strings 69 | ------------- 70 | W W_{\\mathrm{skin}} 71 | rhoA \\rho_{A} 72 | 73 | """ 74 | @parse_variables(__doc__, globals()) 75 | def setup(self, surface): 76 | S = self.S = surface.S 77 | 78 | return [W >= rhoA*S*g] 79 | -------------------------------------------------------------------------------- /gpkitmodels/GP/aircraft/wing/wing_test.py: -------------------------------------------------------------------------------- 1 | " wing test " 2 | from gpkitmodels.GP.aircraft.wing.wing import Wing 3 | from gpkitmodels.GP.aircraft.wing.wing_skin import WingSkin 4 | from gpkitmodels.GP.aircraft.wing.wing_core import WingCore 5 | from gpkitmodels.GP.aircraft.wing.boxspar import BoxSpar 6 | from gpkit import Model, parse_variables 7 | 8 | #pylint: disable=no-member, exec-used 9 | 10 | class FlightState(Model): 11 | """ Flight State 12 | 13 | Variables 14 | --------- 15 | V 50 [m/s] airspeed 16 | rho 1.255 [kg/m^3] air density 17 | mu 1.5e-5 [N*s/m**2] air viscosity 18 | qne [kg/s^2/m] never exceed dynamic pressure 19 | 20 | """ 21 | @parse_variables(__doc__, globals()) 22 | def setup(self): 23 | return [qne == V**2*rho*1.2] 24 | 25 | def wing_test(): 26 | " test wing models " 27 | 28 | W = Wing() 29 | W.substitutions[W.W] = 50 30 | W.substitutions[W.planform.tau] = 0.115 31 | fs = FlightState() 32 | perf = W.flight_model(W, fs) 33 | loading = [W.spar.loading(W, fs)] 34 | loading[0].substitutions["W"] = 100 35 | loading.append(W.spar.gustloading(W, fs)) 36 | loading[1].substitutions["W"] = 100 37 | 38 | from gpkit import settings 39 | if settings["default_solver"] == "cvxopt": 40 | for l in loading: 41 | for v in ["Mtip", "Stip", "wroot", "throot"]: 42 | l.substitutions[v] = 1e-1 43 | 44 | m = Model(perf.Cd, [ 45 | loading[1].v == fs.V, 46 | loading[1].cl == perf.CL, 47 | loading[1].Ww == W.W, 48 | loading[1].Ww <= 0.5*fs.rho*fs.V**2*perf.CL*W.planform.S, 49 | W, fs, perf, loading]) 50 | m.solve(verbosity=0) 51 | 52 | def box_spar(): 53 | " test wing models " 54 | 55 | Wing.sparModel = BoxSpar 56 | W = Wing() 57 | W.substitutions[W.W] = 50 58 | W.substitutions[W.planform.tau] = 0.115 59 | fs = FlightState() 60 | perf = W.flight_model(W, fs) 61 | loading = [W.spar.loading(W, fs)] 62 | loading[0].substitutions["W"] = 100 63 | loading.append(W.spar.gustloading(W, fs)) 64 | loading[1].substitutions["W"] = 100 65 | 66 | from gpkit import settings 67 | if settings["default_solver"] == "cvxopt": 68 | for l in loading: 69 | for v in ["Mtip", "Stip", "wroot", "throot"]: 70 | l.substitutions[v] = 1e-2 71 | 72 | m = Model(perf.Cd, [ 73 | loading[1].v == fs.V, 74 | loading[1].cl == perf.CL, 75 | loading[1].Ww == W.W, 76 | loading[1].Ww <= fs.qne*perf.CL*W.planform.S, 77 | W, fs, perf, loading]) 78 | m.solve(verbosity=0) 79 | 80 | def test(): 81 | " tests " 82 | wing_test() 83 | box_spar() 84 | 85 | if __name__ == "__main__": 86 | test() 87 | -------------------------------------------------------------------------------- /gpkitmodels/GP/beam/README.md: -------------------------------------------------------------------------------- 1 | # Beam 2 | 3 | This model is valid for a discritized beam and calculates non-dimensional sheer stresses, moments, angles and deflections for a given distributed load. Assumes segment lengths are equal along the beam. 4 | 5 | Required inputs are a non-dimensional distributed load that is a vector variable and the number of nodes 6 | 7 | \begin{table}[] 8 | \centering 9 | \caption{My caption} 10 | \label{my-label} 11 | \begin{tabular}{lll} 12 | Input & Description & Type \\ 13 | qbar & Distributed load with N values & VectorVariable \\ 14 | N & Number of nodes & int 15 | \end{tabular} 16 | \end{table} 17 | 18 | The non-dimensional equation derivation is shown below for wing bending application. 19 | 20 | Using a standard Bernoulli-Euler discretized beam model with $n$ nodes, the shear forces and moments can be computed from the distributed loads $q(y)$, with boundary conditions of zero shear forces and moments at the wing tips. 21 | 22 | \begin{align} 23 | \label{e:shear} 24 | \mathcal{S}_i &= \mathcal{S}_{i+1} - \frac{q_{i+1} + q_i}{2}\Delta y \\ 25 | \label{e:moment} 26 | M_i &= M_{i+1} - \frac{\mathcal{S}_{i+1} + \mathcal{S}_i}{2}\Delta y \\ 27 | \label{e:shearboundary} 28 | \mathcal{S}_n &= 0 \\ 29 | \label{e:momentboundary} 30 | M_n &= 0 31 | \end{align} 32 | 33 | Similarly, the angle deflection and deflection can be calculated with boundary conditions of zero angle and deflection and the wing root. 34 | 35 | \begin{align} 36 | \label{e:angle} 37 | \Theta_{i} &= \Theta_{i+1} + \frac{1}{2} \left(\frac{M_i}{EI_i} + \frac{M_{i-1}}{EI_{i-1}} \right) \Delta y \\ 38 | \label{e:deflection} 39 | w_{i} &= w_{i+1} + \frac{1}{2} (\Theta_i + \Theta_{i-1}) \Delta y \\ 40 | \label{e:angleboundary} 41 | \Theta_0 &= 0 \\ 42 | \label{e:defboundary} 43 | w_0 &= 0 44 | \end{align} 45 | 46 | Equations~\eqref{e:shear}-\eqref{e:defboundary} are GP-compatible if expressed as 47 | 48 | \begin{align} 49 | \label{e:sheargp} 50 | \mathcal{S}_{i+1} &\geq \mathcal{S}_i + \frac{q_{i+1} + q_i}{2} \Delta y \\ 51 | \label{e:momentgp} 52 | \mathcal{M}_{i+1} &\geq \mathcal{M}_i + \frac{\mathcal{S}_{i+1} + \mathcal{S}_i}{2} \Delta y \\ 53 | \label{e:anglegp} 54 | \Theta_{i} &\geq \Theta_{i+1} + \frac{1}{2} \left(\frac{\mathcal{M}_i}{EI_i} + \frac{\mathcal{M}_{i-1}}{EI_{i-1}} \right) \Delta y \\ 55 | \label{e:deflection} 56 | w_{i} &\geq w_{i+1} + \frac{1}{2} (\Theta_i + \Theta_{i-1}) \Delta y 57 | \end{align} 58 | 59 | -------------------------------------------------------------------------------- /gpkitmodels/GP/beam/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/beam/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/GP/beam/beam.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/GP/beam/beam.pdf -------------------------------------------------------------------------------- /gpkitmodels/GP/beam/beam.py: -------------------------------------------------------------------------------- 1 | " discretized beam model " 2 | from gpkit import Model, Variable, Vectorize 3 | 4 | #pylint: disable=invalid-name 5 | 6 | class Beam(Model): 7 | """discretized beam bending model 8 | 9 | Upper Unbounded 10 | --------------- 11 | EIbar, dbar_tip 12 | 13 | Lower Unbounded 14 | --------------- 15 | dx, qbar (if not qbarFun) 16 | 17 | """ 18 | qbarFun = None 19 | SbarFun = None 20 | MbarFun = None 21 | 22 | def setup(self, N): 23 | 24 | with Vectorize(N-1): 25 | EIbar = self.EIbar = Variable("\\bar{EI}", "-", 26 | "normalized YM and moment of inertia") 27 | dx = self.dx = Variable("dx", "-", "normalized length of element") 28 | 29 | with Vectorize(N): 30 | Sbar = Variable("\\bar{S}", self.SbarFun, "-", "normalized shear") 31 | Mbar = Variable("\\bar{M}", self.MbarFun, "-", "normalized moment") 32 | th = Variable("\\theta", "-", "deflection slope") 33 | dbar = Variable("\\bar{\\delta}", "-", "normalized displacement") 34 | self.dbar_tip = dbar[-1] 35 | 36 | 37 | throot = Variable("\\theta_{root}", 1e-10, "-", "Base angle") 38 | dbarroot = Variable("\\bar{\\delta}_{root}", 1e-10, "-", 39 | "Base deflection") 40 | 41 | constraints = [] 42 | if self.SbarFun is None: 43 | with Vectorize(N): 44 | qbar = self.qbar = Variable("\\bar{q}", self.qbarFun, "-", 45 | "normalized loading") 46 | Sbartip = Variable("\\bar{S}_{tip}", 1e-10, "-", "Tip loading") 47 | constraints.extend([ 48 | Sbar[:-1] >= Sbar[1:] + 0.5*dx*(qbar[:-1] + qbar[1:]), 49 | Sbar[-1] >= Sbartip]) 50 | 51 | if self.MbarFun is None: 52 | Mbartip = Variable("\\bar{M}_{tip}", 1e-10, "-", "Tip moment") 53 | constraints.extend([ 54 | Mbar[:-1] >= Mbar[1:] + 0.5*dx*(Sbar[:-1] + Sbar[1:]), 55 | Mbar[-1] >= Mbartip]) 56 | 57 | constraints.extend([ 58 | th[0] >= throot, 59 | th[1:] >= th[:-1] + 0.5*dx*(Mbar[1:] + Mbar[:-1])/EIbar, 60 | dbar[0] >= dbarroot, 61 | dbar[1:] >= dbar[:-1] + 0.5*dx*(th[1:] + th[:-1]), 62 | ]) 63 | 64 | return constraints 65 | -------------------------------------------------------------------------------- /gpkitmodels/GP/materials/__init__.py: -------------------------------------------------------------------------------- 1 | from .composite import CFRPFabric, CFRPUD, Kevlar 2 | from .foam import FoamHD, FoamLD 3 | 4 | cfrpfabric = CFRPFabric() 5 | cfrpud = CFRPUD() 6 | foamhd = FoamHD() 7 | foamld = FoamLD() 8 | kevlar = Kevlar() 9 | -------------------------------------------------------------------------------- /gpkitmodels/GP/materials/composite.py: -------------------------------------------------------------------------------- 1 | from gpkit import Model, parse_variables 2 | 3 | class CFRPFabric(Model): 4 | """ Carbon Fiber Reinforced Plastic Fabric Material Properties 5 | 6 | Constants 7 | --------- 8 | rho 1.6 [g/cm^3] density of CFRP 9 | tmin 0.3048 [mm] minimum gauge thickness 10 | tau 570 [MPa] torsional stress limit 11 | E 150 [GPa] Youngs modulus 12 | sigma 400 [MPa] max stress 13 | G 2 [GPa] shear modulus 14 | 15 | LaTex Strings 16 | ------------- 17 | rho \\rho_{\\mathrm{CFRP}} 18 | tmin t_{\\mathrm{min-CFRP}} 19 | tau \\tau_{\\mathrm{CFRP}} 20 | 21 | """ 22 | @parse_variables(__doc__, globals()) 23 | def setup(self): 24 | pass 25 | 26 | class CFRPUD(Model): 27 | """ Carbon Fiber Reinforced Plastic Unidirectional Material Properties 28 | 29 | Constants 30 | --------- 31 | rho 1.6 [g/cm^3] density of CFRP 32 | E 137 [GPa] Youngs Modulus of CFRP 33 | sigma 1700 [MPa] maximum stress limit of CFRP 34 | tmin 0.1 [mm] minimum gague thickness 35 | 36 | LaTex Strings 37 | ------------- 38 | rho \\rho_{\\mathrm{CFRP}} 39 | tmin t_{\\mathrm{min-CFRP}} 40 | sigma \\sigma_{\\mathrm{CFRP}} 41 | 42 | """ 43 | @parse_variables(__doc__, globals()) 44 | def setup(self): 45 | pass 46 | 47 | class Kevlar(Model): 48 | """ Kevlar Material Properties 49 | 50 | Constants 51 | --------- 52 | rho 0.049 [g/cm^3] density of Kevlar 53 | tmin 0.012 [in] minimum gauge thickness 54 | tau 200 [MPa] torsional stress limit 55 | 56 | LaTex Strings 57 | ------------- 58 | rho \\rho_{\\mathrm{Kevlar}} 59 | tmin t_{\\mathrm{min-Kevlar}} 60 | tau \\tau_{\\mathrm{Kevlar}} 61 | 62 | """ 63 | @parse_variables(__doc__, globals()) 64 | def setup(self): 65 | pass 66 | -------------------------------------------------------------------------------- /gpkitmodels/GP/materials/foam.py: -------------------------------------------------------------------------------- 1 | from gpkit import Model, parse_variables 2 | 3 | class FoamHD(Model): 4 | """ Foam high density material properties 5 | 6 | Constants 7 | --------- 8 | rho 0.036 [g/cm^3] foam density 9 | 10 | LaTex Strings 11 | ------------- 12 | rho \\rho_{\\mathrm{foam}} 13 | 14 | """ 15 | @parse_variables(__doc__, globals()) 16 | def setup(self): 17 | pass 18 | 19 | class FoamLD(Model): 20 | """ Foam low density material properties 21 | 22 | Constants 23 | --------- 24 | rho 0.024 [g/cm^3] foam density 25 | 26 | LaTex Strings 27 | ------------- 28 | rho \\rho_{\\mathrm{foam}} 29 | 30 | """ 31 | @parse_variables(__doc__, globals()) 32 | def setup(self): 33 | pass 34 | -------------------------------------------------------------------------------- /gpkitmodels/SP/SimPleAC/SimPleAC.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from gpkit import Model, Variable, SignomialsEnabled, VarKey, units 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | class SimPleAC(Model): 7 | def setup(self): 8 | # Env. constants 9 | g = Variable("g", 9.81, "m/s^2", "gravitational acceleration") 10 | mu = Variable("\\mu", 1.775e-5, "kg/m/s", "viscosity of air", pr=4.) 11 | rho = Variable("\\rho", 1.23, "kg/m^3", "density of air", pr=5.) 12 | rho_f = Variable("\\rho_f", 817, "kg/m^3", "density of fuel") 13 | 14 | # Non-dimensional constants 15 | C_Lmax = Variable("C_{L,max}", 1.6, "-", "max CL with flaps down", pr=5.) 16 | e = Variable("e", 0.92, "-", "Oswald efficiency factor", pr=3.) 17 | k = Variable("k", 1.17, "-", "form factor", pr=10.) 18 | N_ult = Variable("N_{ult}", 3.3, "-", "ultimate load factor", pr=15.) 19 | S_wetratio = Variable("(\\frac{S}{S_{wet}})", 2.075, "-", "wetted area ratio", pr=3.) 20 | tau = Variable("\\tau", 0.12, "-", "airfoil thickness to chord ratio", pr=10.) 21 | W_W_coeff1 = Variable("W_{W_{coeff1}}", 2e-5, "1/m", 22 | "wing weight coefficent 1", pr= 30.) #orig 12e-5 23 | W_W_coeff2 = Variable("W_{W_{coeff2}}", 60., "Pa", 24 | "wing weight coefficent 2", pr=10.) 25 | p_labor = Variable('p_{labor}',1.,'1/min','cost of labor', pr = 20.) 26 | 27 | # Dimensional constants 28 | Range = Variable("Range",3000, "km", "aircraft range") 29 | TSFC = Variable("TSFC", 0.6, "1/hr", "thrust specific fuel consumption") 30 | V_min = Variable("V_{min}", 25, "m/s", "takeoff speed", pr=20.) 31 | W_0 = Variable("W_0", 6250, "N", "aircraft weight excluding wing", pr=20.) 32 | 33 | # Free Variables 34 | LoD = Variable('L/D','-','lift-to-drag ratio') 35 | D = Variable("D", "N", "total drag force") 36 | V = Variable("V", "m/s", "cruising speed") 37 | W = Variable("W", "N", "total aircraft weight") 38 | Re = Variable("Re", "-", "Reynold's number") 39 | CDA0 = Variable("(CDA0)", "m^2", "fuselage drag area") #0.035 originally 40 | C_D = Variable("C_D", "-", "drag coefficient") 41 | C_L = Variable("C_L", "-", "lift coefficient of wing") 42 | C_f = Variable("C_f", "-", "skin friction coefficient") 43 | W_f = Variable("W_f", "N", "fuel weight") 44 | V_f = Variable("V_f", "m^3", "fuel volume") 45 | V_f_avail = Variable("V_{f_{avail}}","m^3","fuel volume available") 46 | T_flight = Variable("T_{flight}", "hr", "flight time") 47 | 48 | # Free variables (fixed for performance eval.) 49 | A = Variable("A", "-", "aspect ratio",fix = True) 50 | S = Variable("S", "m^2", "total wing area", fix = True) 51 | W_w = Variable("W_w", "N", "wing weight")#, fix = True) 52 | W_w_strc = Variable('W_w_strc','N','wing structural weight', fix = True) 53 | W_w_surf = Variable('W_w_surf','N','wing skin weight', fix = True) 54 | V_f_wing = Variable("V_f_wing",'m^3','fuel volume in the wing', fix = True) 55 | V_f_fuse = Variable('V_f_fuse','m^3','fuel volume in the fuselage', fix = True) 56 | constraints = [] 57 | 58 | # Weight and lift model 59 | constraints += [W >= W_0 + W_w + W_f, 60 | W_0 + W_w + 0.5 * W_f <= 0.5 * rho * S * C_L * V ** 2, 61 | W <= 0.5 * rho * S * C_Lmax * V_min ** 2, 62 | T_flight >= Range / V, 63 | LoD == C_L/C_D] 64 | 65 | # Thrust and drag model 66 | C_D_fuse = CDA0 / S 67 | C_D_wpar = k * C_f * S_wetratio 68 | C_D_ind = C_L ** 2 / (np.pi * A * e) 69 | constraints += [W_f >= TSFC * T_flight * D, 70 | D >= 0.5 * rho * S * C_D * V ** 2, 71 | C_D >= C_D_fuse + C_D_wpar + C_D_ind, 72 | V_f_fuse <= 10*units('m')*CDA0, 73 | Re <= (rho / mu) * V * (S / A) ** 0.5, 74 | C_f >= 0.074 / Re ** 0.2] 75 | 76 | # Fuel volume model 77 | with SignomialsEnabled(): 78 | constraints +=[V_f == W_f / g / rho_f, 79 | V_f_wing**2 <= 0.0009*S**3/A*tau**2, # linear with b and tau, quadratic with chord 80 | V_f_avail <= V_f_wing + V_f_fuse, #[SP] 81 | V_f_avail >= V_f 82 | ] 83 | 84 | # Wing weight model 85 | constraints += [W_w_surf >= W_W_coeff2 * S, 86 | W_w_strc**2. >= W_W_coeff1**2. / tau**2. * (N_ult**2. * A ** 3. * ((W_0+V_f_fuse*g*rho_f) * W * S)), 87 | W_w >= W_w_surf + W_w_strc] 88 | 89 | return constraints 90 | 91 | def test(): 92 | m = SimPleAC() 93 | m.cost = m['W_f'] 94 | sol = m.localsolve(verbosity = 2) 95 | 96 | if __name__ == "__main__": 97 | # Most basic way to execute the model 98 | from gpkit import Vectorize 99 | with Vectorize(3): 100 | m = SimPleAC() 101 | # m.cost = m['W'] 102 | # sol1 = m.localsolve(verbosity = 2) 103 | m.substitutions["Range"] = [1000, 3000, 6000] 104 | m.extend([ m["A"] == m["A"][0], 105 | m["S"] == m["S"][0], 106 | m["W_w"] == m["W_w"][0], 107 | m["W_w_strc"] == m["W_w_strc"][0], 108 | m["W_w_surf"] == m["W_w_surf"][0], 109 | m["V_f_wing"] == m["V_f_wing"][0], 110 | m["V_f_fuse"] == m["V_f_fuse"][0]]) 111 | m.cost = 3*m['W_f'][0] + m['W_f'][1] + 0.33*m['W_f'][2] 112 | sol = m.localsolve(verbosity = 1) 113 | # print(sol1.diff(sol2)) 114 | print(sol.table()) 115 | -------------------------------------------------------------------------------- /gpkitmodels/SP/SimPleAC/SimPleAC_multimission.py: -------------------------------------------------------------------------------- 1 | from builtins import range 2 | import numpy as np 3 | from gpkit import Model, Variable, SignomialsEnabled, SignomialEquality, \ 4 | VarKey, units, Vectorize, settings 5 | from gpkitmodels.SP.SimPleAC.SimPleAC_mission import Mission, SimPleAC 6 | from gpkitmodels.SP.atmosphere.atmosphere import Atmosphere 7 | 8 | # SimPleAC with multimission design (updated 5/31/2019, by Berk Ozturk) 9 | 10 | class Multimission(Model): 11 | def setup(self,aircraft,Nmissions,Nsegments): 12 | self.aircraft = aircraft 13 | self.missions = [] 14 | for i in range(0,Nmissions): 15 | self.missions.append(Mission(self.aircraft,Nsegments)) 16 | 17 | # Multimission objective variables 18 | W_f_mm = Variable('W_{f_{mm}}','N','multimission fuel weight') 19 | 20 | with Vectorize(Nmissions): 21 | # Mission variables 22 | hcruise = Variable('h_{cruise_{mm}}', 'm', 'minimum cruise altitude') 23 | Range = Variable("Range_{mm}", "km", "aircraft range") 24 | W_p = Variable("W_{p_{mm}}", "N", "payload weight", pr=20.) 25 | rho_p = Variable("\\rho_{p_{mm}}", 1500, "kg/m^3", "payload density", pr=10.) 26 | V_min = Variable("V_{min_{mm}}", 25, "m/s", "takeoff speed", pr=20.) 27 | cost_index = Variable("C_{mm}", '1/hr','hourly cost index') 28 | TOfac = Variable('T/O factor_{mm}', 2.,'-','takeoff thrust factor') 29 | 30 | constraints = [] 31 | 32 | # Setting up the missions 33 | for i in range(0,Nmissions): 34 | constraints += [ 35 | self.missions[i]['h_{cruise_m}'] == hcruise[i], 36 | self.missions[i]['Range_m'] == Range[i], 37 | self.missions[i]['W_{p_m}'] == W_p[i], 38 | self.missions[i]['\\rho_{p_m}'] == rho_p[i], 39 | self.missions[i]['V_{min_m}'] == V_min[i], 40 | self.missions[i]['C_m'] == cost_index[i], 41 | self.missions[i]['T/O factor_m'] == TOfac[i], 42 | # Upper bounding relevant variables 43 | W_f_mm <= 1e11*units('N'), 44 | ] 45 | 46 | # Multimission constraints 47 | constraints += [W_f_mm >= sum(self.missions[i]['W_{f_m}'] for i in range(0,Nmissions))] 48 | 49 | return constraints, self.aircraft, self.missions 50 | 51 | def test(): 52 | Nmissions = 2 53 | Nsegments = 4 54 | aircraft = SimPleAC() 55 | m = Multimission(aircraft,Nmissions,Nsegments) 56 | m.substitutions.update({ 57 | 'h_{cruise_{mm}}':[5000*units('m'), 5000*units('m')], 58 | 'Range_{mm}' :[3000*units('km'), 2000*units('km')], 59 | 'W_{p_{mm}}' :[6250*units('N'), 8000*units('N')], 60 | '\\rho_{p_{mm}}' :[1500*units('kg/m^3'), 2000*units('kg/m^3')], 61 | 'C_{mm}' :[120*units('1/hr'), 360*units('1/hr')], 62 | }) 63 | 64 | m.cost = (m.missions[0]['W_{f_m}']*units('1/N') + m.missions[1]['C_m']*m.missions[1]['t_m']) 65 | if settings["default_solver"] == "cvxopt": 66 | return 67 | else: 68 | sol = m.localsolve(verbosity=0) 69 | 70 | if __name__ == "__main__": 71 | Nmissions = 2 72 | Nsegments = 4 73 | aircraft = SimPleAC() 74 | m = Multimission(aircraft,Nmissions,Nsegments) 75 | m.substitutions.update({ 76 | 'h_{cruise_{mm}}':[5000*units('m'), 5000*units('m')], 77 | 'Range_{mm}' :[3000*units('km'), 2000*units('km')], 78 | 'W_{p_{mm}}' :[6250*units('N'), 8000*units('N')], 79 | '\\rho_{p_{mm}}' :[1500*units('kg/m^3'), 2000*units('kg/m^3')], 80 | 'C_{mm}' :[120*units('1/hr'), 360*units('1/hr')], 81 | }) 82 | 83 | m.cost = (m.missions[0]['W_{f_m}']*units('1/N') + m.missions[1]['C_m']*m.missions[1]['t_m']) 84 | sol = m.localsolve(verbosity = 2) 85 | -------------------------------------------------------------------------------- /gpkitmodels/SP/SimPleAC/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/SimPleAC/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/SimPleAC/simpleac.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/SimPleAC/simpleac.pdf -------------------------------------------------------------------------------- /gpkitmodels/SP/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/aircraft/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/aircraft/prop/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/dae51_fitdata.csv: -------------------------------------------------------------------------------- 1 | lb1,lb0,ub0,d,e11,e10,K,e21,e20,e00,e01,a1,ftype,max_err,c2,c1,c0,rms_err,ub1 2 | 50000.00000000001,0.4742,1.4495,2,-0.8108155563264187,6.320590230366453,3,-2.8766519103828543,42.173818269878126,-0.7631609541518628,-5.0062373433526455,6.153287557875197,SMA,0.32163665175749223,2.655008812201457,8.068321204642572e-09,309019700605405.5,0.09733995717389085,700000.0000000002 3 | -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/dae51polars/dae51.dat: -------------------------------------------------------------------------------- 1 | DAE 51 2 | # Daedalus propeller airfoil 3 | 1.000000 -0.000051 4 | 0.989166 0.001996 5 | 0.975819 0.004501 6 | 0.957448 0.007991 7 | 0.931599 0.013004 8 | 0.901654 0.018957 9 | 0.870818 0.025167 10 | 0.839998 0.031363 11 | 0.809336 0.037430 12 | 0.778839 0.043301 13 | 0.748503 0.048933 14 | 0.718326 0.054243 15 | 0.688314 0.059175 16 | 0.658443 0.063716 17 | 0.628653 0.067839 18 | 0.598855 0.071530 19 | 0.569004 0.074792 20 | 0.539074 0.077652 21 | 0.509071 0.080095 22 | 0.479015 0.082100 23 | 0.448934 0.083641 24 | 0.418877 0.084690 25 | 0.388863 0.085217 26 | 0.358908 0.085197 27 | 0.329049 0.084600 28 | 0.299321 0.083399 29 | 0.269760 0.081570 30 | 0.240419 0.079081 31 | 0.211388 0.075895 32 | 0.182766 0.071967 33 | 0.154699 0.067243 34 | 0.127404 0.061670 35 | 0.101193 0.055204 36 | 0.076503 0.047832 37 | 0.054060 0.039675 38 | 0.034881 0.031085 39 | 0.020059 0.022737 40 | 0.010052 0.015405 41 | 0.004208 0.009557 42 | 0.001251 0.005050 43 | 0.000113 0.001479 44 | 0.000123 -0.001494 45 | 0.001002 -0.004085 46 | 0.002793 -0.006422 47 | 0.006019 -0.008524 48 | 0.011960 -0.010609 49 | 0.022698 -0.012727 50 | 0.040333 -0.014554 51 | 0.064233 -0.015717 52 | 0.091882 -0.016173 53 | 0.121305 -0.016067 54 | 0.151560 -0.015570 55 | 0.182231 -0.014790 56 | 0.213149 -0.013797 57 | 0.244240 -0.012649 58 | 0.275464 -0.011392 59 | 0.306798 -0.010061 60 | 0.338230 -0.008690 61 | 0.369725 -0.007318 62 | 0.401184 -0.005962 63 | 0.432597 -0.004640 64 | 0.463976 -0.003366 65 | 0.495325 -0.002147 66 | 0.526644 -0.000994 67 | 0.557930 0.000082 68 | 0.589182 0.001071 69 | 0.620390 0.001960 70 | 0.651547 0.002731 71 | 0.682648 0.003367 72 | 0.713694 0.003847 73 | 0.744690 0.004152 74 | 0.775644 0.004269 75 | 0.806546 0.004190 76 | 0.837373 0.003898 77 | 0.868134 0.003359 78 | 0.898985 0.002532 79 | 0.929868 0.001410 80 | 0.956572 0.000219 81 | 0.975393 -0.000742 82 | 0.988983 -0.001472 83 | 1.000000 -0.002051 -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/dae51polars/dae51.txt: -------------------------------------------------------------------------------- 1 | 1.000000 -0.000051 2 | 0.989166 0.001996 3 | 0.975819 0.004501 4 | 0.957448 0.007991 5 | 0.931599 0.013004 6 | 0.901654 0.018957 7 | 0.870818 0.025167 8 | 0.839998 0.031363 9 | 0.809336 0.037430 10 | 0.778839 0.043301 11 | 0.748503 0.048933 12 | 0.718326 0.054243 13 | 0.688314 0.059175 14 | 0.658443 0.063716 15 | 0.628653 0.067839 16 | 0.598855 0.071530 17 | 0.569004 0.074792 18 | 0.539074 0.077652 19 | 0.509071 0.080095 20 | 0.479015 0.082100 21 | 0.448934 0.083641 22 | 0.418877 0.084690 23 | 0.388863 0.085217 24 | 0.358908 0.085197 25 | 0.329049 0.084600 26 | 0.299321 0.083399 27 | 0.269760 0.081570 28 | 0.240419 0.079081 29 | 0.211388 0.075895 30 | 0.182766 0.071967 31 | 0.154699 0.067243 32 | 0.127404 0.061670 33 | 0.101193 0.055204 34 | 0.076503 0.047832 35 | 0.054060 0.039675 36 | 0.034881 0.031085 37 | 0.020059 0.022737 38 | 0.010052 0.015405 39 | 0.004208 0.009557 40 | 0.001251 0.005050 41 | 0.000113 0.001479 42 | 0.000123 -0.001494 43 | 0.001002 -0.004085 44 | 0.002793 -0.006422 45 | 0.006019 -0.008524 46 | 0.011960 -0.010609 47 | 0.022698 -0.012727 48 | 0.040333 -0.014554 49 | 0.064233 -0.015717 50 | 0.091882 -0.016173 51 | 0.121305 -0.016067 52 | 0.151560 -0.015570 53 | 0.182231 -0.014790 54 | 0.213149 -0.013797 55 | 0.244240 -0.012649 56 | 0.275464 -0.011392 57 | 0.306798 -0.010061 58 | 0.338230 -0.008690 59 | 0.369725 -0.007318 60 | 0.401184 -0.005962 61 | 0.432597 -0.004640 62 | 0.463976 -0.003366 63 | 0.495325 -0.002147 64 | 0.526644 -0.000994 65 | 0.557930 0.000082 66 | 0.589182 0.001071 67 | 0.620390 0.001960 68 | 0.651547 0.002731 69 | 0.682648 0.003367 70 | 0.713694 0.003847 71 | 0.744690 0.004152 72 | 0.775644 0.004269 73 | 0.806546 0.004190 74 | 0.837373 0.003898 75 | 0.868134 0.003359 76 | 0.898985 0.002532 77 | 0.929868 0.001410 78 | 0.956572 0.000219 79 | 0.975393 -0.000742 80 | 0.988983 -0.001472 81 | 1.000000 -0.002051 -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/dae51polars/dae51_polarfits.py: -------------------------------------------------------------------------------- 1 | "dae51_polarfits.py" 2 | from __future__ import print_function 3 | from builtins import zip 4 | from builtins import range 5 | import numpy as np 6 | import pandas as pd 7 | import matplotlib.pyplot as plt 8 | from gpfit.fit import fit 9 | import sys 10 | from gpkitmodels.GP.aircraft.wing.wing import Wing 11 | from gpkitmodels.GP.aircraft.prop.propeller import ActuatorProp 12 | import inspect 13 | import os 14 | 15 | GENERATE = True 16 | plt.rcParams.update({'font.size':15}) 17 | 18 | def text_to_df(filename): 19 | "parse XFOIL polars and concatente data in DataFrame" 20 | lines = list(open(filename)) 21 | for i, l in enumerate(lines): 22 | lines[i] = l.split("\n")[0] 23 | for j in 10-np.arange(9): 24 | if " "*j in lines[i]: 25 | lines[i] = lines[i].replace(" "*j, " ") 26 | if "---" in lines[i]: 27 | start = i 28 | data = {} 29 | titles = lines[start-1].split(" ")[1:] 30 | for t in titles: 31 | data[t] = [] 32 | 33 | for l in lines[start+1:]: 34 | for i, v in enumerate(l.split(" ")[1:]): 35 | data[titles[i]].append(v) 36 | 37 | df = pd.DataFrame(data) 38 | return df 39 | 40 | def fit_setup(Re_range): 41 | "set up x and y parameters for gp fitting" 42 | CL = [] 43 | CD = [] 44 | RE = [] 45 | fig, ax = plt.subplots() 46 | for r in Re_range: 47 | dataf = text_to_df("dae51.ncrit09.Re%dk.pol" % r) 48 | if r < 150: 49 | cl = dataf["CL"].values.astype(np.float) 50 | cd = dataf["CD"].values.astype(np.float) 51 | CL.append(np.hstack([cl[cl >= 1.0]]*1)) 52 | CD.append(np.hstack([cd[cl >= 1.0]]*1)) 53 | elif r < 200: 54 | cl = dataf["CL"].values.astype(np.float) 55 | cd = dataf["CD"].values.astype(np.float) 56 | CL.append(cl[cl >= 0.9]) 57 | CD.append(cd[cl >= 0.9]) 58 | else: 59 | CL.append(dataf["CL"].values.astype(np.float)) 60 | CD.append(dataf["CD"].values.astype(np.float)) 61 | ax.plot(dataf["CL"].values.astype(np.float), dataf["CD"].values.astype(np.float)) 62 | ax.legend(["%d" % re for re in Re_range]) 63 | RE.append([r*1000.0]*len(CL[-1])) 64 | 65 | fig.savefig("polarstest.pdf") 66 | 67 | u1 = np.hstack(CL) 68 | u2 = np.hstack(RE) 69 | w = np.hstack(CD) 70 | u = [u1, u2] 71 | xx = np.log(u2) 72 | x = np.log(u) 73 | y = np.log(w) 74 | return x, y 75 | 76 | def return_fit(cl, re): 77 | "polar fit for the dae51 airfoil" 78 | cd = (3.0902e14*cl**-.763161*re**-5.00624 79 | + 8.06832e-09*cl**6.32059*re**-0.810816 80 | + 2.65501*cl**42.1738*re**-2.87665 )**(1/6.1539) 81 | # SMA function, K=3, max RMS error = 0.09734 82 | return cd 83 | 84 | def plot_fits(re, cnstr, x, y): 85 | "plot fit compared to data" 86 | # colors = ["k", "m", "b", "g", "y"] 87 | colors = ["#084081", "#0868ac", "#2b8cbe", "#4eb3d3", "#7bccc4"] 88 | assert len(re) == len(colors) 89 | lre, ind = np.unique(X[1], return_index=True) 90 | xre = np.exp(lre) 91 | xcl = [np.exp(X[0][ind[i-1]:ind[i]]) for i in range(1, len(ind))] 92 | cds = [np.exp(Y[ind[i-1]:ind[i]]) for i in range(1, len(ind))] 93 | yfit = cnstr.evaluate(x) 94 | cdf = [np.exp(yfit[ind[i-1]:ind[i]]) for i in range(1, len(ind))] 95 | fig, ax = plt.subplots() 96 | i = 0 97 | for r, cl, cd, fi in zip(xre, xcl, cds, cdf): 98 | roundre = int(np.round(r)/1000) 99 | if roundre in re: 100 | ax.plot(cl, cd, "o", mec=colors[i], mfc="none", mew=1.5) 101 | ax.plot(cl, fi, c=colors[i], label="Re = %dk" % roundre, lw=2) 102 | i += 1 103 | ax.set_xlabel("$C_L$") 104 | ax.set_ylabel("$c_{d_p}$") 105 | ax.legend(loc=2) 106 | ax.grid() 107 | return fig, ax 108 | 109 | if __name__ == "__main__": 110 | Re = np.array([50, 75, 125, 150, 200, 300, 400, 500, 600, 700]) 111 | X, Y = fit_setup(Re) # call fit(X, Y, 4, "SMA") to get fit 112 | np.random.seed(0) 113 | cn, err = fit(X, Y, 3, "SMA") 114 | print("RMS error: %.5f" % err) 115 | df = cn.get_dataframe() 116 | if GENERATE: 117 | path = os.path.dirname(inspect.getfile(ActuatorProp)) 118 | df.to_csv(path + os.sep + "dae51_fitdata.csv", index=False) 119 | else: 120 | df.to_csv("dae51fitdata.csv", index=False) 121 | 122 | # replot = np.array([150, 200, 300, 350, 400]) 123 | replot = np.array([300, 350, 400, 450, 500]) 124 | F, A = plot_fits(replot, cn, X, Y) 125 | if len(sys.argv) > 1: 126 | path = sys.argv[1] 127 | F.savefig(path + "dae51polarfit1.eps", bbox_inches="tight") 128 | else: 129 | F.savefig("dae51polarfit1.eps", bbox_inches="tight") -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/dae51polars/polarstest.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/aircraft/prop/dae51polars/polarstest.pdf -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/prop/propeller.py: -------------------------------------------------------------------------------- 1 | " propeller model " 2 | from builtins import range 3 | from numpy import pi 4 | from gpkit import Model, Variable,Vectorize,parse_variables, SignomialsEnabled, SignomialEquality 5 | from gpkit.constraints.tight import Tight as TCS 6 | from gpfit.fit_constraintset import XfoilFit 7 | import os 8 | import pandas as pd 9 | 10 | 11 | 12 | class BladeElementPerf(Model): 13 | """ Single element of a propeller blade 14 | 15 | Variables 16 | --------- 17 | dT [lbf] thrust 18 | eta_i [-] local induced efficiency 19 | dQ [N*m] torque 20 | omega [rpm] propeller rotation rate 21 | Wa [m/s] Axial total relative velocity 22 | Wt [m/s] Tangential total relative velocity 23 | Wr [m/s] Total relative velocity 24 | va [m/s] Axial induced velocity 25 | vt [m/s] Tangential induced velocity 26 | G [m^2/s] Circulation 27 | cl [-] local lift coefficient 28 | cd [-] local drag coefficient 29 | B 2 [-] number of blades 30 | r [m] local radius 31 | lam_w [-] advance ratio 32 | eps [-] blade efficiency 33 | AR_b [-] blade aspect ratio 34 | AR_b_max 50 [-] max blade aspect ratio 35 | Re [-] blade reynolds number 36 | f [-] intermediate tip loss variable 37 | F [-] Prandtl tip loss factor 38 | cl_max .6 [-] max airfoil cl 39 | dr [m] length of blade element 40 | M [-] Mach number 41 | a 295 [m/s] Speed of sound at altitude 42 | 43 | """ 44 | 45 | @parse_variables(__doc__, globals()) 46 | def setup(self,static, state): 47 | V = state.V 48 | rho = state.rho 49 | R = static.R 50 | mu = state.mu 51 | path = os.path.dirname(__file__) 52 | fd = pd.read_csv(path + os.sep + "dae51_fitdata.csv").to_dict( 53 | orient="records")[0] 54 | c = static.c 55 | constraints = [TCS([Wa>=V + va]), 56 | TCS([Wt + vt<=omega*r]), 57 | TCS([G == (1./2.)*Wr*c*cl]), 58 | F == (2./pi)*(1.01116*f**.0379556)**(10), #This is the GP fit of arccos(exp(-f)) 59 | M == Wr/a, 60 | lam_w == (r/R)*(Wa/Wt), 61 | va == vt*(Wt/Wa), 62 | eps == cd/cl, 63 | TCS([dQ >= rho*B*G*(Wa+eps*Wt)*r*dr]), 64 | AR_b == R/c, 65 | AR_b <= AR_b_max, 66 | Re == Wr*c*rho/mu, 67 | eta_i == (V/(omega*r))*(Wt/Wa), 68 | TCS([f+(r/R)*B/(2*lam_w) <= (B/2.)*(1./lam_w)]), 69 | XfoilFit(fd, cd, [cl,Re], name="polar"), 70 | cl <= cl_max 71 | ] 72 | with SignomialsEnabled(): 73 | constraints += [SignomialEquality(Wr**2,(Wa**2+Wt**2)), 74 | TCS([dT <= rho*B*G*(Wt-eps*Wa)*dr]), 75 | TCS([vt**2*F**2*(1.+(4.*lam_w*R/(pi*B*r))**2) >= (B*G/(4.*pi*r))**2]), 76 | ] 77 | return constraints, state 78 | 79 | 80 | 81 | class BladeElementProp(Model): 82 | """ Performance for a propeller with multiple elements 83 | 84 | Variables 85 | --------- 86 | Mtip .5 [-] Max tip mach number 87 | omega_max 10000 [rpm] maximum rotation rate 88 | eta [-] overall efficiency 89 | omega [rpm] rotation rate 90 | T [lbf] total thrust 91 | Q [N*m] total torque 92 | """ 93 | @parse_variables(__doc__, globals()) 94 | def setup(self,static, state, N=5): 95 | with Vectorize(N): 96 | blade = BladeElementPerf(static, state) 97 | 98 | 99 | constraints = [blade.dr == static.R/(N), 100 | blade.omega == omega, 101 | blade.r[0] == static.R/(2.*N)] 102 | 103 | for n in range(1,N): 104 | constraints += [TCS([blade.r[n] >= blade.r[n-1] + static.R/N]), 105 | blade.eta_i[n] == blade.eta_i[n-1], 106 | ] 107 | 108 | constraints += [TCS([Q >= sum(blade.dQ)]), 109 | eta == state.V*T/(omega*Q), 110 | blade.M[-1] <= Mtip, 111 | static.T_m >= T, 112 | omega <= omega_max 113 | ] 114 | 115 | with SignomialsEnabled(): 116 | constraints += [TCS([T <= sum(blade.dT)])] 117 | 118 | return constraints, blade 119 | -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/tail/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/aircraft/tail/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/tail/tail_boom_flex.py: -------------------------------------------------------------------------------- 1 | " tail boom flexibility " 2 | from numpy import pi 3 | from gpkit import Model, parse_variables, SignomialsEnabled 4 | 5 | class TailBoomFlexibility(Model): 6 | """ Tail Boom Flexibility Model 7 | 8 | Variables 9 | --------- 10 | Fne [-] tail boom flexibility factor 11 | deda [-] wing downwash derivative 12 | SMcorr 0.55 [-] corrected static margin 13 | sph1 [-] flexibility helper variable 1 14 | sph2 [-] flexibility helper variable 2 15 | 16 | LaTex Strings 17 | ------------- 18 | Fne F_{\mathrm{NE}} 19 | deda d\\epsilon/d\\alpha 20 | SMcorr SM_{\\mathrm{corr}} 21 | 22 | """ 23 | @parse_variables(__doc__, globals()) 24 | def setup(self, htail, hbending, wing): 25 | mh = htail.mh 26 | mw = wing.mw 27 | Vh = htail.Vh 28 | th = hbending.th 29 | CLhmin = htail.CLhmin 30 | CLwmax = wing.planform.CLmax 31 | Sw = wing.planform.S 32 | bw = wing.planform.b 33 | lh = htail.lh 34 | CM = wing.planform.CM 35 | 36 | constraints = [ 37 | Fne >= 1 + mh*th, 38 | sph1*(mw*Fne/mh/Vh) + deda <= 1, 39 | sph2 <= Vh*CLhmin/CLwmax, 40 | # (sph1 + sph2).mono_lower_bound({"sph1": .48, "sph2": .52}) >= ( 41 | # SMcorr + wing["C_M"]/wing["C_{L_{max}}"]), 42 | deda >= mw*Sw/bw/4/pi/lh] 43 | 44 | with SignomialsEnabled(): 45 | constraints.extend([sph1 + sph2 >= SMcorr + CM/CLwmax]) 46 | 47 | return constraints 48 | -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/wing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/aircraft/wing/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/wing/boxspar.py: -------------------------------------------------------------------------------- 1 | " box spar " 2 | from gpkit import parse_variables, SignomialsEnabled 3 | from gpkitmodels.GP.aircraft.wing.boxspar import BoxSpar as BoxSparGP 4 | 5 | #pylint: disable=exec-used, undefined-variable, unused-argument, invalid-name 6 | 7 | class BoxSpar(BoxSparGP): 8 | """ Box Spar Model 9 | 10 | Variables of length N-1 11 | ----------------------- 12 | J [m^4] spar x polar moment of inertia 13 | 14 | """ 15 | @parse_variables(__doc__, globals()) 16 | def setup(self, N, surface): 17 | self.boxspar = BoxSparGP.setup(self, N=N, surface=surface) 18 | 19 | cave = self.cave 20 | tau = self.tau 21 | w = self.w 22 | tshear = self.tshear 23 | 24 | with SignomialsEnabled(): 25 | constraints = [J <= cave*tau*w*tshear/3*(cave*tau + w)] 26 | 27 | return self.boxspar, constraints 28 | -------------------------------------------------------------------------------- /gpkitmodels/SP/aircraft/wing/wing.py: -------------------------------------------------------------------------------- 1 | " wing.py " 2 | import numpy as np 3 | from gpkitmodels.GP.aircraft.wing.wing import Wing as WingGP 4 | from gpkit import parse_variables, SignomialsEnabled 5 | 6 | #pylint: disable=attribute-defined-outside-init, invalid-name 7 | 8 | class Wing(WingGP): 9 | """ SP wing model 10 | 11 | Variables 12 | --------- 13 | mw [-] span wise effectiveness 14 | 15 | """ 16 | @parse_variables(__doc__, globals()) 17 | def setup(self, N=5): 18 | self.wing = WingGP.setup(self, N=N) 19 | with SignomialsEnabled(): 20 | constraints = [mw*(1 + 2/self.planform["AR"]) >= 2*np.pi] 21 | 22 | return self.wing, constraints 23 | -------------------------------------------------------------------------------- /gpkitmodels/SP/atmosphere/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/SP/atmosphere/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/SP/atmosphere/atmosphere.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from gpkit import Model, Variable, SignomialsEnabled, SignomialEquality, VarKey, units 3 | from gpkit.constraints.bounded import Bounded 4 | from gpkit import Vectorize 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | 9 | class Atmosphere(Model): 10 | """ 11 | Atmosphere models borrowed from Tony Tao, 2017. 12 | Fits included for all variables. Density and viscosity are demonstrated. 13 | The rest are commented, to be used with modeler's discretion! 14 | Boundedness will vary depending on model application for other variables. 15 | Signomial equalities are fast and reliable here! 16 | """ 17 | def setup(self): 18 | # Env. constants 19 | alt_top = Variable('h_{top}',10000,'m','highest altitude valid') 20 | #a_MSL = Variable('a_{MSL}',340.20,'m/s','Speed of sound at MSL') 21 | mu_MSL = Variable('\\mu_{MSL}', 1.778e-5, "kg/m/s", 'dynamic viscosity at MSL', pr=4.) 22 | #nu_MSL = Variable('\\nu_{MSL}', 1.4524e-5, 'm^2/s', 'kinematic viscosity at MSL') 23 | #T_MSL = Variable('T_{MSL}', 288.19, 'K', 'temperature at MSL') 24 | rho_MSL = Variable("\\rho_{MSL}", 1.2256, "kg/m^3", "density of air at MSL", pr=5.) 25 | #p_MSL = Variable('P_{MSL}', 101308, 'Pa', 'pressure at MSL') 26 | 27 | alt = Variable('h','m','altitude') 28 | #a = Variable('a','m/s','Speed of sound') 29 | mu = Variable('\\mu', "kg/m/s", 'dynamic viscosity', pr=4.) 30 | #nu = Variable('\\nu', 'm^2/s', 'kinematic viscosity') 31 | #p = Variable('P', 'Pa', 'pressure') 32 | #T = Variable('T', 'K', 'temperature ') 33 | rho = Variable("\\rho", "kg/m^3", "density of air", pr=5.) 34 | 35 | 36 | # Defining ratios needed for constraints 37 | alt_rat = alt/alt_top 38 | #a_rat = a/a_MSL 39 | mu_rat = mu/mu_MSL 40 | #nu_rat = nu/nu_MSL 41 | #p_rat = p/p_MSL 42 | rho_rat = rho/rho_MSL 43 | #T_rat = T/T_MSL 44 | 45 | constraints = [] 46 | with SignomialsEnabled(): 47 | constraints += [ 48 | alt <= alt_top, 49 | #a_rat ** -0.0140 >= 1.00 * (alt_rat) ** 1.20e-05 + 0.00173 * (alt_rat) ** 1.13, 50 | SignomialEquality(mu_rat ** -0.00795, 1.00 * (alt_rat) ** 1.33e-05 + 0.00156 * (alt_rat) ** 1.17), 51 | #nu_rat ** 0.00490 >= 1.00 * (alt_rat) ** 1.67e-05,# + 0.00424 * (alt_rat) ** 1.10, 52 | #p_rat ** -0.00318 >= 1.00 * (alt_rat) ** 2.18e-05,# + 0.00416 * (alt_rat) ** 1.12, 53 | SignomialEquality(rho_rat ** -0.00336, 1.00 * (alt_rat) ** 1.72e-05 + 0.00357 * (alt_rat) ** 1.11), 54 | #T_rat ** -0.00706 >= 1.00 * (alt_rat) ** 1.21e-05,# + 0.00173 * (alt_rat) ** 1.13, 55 | ] 56 | 57 | return constraints 58 | 59 | if __name__ == "__main__": 60 | m = Atmosphere() 61 | m.substitutions.update({'h':5000*units('m')}) 62 | m.cost = m['\\mu']*m['\\rho'] 63 | sol = m.localsolve(verbosity = 3) 64 | print(sol.table()) 65 | -------------------------------------------------------------------------------- /gpkitmodels/__init__.py: -------------------------------------------------------------------------------- 1 | from gpkit import Variable 2 | g = Variable("g", 9.81, "m/s^2", "earth surface gravitational acceleration", 3 | constant=True) 4 | -------------------------------------------------------------------------------- /gpkitmodels/misc/Economic Order Quantity/README.md: -------------------------------------------------------------------------------- 1 | ```python 2 | #inPDF: skip 3 | from gpkit import Variable 4 | ``` 5 | # Economic Order Quantity 6 | Reference: Hopp, Wallace J., and Mark L. Spearman. Factory physics. Waveland Press, 2011. 7 | 8 | Econonic order quantity (EOQ) expresses the fundamental tradeoff between 9 | setup costs and holding costs. Small lot sizes have large setup costs per unit, 10 | but large lot sizes incur holding costs. 11 | 12 | The variables are: 13 | ```python 14 | D = Variable("D", "count/year", "Demand rate") 15 | c = Variable("c", "USD/count", "per unit production cost") 16 | A = Variable("A", "USD", "setup cost") 17 | h = Variable("h", "USD/count/year", "holding cost") 18 | Q = Variable("Q", "count", "lot size") 19 | Y = Variable("Y", "USD/year", "cost per year") 20 | ``` 21 | 22 | The cost per year is simply the sum of holding, setup, and production costs, 23 | $$Y(Q) = \frac{hQ}{2} + \frac{AD}{Q} + cD$$ 24 | Or in gpkit, 25 | ```python 26 | eoq = [Y >= h*Q/2 + A*D/Q + c*D] 27 | ``` 28 | 29 | Now create a model that minimizes cost 30 | and substitute in the values given in Factory Physics. 31 | ```python 32 | from gpkit import Model, units 33 | m = Model(Y, eoq) 34 | m.substitutions.update({D: 1000, # /units("year"), 35 | c: 250, # *units("USD"), 36 | A: 500, # *units("USD"), 37 | h: 35}) # *units("USD/year")}) 38 | sol = m.solve() 39 | # would like to assert here that 40 | # sol.subinto((2*A*D/h)**0.5 =~= sol.subinto(Q) 41 | ``` 42 | ```python 43 | #inPDF: replace with sol.generated.tex 44 | with open("sol.generated.tex", "w") as f: 45 | f.write(m.solution.table(latex=True)) 46 | ``` 47 | Now sweep $Q$ to see the tradoff curve from Factory Physics 48 | ```python 49 | import numpy as np 50 | m.substitutions.update({ 51 | Q: ("sweep", np.logspace(np.log10(10), np.log10(500), 25))}) 52 | sol = m.solve() 53 | ``` 54 | ```python 55 | #inPDF: skip 56 | import matplotlib.pyplot as plt 57 | plt.figure() 58 | plt.plot(sol("Q"), sol("Y")/sol("D") - sol("c")) 59 | plt.ylim((0, 30)) 60 | plt.xlabel(Q) 61 | plt.ylabel("Holding plus Setup Cost [$/unit]") 62 | plt.savefig("qsweep.pdf") 63 | plt.close() 64 | ``` 65 | ![Cost vs Order Quantity](qsweep.pdf) 66 | -------------------------------------------------------------------------------- /gpkitmodels/misc/Economic Order Quantity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Economic Order Quantity/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/misc/Economic Order Quantity/eoq.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Economic Order Quantity/eoq.pdf -------------------------------------------------------------------------------- /gpkitmodels/misc/Economic Order Quantity/qsweep.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Economic Order Quantity/qsweep.pdf -------------------------------------------------------------------------------- /gpkitmodels/misc/Moment of Inertia (cylindrical beam)/MoICylinder.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Moment of Inertia (cylindrical beam)/MoICylinder.PNG -------------------------------------------------------------------------------- /gpkitmodels/misc/Moment of Inertia (cylindrical beam)/README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ------------------------- 3 | 4 | In this example, we express the area moment of inertia (I) of a hollow cylindrical section as a functional of its physical dimensions to formulate the bending section design problem in a GP form. 5 | 6 | ```python 7 | 8 | import numpy as np 9 | from gpkit import Variable, Model, units 10 | from gpkit.constraints.tight import Tight 11 | import matplotlib.pyplot as plt 12 | 13 | Tight.reltol = 1e-2 14 | 15 | class Beam(Model): 16 | def __init__(self): 17 | 18 | ``` 19 | 20 | 21 | Hollow Cylinder Bending Inertia Equations 22 | -------------------------- 23 | 24 | The inertia of a beam describes the stiffness of the beam in bending. If we desire to create a beam bending model in GP, it is a critical quantity. Since we are also interested in structural efficiency, we want to minimize the cross-sectional area of the section, which describes the weight of the beam along with the density of the beam and its length. 25 | 26 | 27 | 28 | \begin{figure}[h] 29 | \centering 30 | \includegraphics[width = 5cm]{MoICylinder} 31 | \caption{The cross-sectional parameters of a hollow cylindrical section} 32 | \label{fig:cylinder} 33 | \end{figure} 34 | 35 | 36 | 37 | 38 | The equations for the inertia and cross-sectional area of a hollow cylindrical section are shown in Figure 1 are: 39 | 40 | \begin{equation} 41 | \tag{1} 42 | A = \pi(r_o^2 - r_i^2) 43 | \end{equation} 44 | 45 | \begin{equation} 46 | \tag{2} 47 | I = \frac{1}{4}\pi(r_o^4 - r_i^4) 48 | \end{equation} 49 | 50 | where $r_o$ is the outer radius, $r_i$ is the inner radius, $A$ is the cross-sectional area, and $I$ is the moment of inertia of the section. The form as-is is not GP compatible by the following reasoning. 51 | 52 | If we say that greater $I$ is good and greater $A$ is bad, then the right bounding inequalities for the expressions above are: 53 | 54 | \begin{equation} 55 | \tag{3} 56 | A \geq \pi(r_o^2 - r_i^2) 57 | \end{equation} 58 | 59 | \begin{equation} 60 | \tag{4} 61 | I \leq \frac{1}{4}\pi(r_o^4 - r_i^4) 62 | \end{equation} 63 | 64 | since we are trying to maximize $I$ and minimize $A$. Equation 4 is GP-compatible, since we can add $\frac{1}{4}\pi r_i^4$ to both sides of the equation and have a valid posynomial. However, no such transformation is possible for Equation 3. And if we changed the sign of the inequality in Equation 3, then $A$ would go off to 0 ($10^{-\infty}$), a non-physical solution. 65 | 66 | GP Formulation 67 | ------------------- 68 | 69 | We begin by rearranging Equation 3 to get the following: 70 | 71 | \begin{equation} 72 | \tag{5} 73 | \frac{A}{\pi} = r_o^2 - r_i^2 74 | \end{equation} 75 | 76 | We then split Equation 4 as shown in Equation 6. Note that we limit $I$ to be less than the right hand side (RHS) of the equation, for later conversion into posynomial form. This allows us to use the cross-sectional area relation from Equation 5 to describe the inertia, yielding Equation 7. 77 | 78 | \begin{equation} 79 | \tag{6} 80 | I \leq \frac{1}{4} \pi (r_o^2 - r_i^2 ) (r_o^2 + r_i^2) 81 | \end{equation} 82 | 83 | \begin{equation} 84 | \tag{7} 85 | I \leq \frac{1}{4} A (r_o^2 + r_i^2) 86 | \end{equation} 87 | 88 | We use the geometric mean to make an approximation for $(r_o^2 + r_i^2)$. 89 | 90 | \begin{equation} 91 | \tag{8} 92 | 2r_or_i \leq r_o^2 + r_i^2 93 | \end{equation} 94 | 95 | Substituting Equation 8 into Equation 7, we can get a conservative bound on the moment of inertia that is GP-compatible. 96 | 97 | \begin{equation} 98 | \tag{9} 99 | I \leq \frac{Ar_or_i}{2} 100 | \end{equation} 101 | 102 | Rearranging the area relation in Equation 3: 103 | 104 | \begin{equation} 105 | \tag{10} 106 | r_i^2 + \frac{A}{\pi} \leq r_o^2 107 | \end{equation} 108 | 109 | We substitute for $r_i$ from Equation 11 in Equation 12: 110 | 111 | \begin{equation} 112 | \tag{11} 113 | r_i^2 \geq \frac{4I^2}{A^2r_o^2} 114 | \end{equation} 115 | 116 | \begin{equation} 117 | \tag{12} 118 | \frac{4I^2}{A^2r_o^2} + \frac{A}{\pi} \leq r_o^2 119 | \end{equation} 120 | 121 | The final formulation is as follows: 122 | 123 | \begin{equation} 124 | \tag{13} 125 | \bar{W} \geq \rho A 126 | \end{equation} 127 | 128 | \begin{equation} 129 | \tag{14} 130 | \frac{4I^2}{A^2r_o^2} + \frac{A}{\pi} \leq r_o^2 131 | \end{equation} 132 | 133 | ```python 134 | 135 | I = Variable("I", "m^4", "Moment of inertia") 136 | A = Variable("A", "m^2", "Cross-sectional area") 137 | ro = Variable("r_o", "m", "Outer radius") 138 | romax = Variable("r_o_{max}", "m", "Maximum outer radius") 139 | 140 | constraints = [Tight([4*I**2/(A**2*ro**2) + A/np.pi <= ro**2]), 141 | ro <= romax] 142 | 143 | # Minimizing cross-sectional area => min(weight) 144 | objective = A 145 | 146 | Model.__init__(self,objective,constraints) 147 | ``` 148 | 149 | 150 | This formulation is GP-compatible, because the equations are of monomial and posynomial forms, and we have the right bounds on the two parameters we care about. Greater moment of inertia $I$ is good, but it is upper bounded by the outer radius. And area $A$ is bounded because it is directly proportional to the weight of the structure in question, which we want to minimize. Using Equations 13 and 14, we can give conservative approximations for the area moments of inertia of hollow cylindrical beams, allowing us to calculate their deflections under bending loads. 151 | 152 | However, it is important to note that the geometric mean has been used in Equation 8 to give a conservative bound on the moment of inertia, which means that the solution is not exact. Furthermore, it doesn't seem absolutely clear that the constraint in Equation 14 will be always tight, but results from problems that use this models show empirically that it is always tight. The engineering intuition holds! 153 | 154 | ```python 155 | 156 | 157 | if __name__ == "__main__": 158 | M = Beam() 159 | M.substitutions.update({"I": 10**-4}) 160 | M.substitutions.update({"r_o_{max}": 0.2}) 161 | sol = M.sweep({"I": np.logspace(-10, -3, 100)}, skipsweepfailures=True) 162 | print(sol.table()) 163 | 164 | I, A, ro = sol("I"), sol("A"), sol("r_o") 165 | plt.semilogx(I, (4*I**2/(A**2*ro**2) + A/np.pi - ro**2)/(ro**2)) 166 | plt.xlabel("Moment of inertia [m^-4]") 167 | plt.ylabel("Tightness of constraint") 168 | plt.ylim([-2.5e-5, 1e-5]) 169 | plt.title("Checking constraint tightness (normalized by ro**2)") 170 | plt.savefig("tightness.png") 171 | # plt.show() 172 | 173 | ``` 174 | 175 | \begin{figure}[h] 176 | \centering 177 | \includegraphics[width = 10cm]{tightness} 178 | \caption{Verification that Equation 14 stays tight over a range of $I$ values} 179 | \label{fig:cylinder} 180 | \end{figure} 181 | -------------------------------------------------------------------------------- /gpkitmodels/misc/Moment of Inertia (cylindrical beam)/moi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Moment of Inertia (cylindrical beam)/moi.pdf -------------------------------------------------------------------------------- /gpkitmodels/misc/Moment of Inertia (cylindrical beam)/moi.py: -------------------------------------------------------------------------------- 1 | "get the python code from the markdown file" 2 | from gpkit.tools import mdparse 3 | exec(mdparse("moi.md")) 4 | -------------------------------------------------------------------------------- /gpkitmodels/misc/Moment of Inertia (cylindrical beam)/tightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Moment of Inertia (cylindrical beam)/tightness.png -------------------------------------------------------------------------------- /gpkitmodels/misc/Net Present Value/README.md: -------------------------------------------------------------------------------- 1 | # NPV Model 2 | ```python 3 | #inPDF: skip 4 | 5 | from numpy import pi 6 | from gpkit import VectorVariable, Variable, Model, units 7 | import gpkit 8 | from gpkit.tools import te_exp_minus1 9 | 10 | class NPV(Model): 11 | def __init__(self, **kwargs): 12 | 13 | 14 | ``` 15 | # Variables 16 | 17 | ```python 18 | #inPDF: replace with vartable.generated.tex 19 | N = 3 # number of payments 20 | npv = Variable('NPV', 'USD', 'net present value') 21 | PV = Variable('PV', 'USD', 'present value') 22 | C = VectorVariable(N, 'C', 'USD', 'cash flow') 23 | C_r = Variable('C_r', 5e6, 'USD/years', 'cash flow rate') 24 | r = Variable('r', 0.1, '1/years', 'interest rate') 25 | t = VectorVariable(N, 't', 'years', 'time') 26 | 27 | ``` 28 | 29 | # Problem Set Up 30 | 31 | Let's assume that we want the Net Present Value (NPV) to be \$10 Million. NPV can be expressed by: 32 | 33 | $$ \text{NPV} = \displaystyle\sum\limits_{i=0}^N \text{PV}_i $$ . 34 | 35 | Let's assume that the time periods of the payments are not equal but that the PV$_{i}$ are equal and that there are 3 payments. PV can be expressed by 36 | 37 | $$\text{PV} = \text{C} e^{-rt} $$. 38 | 39 | With these assumptions we can get rid of the $i$ subscript and claim that 40 | 41 | $$\text{NPV} \leq 3\text{PV} $$ 42 | 43 | Now the problem becomes solving for the length of each time period such that each PV is equal. Let's assume that the same payment, C, is made at each payment period and that C is given. This allows us to write 44 | 45 | $$ \text{C} \leq \text{PV}e^{r t_i} $$ 46 | 47 | Let's assume that $t_0$ is the time of evaulation of the NPV and that $t_i$ is when every payment is made and every PV evaluated. This means that 48 | 49 | $$ \begin{bmatrix} \Delta t_1 = t_1 - t_0 \\ 50 | \Delta t_2 = t_2 - t_1 \\ 51 | \Delta t_3 = t_3 - t_2 52 | \end{bmatrix} $$ 53 | 54 | If there is a cash flow rate, C$_r$ then, 55 | 56 | $$ \text{C} \leq\text{C}_r \Delta t $$ 57 | 58 | 59 | # Constraints and Objective 60 | 61 | ```python 62 | #inPDF: replace with model.generated.tex 63 | 64 | constraints = [N*PV >= npv, 65 | C >= PV*(te_exp_minus1(r*t, 4) + 1), 66 | t >= t.left + C/C_r] 67 | 68 | cost = 1/npv 69 | ``` 70 | ```python 71 | #inPDF: replace with sol.generated.tex 72 | 73 | Model.__init__(self, cost, constraints, **kwargs) 74 | 75 | @classmethod 76 | def test(cls): 77 | "test the model by solving it" 78 | return cls().solve() 79 | 80 | if __name__ == "__main__": 81 | M = NPV() 82 | SOL = M.solve() 83 | with open("vartable.generated.tex", "w") as f: 84 | f.write("\\begin{tabbing}\n XXXXXXXXXX \\= \\kill\n") 85 | for var in M.varkeys: 86 | f.write("$%s$ : [%s] %s \\\\\n" % (var.name, var.units, var.label)) 87 | f.write("\\end{tabbing}") 88 | with open("model.generated.tex", "w") as f: 89 | f.write("$$ %s $$" % M.latex(excluded=["models"]).replace("[ll]", "{ll}")) 90 | with open("sol.generated.tex", "w") as f: 91 | f.write(SOL.table(latex=True)) 92 | ``` 93 | -------------------------------------------------------------------------------- /gpkitmodels/misc/Net Present Value/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Net Present Value/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/misc/Net Present Value/npv.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/misc/Net Present Value/npv.pdf -------------------------------------------------------------------------------- /gpkitmodels/misc/Net Present Value/npv.py: -------------------------------------------------------------------------------- 1 | "get the python code from the markdown file" 2 | from gpkit.tools import mdparse 3 | exec(mdparse("npv.md")) 4 | -------------------------------------------------------------------------------- /gpkitmodels/misc/README.md: -------------------------------------------------------------------------------- 1 | Miscellanous models 2 | =================== 3 | 4 | This folder contains smaller models which have not been designed as part of a larger engineering system. Many of them are documented with GPkit's MarkDown literate programming framework, using the shell script and latex template in this directory. 5 | -------------------------------------------------------------------------------- /gpkitmodels/misc/make_gpmd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Simple command to generate GPkit MarkDown reports 3 | python -c "from gpkit.tools import mdmake; exec mdmake('README.md')" 4 | pandoc $1.md.tex.md --template ../default.latex -o $1.pdf 5 | -------------------------------------------------------------------------------- /gpkitmodels/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/convexengineering/gplibrary/3229a68974b37d818a0fe1b2bad9b448f40f7323/gpkitmodels/tools/__init__.py -------------------------------------------------------------------------------- /gpkitmodels/tools/fit_constraintset.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from builtins import str 3 | from builtins import zip 4 | from builtins import range 5 | import numpy as np 6 | from gpkit import ConstraintSet 7 | from gpkit import Variable, NomialArray 8 | from gpkit.small_scripts import unitstr 9 | from .xfoilWrapper import blind_call, single_cl 10 | 11 | class FitCS(ConstraintSet): 12 | def __init__(self, df, ivar, dvars, nobounds=False, err_margin=False, airfoil=False): 13 | self.airfoil = airfoil 14 | self.dvars = dvars 15 | self.ivar = ivar 16 | 17 | K = int(df["K"].iloc[0]) 18 | d = int(df["d"].iloc[0]) 19 | ftype = df["ftype"].iloc[0] 20 | A = np.array(df[["e%d%d" % (k, i) for k in range(1, K+1) for i in 21 | range(1, d+1)]])[0].astype(float) 22 | B = np.array(df[["c%d" % k for k in range(1, K+1)]])[0].astype(float) 23 | 24 | withvector = False 25 | withvar = False 26 | for dv in self.dvars: 27 | if hasattr(dv, "__len__"): 28 | withvector = True 29 | N = len(dv) 30 | else: 31 | withvar = True 32 | if withvector: 33 | if withvar: 34 | self.dvars = np.array([dv if isinstance(dv, NomialArray) else 35 | [dv]*N for dv in self.dvars]).T 36 | else: 37 | self.dvars = np.array(self.dvars).T 38 | else: 39 | self.dvars = np.array([self.dvars]) 40 | monos = [B*NomialArray([(dv**A[k*d:(k+1)*d]).prod() for k in 41 | range(K)]) for dv in self.dvars] 42 | 43 | if err_margin == "Max": 44 | maxerr = float(df["max_err"].iloc[0]) 45 | self.mfac = Variable("m_{fac-fit}", 1 + maxerr, "-", 46 | "max error of " + ivar.descr["label"] 47 | + " fit") 48 | elif err_margin == "RMS": 49 | rmserr = float(df["rms_err"].iloc[0]) 50 | self.mfac = Variable("m_{fac-fit}", 1 + rmserr, "-", 51 | "RMS error of " + ivar.descr["label"] 52 | + " fit") 53 | else: 54 | self.mfac = Variable("m_{fac-fit}", 1.0, "-", "fit factor") 55 | 56 | if ftype == "ISMA": 57 | # constraint of the form 1 >= c1*u1^exp1*u2^exp2*w^(-alpha) + .... 58 | alpha = np.array(df[["a%d" % k for k in 59 | range(1, K+1)]])[0].astype(float) 60 | lhs = 1 61 | rhs = NomialArray([(mono/(ivar/self.mfac)**alpha).sum() for mono 62 | in monos]) 63 | elif ftype == "SMA": 64 | # constraint of the form w^alpha >= c1*u1^exp1 + c2*u2^exp2 +.... 65 | alpha = float(df["a1"].iloc[0]) 66 | lhs = (ivar/self.mfac)**alpha 67 | rhs = NomialArray([mono.sum() for mono in monos]) 68 | elif ftype == "MA": 69 | # constraint of the form w >= c1*u1^exp1, w >= c2*u2^exp2, .... 70 | lhs, rhs = (ivar/self.mfac), NomialArray(monos) 71 | 72 | if K == 1: 73 | # when possible, return an equality constraint 74 | if withvector: 75 | cstrt = [(lh == rh) for rh, lh in zip(rhs, lhs)] 76 | else: 77 | cstrt = (lhs == rhs) 78 | else: 79 | if withvector: 80 | cstrt = [(lh >= rh) for rh, lh in zip(rhs, lhs)] 81 | else: 82 | cstrt = (lhs >= rhs) 83 | 84 | constraints = [cstrt] 85 | if not hasattr(self.mfac, "__len__"): 86 | self.mfac = [self.mfac]*len(self.dvars) 87 | if not hasattr(self.ivar, "__len__"): 88 | self.ivar = [self.ivar]*len(self.dvars) 89 | 90 | self.bounds = [] 91 | for dvar in self.dvars: 92 | bds = {} 93 | for i, v in enumerate(dvar): 94 | bds[v] = [float(df["lb%d" % (i+1)].iloc[0]), 95 | float(df["ub%d" % (i+1)].iloc[0])] 96 | self.bounds.append(bds) 97 | 98 | ConstraintSet.__init__(self, constraints) 99 | 100 | def process_result(self, result, TOL=0.03): 101 | super(FitCS, self).process_result(result) 102 | 103 | 104 | for mfac, dvrs, ivr, bds in zip(self.mfac, self.dvars, self.ivar, 105 | self.bounds): 106 | 107 | if self.airfoil: 108 | runxfoil = True 109 | if ".dat" in self.airfoil: 110 | topline = "load " + self.airfoil + " \n afl \n" 111 | elif "naca" in self.airfoil: 112 | topline = self.airfoil + "\n" 113 | else: 114 | print("Bad airfoil specified") 115 | else: 116 | runxfoil = False 117 | bndwrn = True 118 | 119 | if abs(result["sensitivities"]["constants"][mfac]) < 1e-5: 120 | bndwrn = False 121 | runxfoil = False 122 | 123 | if runxfoil: 124 | cl, re = 0.0, 0.0 125 | for d in dvrs: 126 | if "C_L" in str(d): 127 | cl = result(d) 128 | if "Re" in str(d): 129 | re = result(d) 130 | cdgp = result(ivr) 131 | failmsg = "Xfoil call failed at CL=%.4f and Re=%.1f" % (cl, re) 132 | try: 133 | x = blind_call(topline, cl, re, 0.0) 134 | if "VISCAL: Convergence failed" in x: 135 | print("Convergence Warning: %s" % failmsg) 136 | cd, cl = cdgp, 1.0 137 | else: 138 | cd, cl = x[0], x[1] 139 | except: 140 | print("Unable to start Xfoil: %s" % failmsg) 141 | cd, cl = cdgp, 1.0 142 | 143 | err = 1 - cdgp/cd 144 | if err > TOL: 145 | msg = ("Drag error for %s is %.2f. Re=%.1f; CL=%.4f;" 146 | " Xfoil cd=%.6f, GP sol cd=%.6f" % 147 | (", ".join(d.descr["models"]), err, re, cl, cd, 148 | cdgp)) 149 | print("Warning: %s" % msg) 150 | else: 151 | bndwrn = False 152 | 153 | if bndwrn: 154 | for d in dvrs: 155 | num = result(d) 156 | err = 0.0 157 | if num < bds[d][0]: 158 | direct = "lower" 159 | bnd = bds[d][0] 160 | err = num/bnd 161 | if num > bds[d][1]: 162 | direct = "upper" 163 | bnd = bds[d][1] 164 | err = 1 - num/bnd 165 | 166 | if err > TOL: 167 | msg = ("Variable %.100s could cause inaccurate result" 168 | " because it exceeds" % d 169 | + " %s bound. Solution is %.4f but" 170 | " bound is %.4f" % 171 | (direct, num, bnd)) 172 | print("Warning: " + msg) 173 | -------------------------------------------------------------------------------- /gpkitmodels/tools/ipynb2module.py: -------------------------------------------------------------------------------- 1 | 2 | "tool for importing ipython notebooks as modules" 3 | from __future__ import print_function 4 | 5 | 6 | from builtins import object 7 | import io, os, sys, types 8 | 9 | # Import Modules 10 | from IPython import get_ipython 11 | from nbformat import read 12 | from IPython.core.interactiveshell import InteractiveShell 13 | 14 | 15 | def find_notebook(fullname, path=None): 16 | """find a notebook, given its fully qualified name and an optional path 17 | 18 | This turns "foo.bar" into "foo/bar.ipynb" 19 | and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar 20 | does not exist. 21 | """ 22 | name = fullname.rsplit('.', 1)[-1] 23 | if not path: 24 | path = [''] 25 | for d in path: 26 | nb_path = os.path.join(d, name + ".ipynb") 27 | if os.path.isfile(nb_path): 28 | return nb_path 29 | # let import Notebook_Name find "Notebook Name.ipynb" 30 | nb_path = nb_path.replace("_", " ") 31 | if os.path.isfile(nb_path): 32 | return nb_path 33 | 34 | 35 | class NotebookLoader(object): 36 | """Module Loader for Jupyter Notebooks""" 37 | def __init__(self, path=None): 38 | self.shell = InteractiveShell.instance() 39 | self.path = path 40 | 41 | def load_module(self, fullname): 42 | """import a notebook as a module""" 43 | path = find_notebook(fullname, self.path) 44 | 45 | print("importing Jupyter notebook from %s" % path) 46 | 47 | # load the notebook object 48 | with io.open(path, 'r', encoding='utf-8') as f: 49 | nb = read(f, 4) 50 | 51 | 52 | # create the module and add it to sys.modules 53 | # if name in sys.modules: 54 | # return sys.modules[name] 55 | mod = types.ModuleType(fullname) 56 | mod.__file__ = path 57 | mod.__loader__ = self 58 | mod.__dict__['get_ipython'] = get_ipython 59 | sys.modules[fullname] = mod 60 | 61 | # extra work to ensure that magics that would affect the user_ns 62 | # actually affect the notebook module's ns 63 | save_user_ns = self.shell.user_ns 64 | self.shell.user_ns = mod.__dict__ 65 | 66 | try: 67 | for cell in nb.cells: 68 | if cell.cell_type == 'code': 69 | # transform the input to executable Python 70 | code = self.shell.input_transformer_manager.transform_cell(cell.source) 71 | # run the code in themodule 72 | exec(code, mod.__dict__) 73 | finally: 74 | self.shell.user_ns = save_user_ns 75 | return mod 76 | 77 | class NotebookFinder(object): 78 | """Module finder that locates Jupyter Notebooks""" 79 | def __init__(self): 80 | self.loaders = {} 81 | 82 | def find_module(self, fullname, path=None): 83 | nb_path = find_notebook(fullname, path) 84 | if not nb_path: 85 | return 86 | 87 | key = path 88 | if path: 89 | # lists aren't hashable 90 | key = os.path.sep.join(path) 91 | 92 | if key not in self.loaders: 93 | self.loaders[key] = NotebookLoader(path) 94 | return self.loaders[key] 95 | 96 | 97 | # Register the hook 98 | def enable(): 99 | sys.meta_path.append(NotebookFinder()) 100 | -------------------------------------------------------------------------------- /gpkitmodels/tools/summing_constraintset.py: -------------------------------------------------------------------------------- 1 | " helpers.py " 2 | from builtins import zip 3 | import numpy as np 4 | from gpkit import ConstraintSet, Variable 5 | 6 | def summing_vars(models, varname): 7 | "returns a list of variables with shared varname in model list" 8 | modelnames = set(m.lineage for m in models) 9 | vkeys = [] 10 | for m in models: 11 | for v in m.varkeys[varname]: 12 | if v.lineage in modelnames: 13 | vkeys.append(v) 14 | vrs = [m[v] for m, v in zip(models, vkeys)] 15 | return vrs 16 | 17 | class SummingConstraintSet(ConstraintSet): 18 | def __init__(self, lhs, varname, models=[], variables=[], **kwargs): 19 | summedvars = set([v.key for v in variables]) 20 | alreadysummed = set() 21 | for model in models: 22 | twovars = 0 23 | for var in model.varkeys: 24 | if var.name == varname: 25 | twovars += 1 26 | if twovars > 1: 27 | for dvar in model.variables_byname(varname): 28 | if model.lineage == dvar.lineage: 29 | mvars = dvar 30 | else: 31 | mvars = model[varname] 32 | if not hasattr(mvars, "__len__"): 33 | mvars = [mvars] 34 | # next line makes the recursion stop at depth one 35 | # for safety to avoid double counting 36 | mvars = [v for v in mvars if v.key.lineage == model.lineage] 37 | assert len(mvars) == 1 38 | summedvars = summedvars.union([v.key for v in mvars]) 39 | for constraint in model.flat(): 40 | if hasattr(constraint, "summedvars"): 41 | alreadysummed = alreadysummed.union(constraint.summedvars) 42 | summedvars = summedvars.difference(alreadysummed) 43 | ConstraintSet.__init__(self, [lhs >= sum(Variable(**vk.descr) 44 | for vk in summedvars)], 45 | **kwargs) 46 | @property 47 | def summedvars(self): 48 | return set(self[0].p_lt.varkeys) 49 | -------------------------------------------------------------------------------- /gpkitmodels/tools/xfoilWrapper.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from builtins import range 3 | import subprocess 4 | import numpy as np 5 | import scipy.optimize as spo 6 | import math 7 | import sys 8 | from gpkit.tests.helpers import NullFile 9 | 10 | def blind_call(topline, cl, Re, M, max_iter = 100, 11 | pathname = "/usr/local/bin/xfoil"): 12 | 13 | if '.dat' in topline: 14 | tl=topline.split() 15 | afile=tl[1] 16 | if '.txt' in topline: 17 | tl=topline.split() 18 | afile=tl[1] 19 | 20 | proc = subprocess.Popen([pathname], stdout=subprocess.PIPE, stdin=subprocess.PIPE) 21 | proc.stdin.write(topline + 22 | 'oper \n' + 23 | "iter %d\n" %(max_iter)+ 24 | 'visc \n' + 25 | "%.2e \n" %(Re) + 26 | "M \n" + 27 | "%.2f \n" %(M) + 28 | "a 2.0 \n" + 29 | "cl %.4f \n" %(cl) + 30 | '\n' + 31 | 'quit \n') 32 | stdout_val = proc.communicate()[0] 33 | proc.stdin.close() 34 | 35 | if ("VISCAL: Convergence failed\n" in stdout_val): 36 | return stdout_val 37 | 38 | res = {} 39 | if ("VISCAL: Convergence failed\n" not in stdout_val): 40 | ostr = stdout_val.split() 41 | ctr = 0 42 | for i in range(0,len(ostr)): 43 | ix = len(ostr)-(i+1) 44 | vl = ostr[ix] 45 | if vl in ['a','CL','CD','Cm']: 46 | res[vl] = ostr[ix + 2] 47 | ctr += 1 48 | if ctr >= 4: 49 | break 50 | cd = res['CD'] 51 | cl = res['CL'] 52 | # alpha_ret = res['a'] 53 | cm = res['Cm'] 54 | return float(cd), float(cl), float(cm), stdout_val 55 | 56 | def single_cl(CL, Re = 1e7, M = 0.0, airfoil=[], pathname = "/home/ckarcher/Xfoil/bin/./xfoil", 57 | number_of_samples = 51, sampling_min=-10, sampling_max=20, fitting_fraction = 1.4): 58 | 59 | num_samples = number_of_samples 60 | sample_min=sampling_min 61 | sample_max=sampling_max 62 | 63 | ls_res = subprocess.check_output(["ls -a"], shell = True) 64 | ls = ls_res.split() 65 | 66 | remove_kulfan = False 67 | if list(airfoil): 68 | if ('.dat' in airfoil) or ('.txt' in airfoil): 69 | topline = 'load ' + airfoil + ' \n afl \n' 70 | elif ('naca' == airfoil.lower()[0:4]) and (len(airfoil)==8): 71 | topline = airfoil + ' \n' 72 | else: 73 | print("Error: Invalid airfoil passed into XFOIL. Defaulting to a NACA0012.") 74 | topline = 'naca0012 \n' 75 | 76 | initial_list = np.linspace(sample_min,sample_max,num_samples).tolist() 77 | cd_calcl = [] 78 | cl_calcl = [] 79 | alpha_calcl = [] 80 | cm_calcl = [] 81 | for alpha in initial_list: 82 | # x = blind_call(topline, alpha, Re, M) 83 | try: 84 | x = blind_call(topline, alpha, Re, M) 85 | except: 86 | x=[1,1] 87 | if len(x)==5: 88 | cd_calcl.append(x[0]) 89 | cl_calcl.append(x[1]) 90 | alpha_calcl.append(x[2]) 91 | cm_calcl.append(x[3]) 92 | elif len(x)>10: 93 | pass 94 | 95 | cd_calc = np.asarray(cd_calcl) 96 | cl_calc = np.asarray(cl_calcl) 97 | alpha_calc = np.asarray(alpha_calcl) 98 | cm_calc = np.asarray(cm_calcl) 99 | 100 | vldidx = np.where(cl_calc<=max(cl_calc)) 101 | cd_calc=cd_calc[vldidx[0].tolist()] 102 | cl_calc=cl_calc[vldidx[0].tolist()] 103 | alpha_calc=alpha_calc[vldidx[0].tolist()] 104 | cm_calc=cm_calc[vldidx[0].tolist()] 105 | 106 | vldidx2 = np.where(cl_calc>=0.0) 107 | cd_calc=cd_calc[vldidx2[0].tolist()] 108 | cl_calc=cl_calc[vldidx2[0].tolist()] 109 | alpha_calc=alpha_calc[vldidx2[0].tolist()] 110 | cm_calc=cm_calc[vldidx2[0].tolist()] 111 | 112 | p_cd = np.polyfit(np.append(-cl_calc,cl_calc), np.append(cd_calc,cd_calc),int(len(cl_calc)/fitting_fraction)) 113 | p_alpha = np.polyfit(np.append(-cl_calc,cl_calc), np.append(alpha_calc,alpha_calc),int(len(cl_calc)/fitting_fraction)) 114 | p_cm = np.polyfit(np.append(-cl_calc,cl_calc), np.append(cm_calc,cm_calc),int(len(cl_calc)/fitting_fraction)) 115 | 116 | cd_guess = np.polyval(p_cd,CL) 117 | alpha_guess = np.polyval(p_alpha,CL) 118 | cm_guess = np.polyval(p_cm,CL) 119 | 120 | return cd_guess, CL, alpha_guess, cm_guess 121 | -------------------------------------------------------------------------------- /researchmodeltests.sh: -------------------------------------------------------------------------------- 1 | export PIP=`which pip` 2 | echo $PIP 3 | python -c "from gpkit.tests.test_repo import test_repos; test_repos(xmloutput=True, ingpkitmodels=True)" 4 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | python -c "from gpkit.tests.from_paths import run; run()" 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Standard Python setup script for GPkit Models""" 2 | from __future__ import print_function 3 | import sys 4 | from distutils.core import setup 5 | from setuptools import find_packages 6 | 7 | LONG_DESCRIPTION = """ 8 | GPkit Models is a library of geometric programming and signomial programming 9 | models that can be manipulated and solved using 10 | `GPkit `_. 11 | 12 | `Documentation `_ 13 | 14 | `Citing GPkit `_ 15 | """ 16 | 17 | LICENSE = """The MIT License (MIT) 18 | 19 | Copyright (c) 2015 MIT Hoburg Research Group 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE.""" 38 | 39 | setup( 40 | name="gpkitmodels", 41 | description="Library of geometric and signomial programming models " 42 | "that can be manipulated and solved using GPkit.", 43 | author="MIT Department of Aeronautics and Astronautics", 44 | author_email="gpkit@mit.edu", 45 | url="https://www.github.com/hoburg/gpkit-models", 46 | install_requires=["numpy>=1.12", "scipy", "pint", "future"], 47 | version="0.0.0.0", 48 | packages=find_packages(), 49 | package_data={"gpkitmodels": ["GP/aircraft/wing/*.csv", 50 | ("GP/aircraft/fuselage/" 51 | "fuselage_profile_drag/*.csv"), 52 | "GP/aircraft/engine/*.csv", 53 | "GP/aircraft/motor/*.csv", 54 | "GP/aircraft/prop/*.csv", 55 | "SP/aircraft/prop/*.csv", 56 | "GP/aircraft/engine/DF70/*.csv", 57 | "GP/aircraft/tail/*.csv"]}, 58 | license=LICENSE, 59 | long_description=LONG_DESCRIPTION, 60 | ) 61 | --------------------------------------------------------------------------------