From edccf56135714928e5d4e2cf5e7a5b8f235081fe Mon Sep 17 00:00:00 2001 From: Rick Lupton Date: Sun, 19 May 2019 22:32:54 +0100 Subject: [PATCH] Initial setup --- .travis.yml | 10 +- CHANGELOG.rst | 9 +- Pipfile | 12 + Pipfile.lock | 205 +++++++ README.rst | 21 +- docs/Makefile | 9 +- docs/api.rst | 7 + docs/conf.py | 68 +-- docs/index.rst | 48 +- requirements.txt | 17 - setup.cfg | 10 +- src/bemused/__init__.py | 7 + src/bemused/bem.py | 500 ++++++++++++++++++ src/bemused/fast_interpolation.py | 34 ++ src/bemused/models.py | 76 +++ src/bemused/skeleton.py | 116 ---- tests/benchmark.ipy | 28 + tests/data/Bladed_demo_a_modified/README.md | 15 + .../data/Bladed_demo_a_modified/aeroinfo.$01 | Bin 0 -> 1224 bytes .../data/Bladed_demo_a_modified/aeroinfo.$PJ | 383 ++++++++++++++ .../data/Bladed_demo_a_modified/aeroinfo.$TE | 1 + .../data/Bladed_demo_a_modified/aeroinfo.$VE | Bin 0 -> 31304 bytes .../data/Bladed_demo_a_modified/aeroinfo.$me | 3 + .../data/Bladed_demo_a_modified/aeroinfo.%01 | 17 + .../aeroinfo_12ms_0deg.csv | 11 + .../aeroinfo_14ms_2deg.$01 | Bin 0 -> 1224 bytes .../aeroinfo_14ms_2deg.$PJ | 385 ++++++++++++++ .../aeroinfo_14ms_2deg.$TE | 1 + .../aeroinfo_14ms_2deg.$VE | Bin 0 -> 31319 bytes .../aeroinfo_14ms_2deg.$me | 3 + .../aeroinfo_14ms_2deg.%01 | 17 + .../aeroinfo_14ms_2deg.csv | 11 + tests/data/Bladed_demo_a_modified/blade.yaml | 4 + tests/data/Bladed_demo_a_modified/pcoeffs.$02 | Bin 0 -> 560 bytes tests/data/Bladed_demo_a_modified/pcoeffs.$PJ | 387 ++++++++++++++ tests/data/Bladed_demo_a_modified/pcoeffs.$TE | 1 + tests/data/Bladed_demo_a_modified/pcoeffs.$VE | Bin 0 -> 31462 bytes tests/data/Bladed_demo_a_modified/pcoeffs.$me | 3 + tests/data/Bladed_demo_a_modified/pcoeffs.%02 | 29 + tests/data/Bladed_demo_a_modified/pcoeffs.csv | 36 ++ tests/data/aerofoils.npz | Bin 0 -> 14302 bytes tests/regression/.gitignore | 3 + .../regression/demo_a_modified/aerofoils.npz | Bin 0 -> 14302 bytes ...out.12343ce.inp=input_12ms_18rpm_0deg.yaml | 16 + ...out.12343ce.inp=input_12ms_18rpm_8deg.yaml | 16 + ...out.12343ce.inp=input_12ms_22rpm_0deg.yaml | 16 + ...43ce.inp=input_12ms_22rpm_0deg_to14ms.yaml | 16 + ...out.12343ce.inp=input_18ms_22rpm_0deg.yaml | 16 + ...out.12343ce.inp=input_18ms_22rpm_5deg.yaml | 16 + ...out.54da0d4.inp=input_12ms_18rpm_0deg.yaml | 16 + ...out.54da0d4.inp=input_12ms_18rpm_8deg.yaml | 16 + ...out.54da0d4.inp=input_12ms_22rpm_0deg.yaml | 16 + ...out.54da0d4.inp=input_18ms_22rpm_0deg.yaml | 16 + ...out.54da0d4.inp=input_18ms_22rpm_5deg.yaml | 16 + .../demo_a_modified/demo_a_modified.bladedef | 4 + .../input_12ms_18rpm_0deg.yaml | 5 + .../input_12ms_18rpm_8deg.yaml | 5 + .../input_12ms_22rpm_0deg.yaml | 5 + .../input_12ms_22rpm_0deg_to14ms.yaml | 6 + .../input_18ms_22rpm_0deg.yaml | 5 + .../input_18ms_22rpm_5deg.yaml | 5 + tests/regression/jobconfig | 2 + tests/regression/run-bem.py | 63 +++ tests/regression/userconfig | 14 + tests/test_BEMModel.py | 117 ++++ tests/test_aerofoil_database.py | 39 ++ tests/test_blade.py | 45 ++ tests/test_fast_interpolation.py | 47 ++ tests/test_misc_functions.py | 109 ++++ tests/test_models.py | 116 ++++ tests/test_results_against_Bladed.py | 111 ++++ tests/test_skeleton.py | 17 - tests/utils.py | 11 + tox.ini | 1 - 74 files changed, 3141 insertions(+), 249 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 docs/api.rst delete mode 100644 requirements.txt create mode 100644 src/bemused/bem.py create mode 100644 src/bemused/fast_interpolation.py create mode 100644 src/bemused/models.py delete mode 100644 src/bemused/skeleton.py create mode 100644 tests/benchmark.ipy create mode 100644 tests/data/Bladed_demo_a_modified/README.md create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.$01 create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.$PJ create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.$TE create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.$VE create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.$me create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo.%01 create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_12ms_0deg.csv create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$01 create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$PJ create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$TE create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$VE create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$me create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.%01 create mode 100644 tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.csv create mode 100644 tests/data/Bladed_demo_a_modified/blade.yaml create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.$02 create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.$PJ create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.$TE create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.$VE create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.$me create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.%02 create mode 100644 tests/data/Bladed_demo_a_modified/pcoeffs.csv create mode 100644 tests/data/aerofoils.npz create mode 100644 tests/regression/.gitignore create mode 100644 tests/regression/demo_a_modified/aerofoils.npz create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_8deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg_to14ms.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_5deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_8deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_5deg.yaml create mode 100644 tests/regression/demo_a_modified/demo_a_modified.bladedef create mode 100644 tests/regression/demo_a_modified/input_12ms_18rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/input_12ms_18rpm_8deg.yaml create mode 100644 tests/regression/demo_a_modified/input_12ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/input_12ms_22rpm_0deg_to14ms.yaml create mode 100644 tests/regression/demo_a_modified/input_18ms_22rpm_0deg.yaml create mode 100644 tests/regression/demo_a_modified/input_18ms_22rpm_5deg.yaml create mode 100644 tests/regression/jobconfig create mode 100755 tests/regression/run-bem.py create mode 100644 tests/regression/userconfig create mode 100644 tests/test_BEMModel.py create mode 100644 tests/test_aerofoil_database.py create mode 100644 tests/test_blade.py create mode 100644 tests/test_fast_interpolation.py create mode 100644 tests/test_misc_functions.py create mode 100644 tests/test_models.py create mode 100644 tests/test_results_against_Bladed.py delete mode 100644 tests/test_skeleton.py create mode 100644 tests/utils.py diff --git a/.travis.yml b/.travis.yml index 2483a1a..3d3fa22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ # Travis configuration file using the build matrix feature # Read more under http://docs.travis-ci.com/user/build-configuration/ -# THIS SCRIPT IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! sudo: false language: python @@ -13,16 +12,9 @@ matrix: - env: DISTRIB="conda" PYTHON_VERSION="3.6" COVERAGE="false" install: - source tests/travis_install.sh - - pip install -r requirements.txt - # ^ DEPRECATION WARNING: - # The automatic creation of a `requirements.txt` file is deprecated. - # See `Dependency Management` in the docs for other options. -before_script: - - git config --global user.email "you@example.com" - - git config --global user.name "Your Name" + - pip install -e '.[testing]' script: - python setup.py test - # ^ Change here if using tox after_success: - if [[ "$COVERAGE" == "true" ]]; then coveralls || echo "failed"; fi after_script: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 226e6f5..1ae2af0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,9 +2,10 @@ Changelog ========= -Version 0.1 +Version 0.2 =========== -- Feature A added -- FIX: nasty bug #1729 fixed -- add your changes here! +First release as *bemused*. This code was previously on Github under the name +"`py\-bem`_". + +.. _py-bem: https://github.com/ricklupton/py-bem diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..71812e1 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +bemused = {path = ".",extras = ["testing"],editable = true} + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..d1e9b54 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,205 @@ +{ + "_meta": { + "hash": { + "sha256": "40cddc83e7f860217cdf67d07f76a08362db02b103f3aec12520dc295b8e7065" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "bemused": { + "editable": true, + "extras": [ + "testing" + ], + "path": "." + }, + "coverage": { + "hashes": [ + "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", + "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", + "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", + "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", + "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", + "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", + "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", + "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", + "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", + "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", + "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", + "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", + "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", + "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", + "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", + "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", + "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", + "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", + "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", + "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", + "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", + "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", + "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", + "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", + "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", + "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", + "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", + "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", + "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", + "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", + "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" + ], + "version": "==4.5.3" + }, + "more-itertools": { + "hashes": [ + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" + ], + "markers": "python_version > '2.7'", + "version": "==7.0.0" + }, + "numpy": { + "hashes": [ + "sha256:0e2eed77804b2a6a88741f8fcac02c5499bba3953ec9c71e8b217fad4912c56c", + "sha256:1c666f04553ef70fda54adf097dbae7080645435fc273e2397f26bbf1d127bbb", + "sha256:1f46532afa7b2903bfb1b79becca2954c0a04389d19e03dc73f06b039048ac40", + "sha256:315fa1b1dfc16ae0f03f8fd1c55f23fd15368710f641d570236f3d78af55e340", + "sha256:3d5fcea4f5ed40c3280791d54da3ad2ecf896f4c87c877b113576b8280c59441", + "sha256:48241759b99d60aba63b0e590332c600fc4b46ad597c9b0a53f350b871ef0634", + "sha256:4b4f2924b36d857cf302aec369caac61e43500c17eeef0d7baacad1084c0ee84", + "sha256:54fe3b7ed9e7eb928bbc4318f954d133851865f062fa4bbb02ef8940bc67b5d2", + "sha256:5a8f021c70e6206c317974c93eaaf9bc2b56295b6b1cacccf88846e44a1f33fc", + "sha256:754a6be26d938e6ca91942804eb209307b73f806a1721176278a6038869a1686", + "sha256:771147e654e8b95eea1293174a94f34e2e77d5729ad44aefb62fbf8a79747a15", + "sha256:78a6f89da87eeb48014ec652a65c4ffde370c036d780a995edaeb121d3625621", + "sha256:7fde5c2a3a682a9e101e61d97696687ebdba47637611378b4127fe7e47fdf2bf", + "sha256:80d99399c97f646e873dd8ce87c38cfdbb668956bbc39bc1e6cac4b515bba2a0", + "sha256:88a72c1e45a0ae24d1f249a529d9f71fe82e6fa6a3fd61414b829396ec585900", + "sha256:a4f4460877a16ac73302a9c077ca545498d9fe64e6a81398d8e1a67e4695e3df", + "sha256:a61255a765b3ac73ee4b110b28fccfbf758c985677f526c2b4b39c48cc4b509d", + "sha256:ab4896a8c910b9a04c0142871d8800c76c8a2e5ff44763513e1dd9d9631ce897", + "sha256:abbd6b1c2ef6199f4b7ca9f818eb6b31f17b73a6110aadc4e4298c3f00fab24e", + "sha256:b16d88da290334e33ea992c56492326ea3b06233a00a1855414360b77ca72f26", + "sha256:b78a1defedb0e8f6ae1eb55fa6ac74ab42acc4569c3a2eacc2a407ee5d42ebcb", + "sha256:cfef82c43b8b29ca436560d51b2251d5117818a8d1fb74a8384a83c096745dad", + "sha256:d160e57731fcdec2beda807ebcabf39823c47e9409485b5a3a1db3a8c6ce763e" + ], + "version": "==1.16.3" + }, + "pluggy": { + "hashes": [ + "sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180", + "sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a" + ], + "version": "==0.11.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pytest": { + "hashes": [ + "sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24", + "sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6" + ], + "version": "==4.5.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", + "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a" + ], + "version": "==2.7.1" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "version": "==5.1" + }, + "scipy": { + "hashes": [ + "sha256:014cb900c003b5ac81a53f2403294e8ecf37aedc315b59a6b9370dce0aa7627a", + "sha256:281a34da34a5e0de42d26aed692ab710141cad9d5d218b20643a9cb538ace976", + "sha256:588f9cc4bfab04c45fbd19c1354b5ade377a8124d6151d511c83730a9b6b2338", + "sha256:5a10661accd36b6e2e8855addcf3d675d6222006a15795420a39c040362def66", + "sha256:628f60be272512ca1123524969649a8cb5ae8b31cca349f7c6f8903daf9034d7", + "sha256:6dcc43a88e25b815c2dea1c6fac7339779fc988f5df8396e1de01610604a7c38", + "sha256:70e37cec0ac0fe95c85b74ca4e0620169590fd5d3f44765f3c3a532cedb0e5fd", + "sha256:7274735fb6fb5d67d3789ddec2cd53ed6362539b41aa6cc0d33a06c003aaa390", + "sha256:78e12972e144da47326958ac40c2bd1c1cca908edc8b01c26a36f9ffd3dce466", + "sha256:790cbd3c8d09f3a6d9c47c4558841e25bac34eb7a0864a9def8f26be0b8706af", + "sha256:79792c8fe8e9d06ebc50fe23266522c8c89f20aa94ac8e80472917ecdce1e5ba", + "sha256:865afedf35aaef6df6344bee0de391ee5e99d6e802950a237f9fb9b13e441f91", + "sha256:870fd401ec7b64a895cff8e206ee16569158db00254b2f7157b4c9a5db72c722", + "sha256:963815c226b29b0176d5e3d37fc9de46e2778ce4636a5a7af11a48122ef2577c", + "sha256:9726791484f08e394af0b59eb80489ad94d0a53bbb58ab1837dcad4d58489863", + "sha256:9de84a71bb7979aa8c089c4fb0ea0e2ed3917df3fb2a287a41aaea54bbad7f5d", + "sha256:b2c324ddc5d6dbd3f13680ad16a29425841876a84a1de23a984236d1afff4fa6", + "sha256:b86ae13c597fca087cb8c193870507c8916cefb21e52e1897da320b5a35075e5", + "sha256:ba0488d4dbba2af5bf9596b849873102d612e49a118c512d9d302ceafa36e01a", + "sha256:d78702af4102a3a4e23bb7372cec283e78f32f5573d92091aa6aaba870370fe1", + "sha256:def0e5d681dd3eb562b059d355ae8bebe27f5cc455ab7c2b6655586b63d3a8ea", + "sha256:e085d1babcb419bbe58e2e805ac61924dac4ca45a07c9fa081144739e500aa3c", + "sha256:e2cfcbab37c082a5087aba5ff00209999053260441caadd4f0e8f4c2d6b72088", + "sha256:e742f1f5dcaf222e8471c37ee3d1fd561568a16bb52e031c25674ff1cf9702d5", + "sha256:f06819b028b8ef9010281e74c59cb35483933583043091ed6b261bb1540f11cc", + "sha256:f15f2d60a11c306de7700ee9f65df7e9e463848dbea9c8051e293b704038da60", + "sha256:f31338ee269d201abe76083a990905473987371ff6f3fdb76a3f9073a361cf37", + "sha256:f6b88c8d302c3dac8dff7766955e38d670c82e0d79edfc7eae47d6bb2c186594" + ], + "version": "==1.2.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + } + }, + "develop": {} +} diff --git a/README.rst b/README.rst index eef5342..98188a7 100644 --- a/README.rst +++ b/README.rst @@ -2,18 +2,19 @@ bemused ======= +`Blade Element Momentum (BEM)`_ calculations implemented in Python. For +modelling wind turbine (or other rotor) aerodynamics and loads. -Add a short description here! +Getting started +--------------- +See `this blog post`_ for an introduction to how to use this package. -Description -=========== +Documentation +------------- -A longer description of your project goes here... +Documentation is available `here`_. - -Note -==== - -This project has been set up using PyScaffold 3.1. For details and usage -information on PyScaffold see https://pyscaffold.org/. +.. _Blade Element Momentum (BEM): https://en.wikipedia.org/wiki/Blade_element_momentum_theory +.. _this blog post: https://ricklupton.name/2019/TBC +.. _here: https://bemused.readthedocs.io/ diff --git a/docs/Makefile b/docs/Makefile index 7e331b9..9781f37 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -53,11 +53,12 @@ help: clean: rm -rf $(BUILDDIR)/* $(AUTODOCDIR) -$(AUTODOCDIR): $(MODULEDIR) - mkdir -p $@ - $(AUTODOCBUILD) -f -o $@ $^ +# $(AUTODOCDIR): $(MODULEDIR) +# mkdir -p $@ +# $(AUTODOCBUILD) -f -o $@ $^ -doc-requirements: $(AUTODOCDIR) +doc-requirements: +# $(AUTODOCDIR) html: doc-requirements $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..fc9f5b1 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,7 @@ +bemused package +=============== + +.. automodule:: bemused + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 85ca948..5008e34 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,40 +21,40 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.join(__location__, '../src')) -# -- Run sphinx-apidoc ------------------------------------------------------ -# This hack is necessary since RTD does not issue `sphinx-apidoc` before running -# `sphinx-build -b html . _build/html`. See Issue: -# https://github.com/rtfd/readthedocs.org/issues/1139 -# DON'T FORGET: Check the box "Install your project inside a virtualenv using -# setup.py install" in the RTD Advanced Settings. -# Additionally it helps us to avoid running apidoc manually - -try: # for Sphinx >= 1.7 - from sphinx.ext import apidoc -except ImportError: - from sphinx import apidoc - -output_dir = os.path.join(__location__, "api") -module_dir = os.path.join(__location__, "../src/bemused") -try: - shutil.rmtree(output_dir) -except FileNotFoundError: - pass - -try: - import sphinx - from pkg_resources import parse_version - - cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" - cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) - - args = cmd_line.split(" ") - if parse_version(sphinx.__version__) >= parse_version('1.7'): - args = args[1:] - - apidoc.main(args) -except Exception as e: - print("Running `sphinx-apidoc` failed!\n{}".format(e)) +# # -- Run sphinx-apidoc ------------------------------------------------------ +# # This hack is necessary since RTD does not issue `sphinx-apidoc` before running +# # `sphinx-build -b html . _build/html`. See Issue: +# # https://github.com/rtfd/readthedocs.org/issues/1139 +# # DON'T FORGET: Check the box "Install your project inside a virtualenv using +# # setup.py install" in the RTD Advanced Settings. +# # Additionally it helps us to avoid running apidoc manually + +# try: # for Sphinx >= 1.7 +# from sphinx.ext import apidoc +# except ImportError: +# from sphinx import apidoc + +# output_dir = os.path.join(__location__, "api") +# module_dir = os.path.join(__location__, "../src/bemused") +# try: +# shutil.rmtree(output_dir) +# except FileNotFoundError: +# pass + +# try: +# import sphinx +# from pkg_resources import parse_version + +# cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" +# cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) + +# args = cmd_line.split(" ") +# if parse_version(sphinx.__version__) >= parse_version('1.7'): +# args = args[1:] + +# apidoc.main(args) +# except Exception as e: +# print("Running `sphinx-apidoc` failed!\n{}".format(e)) # -- General configuration ----------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 65ac0d8..29501ae 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,26 +2,17 @@ bemused ======= -This is the documentation of **bemused**. +This is the documentation of **BEMused**, a Python implementation of the `Blade +Element Momentum (BEM)`_ method of modelling the flow around and loads on a wind +turbine rotor (or other kind of rotor). -.. note:: - - This is the main page of your project's `Sphinx`_ documentation. - It is formatted in `reStructuredText`_. Add additional pages - by creating rst-files in ``docs`` and adding them to the `toctree`_ below. - Use then `references`_ in order to link them from this page, e.g. - :ref:`authors` and :ref:`changes`. - - It is also possible to refer to the documentation of other Python packages - with the `Python domain syntax`_. By default you can reference the - documentation of `Sphinx`_, `Python`_, `NumPy`_, `SciPy`_, `matplotlib`_, - `Pandas`_, `Scikit-Learn`_. You can add more by extending the - ``intersphinx_mapping`` in your Sphinx's ``conf.py``. - - The pretty useful extension `autodoc`_ is activated by default and lets - you include documentation from docstrings. Docstrings can be written in - `Google style`_ (recommended!), `NumPy style`_ and `classical style`_. +This code was originally written for `Rick Lupton's PhD thesis`_ and used in `a +related paper`_. +To use it you need to create a :class:`bemused.Blade` object defining your blade +parameters (chord, twist and thickness) and an :class:`bemused.AerofoilDatabase` +containing lift and drag coefficients. You can then use +:class:`bemused.BEMModel` to do the calculations. Contents ======== @@ -32,7 +23,7 @@ Contents License Authors Changelog - Module Reference + Module Reference Indices and tables @@ -42,18 +33,7 @@ Indices and tables * :ref:`modindex` * :ref:`search` -.. _toctree: http://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html -.. _reStructuredText: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html -.. _references: http://www.sphinx-doc.org/en/stable/markup/inline.html -.. _Python domain syntax: http://sphinx-doc.org/domains.html#the-python-domain -.. _Sphinx: http://www.sphinx-doc.org/ -.. _Python: http://docs.python.org/ -.. _Numpy: http://docs.scipy.org/doc/numpy -.. _SciPy: http://docs.scipy.org/doc/scipy/reference/ -.. _matplotlib: https://matplotlib.org/contents.html# -.. _Pandas: http://pandas.pydata.org/pandas-docs/stable -.. _Scikit-Learn: http://scikit-learn.org/stable -.. _autodoc: http://www.sphinx-doc.org/en/stable/ext/autodoc.html -.. _Google style: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings -.. _NumPy style: https://numpydoc.readthedocs.io/en/latest/format.html -.. _classical style: http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists + +.. _Blade Element Momentum (BEM): https://en.wikipedia.org/wiki/Blade_element_momentum_theory +.. _Rick Lupton's PhD thesis: https://doi.org/10.17863/CAM.14119 +.. _a related paper: https://doi.org/10.1016/j.renene.2018.11.067 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 434a5f2..0000000 --- a/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -# ============================================================================= -# DEPRECATION WARNING: -# -# The file `requirements.txt` does not influence the package dependencies and -# will not be automatically created in the next version of PyScaffold (v4.x). -# -# Please have look at the docs for better alternatives -# (`Dependency Management` section). -# ============================================================================= -# -# Add your pinned requirements so that they can be easily installed with: -# pip install -r requirements.txt -# Remember to also add them in setup.cfg but unpinned. -# Example: -# numpy==1.13.3 -# scipy==1.0 - diff --git a/setup.cfg b/setup.cfg index 3cdc207..2056f78 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,11 +4,11 @@ [metadata] name = bemused -description = Add a short description here! +description = Blade Element Momentum calculations author = Rick Lupton author-email = mail@ricklupton.name license = mit -url = https://pyscaffold.org/ +url = https://github.com/ricklupton/bemused long-description = file: README.rst # Change if running only on Windows, Mac or Linux (comma-separated) platforms = any @@ -27,7 +27,10 @@ package_dir = # DON'T CHANGE THE FOLLOWING LINE! IT WILL BE UPDATED BY PYSCAFFOLD! setup_requires = pyscaffold>=3.1a0,<3.2a0 # Add here dependencies of your project (semicolon/line-separated), e.g. -# install_requires = numpy; scipy +install_requires = + numpy + scipy >= 0.19 + pyyaml # The usage of test_requires is discouraged, see `Dependency Management` docs # tests_require = pytest; pytest-cov # Require a specific Python version, e.g. Python 2.7 or >= 3.4 @@ -46,6 +49,7 @@ exclude = testing = pytest pytest-cov + pandas [options.entry_points] # Add here console scripts like: diff --git a/src/bemused/__init__.py b/src/bemused/__init__.py index c99a72a..a3836aa 100644 --- a/src/bemused/__init__.py +++ b/src/bemused/__init__.py @@ -9,3 +9,10 @@ __version__ = 'unknown' finally: del get_distribution, DistributionNotFound + +from .bem import * +from . import models + + +__all__ = ['Blade', 'AerofoilDatabase', 'BEMModel', + 'FrozenWakeAerodynamics', 'EquilibriumWakeAerodynamics'] diff --git a/src/bemused/bem.py b/src/bemused/bem.py new file mode 100644 index 0000000..35d3f8e --- /dev/null +++ b/src/bemused/bem.py @@ -0,0 +1,500 @@ +import numpy as np +from numpy import pi, sin, cos, arctan2, trapz, array, newaxis, nan +from scipy.interpolate import interp1d, RegularGridInterpolator +from .fast_interpolation import fast_interpolation +import yaml + + +class Blade: + """Holds a blade definition. + + Attributes + ---------- + x : ndarray + Position of blade stations (measured from blade root) + chord : ndarray + Chord length [m] + twist : ndarray + Twist, positive points leading edge upwind [rad] + thickness : ndarray + Percentage thickness of aerofoil [%] + density : ndarray, optional + Mass per unit length of blade [kg/m] + EA : ndarray, optional + Axial stiffness + EI_flap, EI_edge : ndarray, optional + Bending stiffness in flapwise and edgewise directions + + """ + def __init__(self, x, chord, twist, thickness, + density=None, EA=None, EI_flap=None, EI_edge=None): + + self.x = array(x) + + # Optional mass properties + if density is None: + density = nan * self.x + if EA is None: + EA = nan * self.x + if EI_flap is None: + EI_flap = nan * self.x + if EI_edge is None: + EI_edge = nan * self.x + + self.chord = array(chord) + self.twist = array(twist) + self.thickness = array(thickness) + self.density = array(density) + self.EA = array(EA) + self.EI_flap = array(EI_flap) + self.EI_edge = array(EI_edge) + + if not (len(x) == len(chord) == len(twist) == len(thickness) == + len(density) == len(EA) == len(EI_flap) == len(EI_edge)): + raise ValueError("Shape mismatch") + + @classmethod + def from_yaml(cls, filename_or_file): + """Load blade definition from YAML file. + + The file should have `x`, `chord`, `twist` and `thickness` keys. + + For example: + + .. code-block:: yaml + + x: [0.2, 3.2, 4.3] + chord: [2.3, 1.0, 3] + twist: [2, 5, 0.2] + thickness: [100, 100, 43] + + Note + ---- + NB: In the definition file, the twist is measured in degrees! + + Parameters + ---------- + filename_or_file: str or file + Filename or file-like object to load the YAML data from + + """ + if isinstance(filename_or_file, str): + with open(filename_or_file) as f: + data = yaml.safe_load(f) + else: + data = yaml.safe_load(filename_or_file) + return Blade(data['x'], + data['chord'], + array(data['twist']) * pi / 180, + data['thickness'], + data.get('density'), + data.get('EA'), + data.get('EI_flap'), + data.get('EI_edge')) + + def resample(self, new_x): + """Resample all blade data to the new `x` coordinates. + + Parameters + ---------- + new_x: array + Coordinates (distance along the blade) to resample at + + Returns + ------- + A new :class:`Blade` instance. + + """ + return Blade(new_x, + np.interp(new_x, self.x, self.chord), + np.interp(new_x, self.x, self.twist), + np.interp(new_x, self.x, self.thickness), + np.interp(new_x, self.x, self.density), + np.interp(new_x, self.x, self.EA), + np.interp(new_x, self.x, self.EI_flap), + np.interp(new_x, self.x, self.EI_edge)) + + +class AerofoilDatabase(object): + """Store aerofoil list and drag data. + + Loads data in `.npz` format. The data file should have two variables: + + - datasets : list of aerofoils + - thicknesses : fractional thicknesses of the aerofoils in `datasets` + + Each aerofoil is an array with `alpha`, `CL`, `CD` and `CM` + columns, where the angles are in radians. + + """ + def __init__(self, filename): + self.filename = filename + self.aerofoils = np.load(filename, allow_pickle=True) + + # Reinterpolate data for all aerofoils to consistent values of alpha + datasets = self.aerofoils['datasets'] + alpha = [] + for a in sorted(a for data in datasets for a in data['alpha']): + if alpha and abs(a - alpha[-1]) < 1e-5: + continue + alpha.append(a) + self.alpha = np.array(alpha) + lift_drag = np.dstack([ + [np.interp(alpha, data['alpha'], data['CL']) for data in datasets], + [np.interp(alpha, data['alpha'], data['CD']) for data in datasets] + ]) + self.lift_drag_by_thickness = interp1d( + self.aerofoils['thicknesses'], lift_drag, axis=0, copy=False) + + def for_thickness(self, thickness): + """Return interpolated lift & drag data for the given thickness. + + Parameters + ---------- + thickness : float + Fractional thickness + + """ + lift_drag = self.lift_drag_by_thickness(thickness) + return lift_drag + + +def _strip_boundaries(radii): + # Find two ends of strip -- halfway between this point and + # neighbours, apart from at ends when it's half as wide. + radii = 1.0 * np.asarray(radii) + midpoints = (radii[1:] + radii[:-1]) / 2 + return np.r_[radii[0], midpoints, radii[-1]] + + +def _wrap_angle(theta): + """Wraps the angle to [-pi, pi]""" + return (theta + pi) % (2 * pi) - pi + + +def _thrust_correction_factor(a): + """Correction to the thrust for high induction factors""" + a = np.atleast_1d(a) + H = np.ones_like(a) + i = (a > 0.3539) + ai = a[i] + H[i] = (4*ai*(1-ai) / (0.60 + 0.61*ai + 0.79*ai**2)) + return H + + +def LSR(windspeed, rotorspeed, radius): + return radius * rotorspeed / windspeed + + +def inflow(LSR, factors, extra_velocity_factors=None): + """Calculate inflow angle from LSR, induction factors and normalised + extra blade velocities""" + factors = np.asarray(factors) + Ux = (1.0 - factors[:, 0]) + Uy = LSR * (1.0 + factors[:, 1]) + if extra_velocity_factors is not None: + Ux -= extra_velocity_factors[:, 0] + Uy -= extra_velocity_factors[:, 1] + phi = arctan2(Ux, Uy) + inplane = (abs(phi) < 1e-2) + W = np.zeros_like(phi) + W[inplane] = Uy[inplane] / cos(phi[inplane]) + W[~inplane] = Ux[~inplane] / sin(phi[~inplane]) + return W, phi + + +def iterate_induction_factors(LSR, force_coeffs, solidity, pitch, + factors, extra_velocity_factors=None): + a, at = factors[:, 0], factors[:, 1] + W, phi = inflow(LSR, factors, extra_velocity_factors) + cx, cy = force_coeffs[:, 0], force_coeffs[:, 1] + + # calculate new induction factors + Kx = np.inf * np.ones_like(a) + Ky = np.inf * np.ones_like(a) + ix = (solidity * cx != 0) + iy = (solidity * cy != 0) + + Kx[ix] = 4*sin(phi[ix])**2 / (solidity*cx)[ix] + Ky[iy] = 4*sin(phi[iy])*cos(phi[iy]) / (solidity*cy)[iy] + H = _thrust_correction_factor(a) + + new = np.empty_like(factors) + new[:, 0] = 1. / (Kx/H + 1) + new[:, 1] = 1. / (-Ky - 1) + + # Slow down iteration a bit to improve convergence. + # XXX is there a justification for this? + new[...] = (factors + 3*new) / 4 + return new + + +class BEMModel(object): + """A Blade Element - Momentum model. + + Parameters + ---------- + blade : Blade object + Blade parameter definition + root_length : float + Distance from centre of rotor to start of blade + num_blades : int + Number of blades in the rotor + aerofoil_database : AerofoilDatabase object + Definitions of aerofoil coefficients + + """ + def __init__(self, blade, root_length, num_blades, aerofoil_database): + self.blade = blade + self.root_length = root_length + self.num_blades = num_blades + + self.radii = root_length + np.asarray(self.blade.x) + self.boundaries = _strip_boundaries(self.radii) + self.solidity = (self.num_blades * self.blade.chord / + (2 * pi * self.radii)) + + # Aerofoil data + self.alpha = aerofoil_database.alpha + self.lift_drag_data = np.array([ + aerofoil_database.for_thickness(th / 100) + for th in self.blade.thickness]) + self._lift_drag_interp = fast_interpolation( + aerofoil_database.alpha, self.lift_drag_data, axis=1) + self._last_factors = np.zeros((len(self.radii), 2)) + + def lift_drag(self, alpha, annuli=None): + """Interpolate lift & drag coefficients for given angle of attack. + + Parameters + ---------- + alpha : array_like + Angle of attach at each annulus [radians] + annuli : slice or indices, optional + Subset of annuli to return data for. If given, `alpha` + should refer only to the annuli of interest. + + Returns + ------- + Array of shape (number of annuli, 2) containing CL and CD. + + """ + if annuli is None or annuli == slice(None): + alpha = np.vstack((alpha, alpha)).T + return self._lift_drag_interp(alpha) + else: + data = self.lift_drag_data[annuli] + if len(alpha) != data.shape[0]: + raise ValueError("Shape mismatch %s != %s" % + (len(self.alpha), data.shape)) + return np.array([ + interp1d(self.alpha, self.lift_drag_data[annuli][i], + axis=-2, copy=False)(alpha[i]) + for i in range(len(alpha)) + ]) + + def force_coefficients(self, inflow_angle, pitch, annuli=None): + """Calculate force coefficients for given inflow. + + The force coefficients Cx and Cy are the out-of-plane and + in-plane non-dimensional force per unit length, respectively. + + Parameters + ---------- + inflow_angle : array_like + Inflow angle at each annulus [radians]. Zero is in-plane, positive + is towards upwind. + annuli : slice or indices, optional + Subset of annuli to return data for. If given, `alpha` + should refer only to the annuli of interest. + + Returns + ------- + Array of shape (number of annuli, 2) containing CL and CD. + + """ + if annuli is None: + annuli = slice(None) + twist = self.blade.twist[annuli] + if len(twist) != len(inflow_angle): + raise ValueError("Shape mismatch") + + # lift & drag coefficients + alpha = _wrap_angle(inflow_angle - twist - pitch) + cl_cd = self.lift_drag(alpha, annuli) + + # resolve in- and out-of-plane + cphi, sphi = np.cos(inflow_angle), np.sin(inflow_angle) + A = array([[cphi, sphi], [-sphi, cphi]]) + # cx_cy = dot(A, cl_cd) + # cx_cy = np.c_[ + # cl_cd[:, 0] * cphi + cl_cd[:, 1] * sphi, + # cl_cd[:, 0] * -sphi + cl_cd[:, 1] * cphi, + # ] + cx_cy = np.einsum('ijh, hj -> hi', A, cl_cd) + return cx_cy + + def solve(self, windspeed, rotorspeed, pitch, + extra_velocity_factors=None, tol=None, + max_iterations=500, annuli=None): + """Calculate the BEM solution for the given conditions. + + Parameters + ---------- + windspeed : float + Free-stream wind speed + rotorspeed : float + Rotor speed [rad/s] + pitch : float + Pitch angle [rad] + extra_velocity_factors : ndarray, optional + Blade velocity normalised by windspeed + tol : float, optional + Absolute tolerance for solution + max_iterations : int, optional + Maximum number of iterations + annuli : slice or indices, optional + Subset of annuli to return data for. + + Returns + ------- + Array of axial and tangential induction factors at each annulus, + shape (number of annuli, 2). + + Raises + ------ + RuntimeError if maximum number of iterations reached. + + """ + if tol is None: + tol = 1e-6 + if annuli is None: + annuli = slice(None) + + r = self.radii[annuli] + factors = self._last_factors[annuli] + for i in range(max_iterations): + lsr = LSR(windspeed, rotorspeed, r) + W, phi = inflow(lsr, factors, extra_velocity_factors) + force_coeffs = self.force_coefficients(phi, pitch, annuli) + new_factors = iterate_induction_factors(lsr, force_coeffs, + self.solidity[annuli], + pitch, factors, + extra_velocity_factors) + if np.max(abs(new_factors - factors)) < tol: + self._last_factors[annuli] = new_factors + return new_factors + factors = new_factors + raise RuntimeError("maximum iterations reached") + + def solve_wake(self, windspeed, rotorspeed, pitch, extra_velocities=None, + tol=None): + if extra_velocities is not None: + extra_velocity_factors = extra_velocities / windspeed + else: + extra_velocity_factors = None + factors = self.solve(windspeed, rotorspeed, pitch, + extra_velocity_factors) + factors[:, 0] *= windspeed + factors[:, 1] *= self.radii * rotorspeed + return factors + + def inflow_derivatives(self, windspeed, rotorspeed, pitch, + factors, extra_velocity_factors=None, annuli=None): + """Calculate the derivatives of the aerodynamic induced velocities for + an annuli + + $$ + C_T = 4 a (1-a) + \\frac{16}{3 \\pi U_0} + \\frac{R_2^3 - R_1^3}{R_2^2 - R_1^2} \\dot{a} + $$ + + """ + + if annuli is None: + annuli = slice(None) + + r = self.radii[annuli] + u = factors[:, 0] * windspeed + ut = factors[:, 1] * rotorspeed * r + if not (r.shape == u.shape == ut.shape): + raise ValueError("Shape mismatch") + + Wnorm, phi = inflow(LSR(windspeed, rotorspeed, r), + factors, extra_velocity_factors) + force_coeffs = self.force_coefficients(phi, pitch, annuli) + cx, cy = force_coeffs[:, 0], force_coeffs[:, 1] + Kx = 4 * sin(phi) ** 2 / (cx * self.solidity[annuli]) + Ky = 4 * sin(phi) * cos(phi) / (self.solidity[annuli] * cy) + + R1, R2 = self.boundaries[:-1][annuli], self.boundaries[1:][annuli] + mu = (16.0 / (3*pi)) * (R2**3 - R1**3) / (R2**2 - R1**2) + + H = _thrust_correction_factor(factors[:, 0]) + ii = abs(factors[:, 0] - 1) < 1e-3 + udot, utdot = np.zeros_like(u), np.zeros_like(ut) + + # Special case + udot[ii] = -factors[ii, 0] * (0.60*windspeed**2 + + 0.61*u[ii]*windspeed + + 0.79*u[ii]**2) / mu[ii] + + # Normal case + udot[~ii] = (4 * (windspeed - u[~ii]) * + ((windspeed - u[~ii]) / Kx[~ii] - (u / H)[~ii]) / mu[~ii]) + utdot[~ii] = (4 * (windspeed - u[~ii]) * ( + -(rotorspeed * r[~ii] + ut[~ii]) / Ky[~ii] + - ut[~ii]) / mu[~ii]) + return np.c_[udot, utdot] + + def forces(self, windspeed, rotorspeed, pitch, rho, + factors, extra_velocity_factors=None, annuli=None): + """Calculate in- and out-of-plane forces per unit length""" + + if extra_velocity_factors is None: + extra_velocity_factors = np.zeros_like(factors) + if annuli is None: + annuli = slice(None) + + factors = np.asarray(factors) + r = self.radii[annuli] + chord = self.blade.chord[annuli] + if not len(r) == factors.shape[0]: + raise ValueError("Shape mismatch") + + # Calculate force coefficients + Wnorm, phi = inflow(LSR(windspeed, rotorspeed, r), + factors, extra_velocity_factors) + W = windspeed * Wnorm + force_coeffs = self.force_coefficients(phi, pitch, annuli) + forces = (0.5 * rho * W[:, newaxis]**2 * + chord[:, newaxis] * force_coeffs) + + # Force last station to have zero force for compatibility with Bladed + # XXX this wouldn't work if the last station isn't guaranteed + # to be at the tip + if r[-1] == self.radii[-1]: + forces[-1] = (0, 0) + + return forces + + def pcoeffs(self, windspeed, rotorspeed, pitch=0.0): + # We'll nondimensionalise again later so value of rho doesn't matter + factors = self.solve(windspeed, rotorspeed, pitch) + forces = self.forces(windspeed, rotorspeed, pitch, + rho=1, factors=factors) + fx, fy = zip(*forces) + + # Integrate forces and moments about shaft + r = self.radii + thrust = self.num_blades * trapz(fx, x=r) + torque = self.num_blades * trapz(-array(fy) * r, x=r) + power = torque * rotorspeed + + # Nondimensionalise + A = pi * r[-1]**2 + CT = thrust / (0.5 * 1 * windspeed**2 * A) + CQ = torque / (0.5 * 1 * windspeed**2 * A * r[-1]) + CP = power / (0.5 * 1 * windspeed**3 * A) + + return CT, CQ, CP diff --git a/src/bemused/fast_interpolation.py b/src/bemused/fast_interpolation.py new file mode 100644 index 0000000..d4c6f56 --- /dev/null +++ b/src/bemused/fast_interpolation.py @@ -0,0 +1,34 @@ +""" +From http://stackoverflow.com/a/13504757 +""" + +from scipy.interpolate import interp1d +from scipy.interpolate._fitpack import _bspleval +import numpy as np + + +class fast_interpolation: + def __init__(self, x, y, axis=-1): + assert len(x) == y.shape[axis] + self.x = x + self.y = y + self.axis = axis + self._f = interp1d(x, y, axis=axis, kind='slinear', copy=False) + + def __getstate__(self): + return dict(x=self.x, y=self.y, axis=self.axis) + + def __setstate__(self, state): + self.x = state['x'] + self.y = state['y'] + self.axis = state['axis'] + self._f = interp1d(self.x, self.y, axis=self.axis, + kind='slinear', copy=False) + + def __call__(self, new_x): + #assert new_x.shape == y.shape + xj, cvals, k = self._f._spline.tck + result = np.empty_like(new_x) + for i, value in enumerate(new_x.flat): + result.flat[i] = _bspleval(value, self.x, cvals[:, i], k, 0) + return result diff --git a/src/bemused/models.py b/src/bemused/models.py new file mode 100644 index 0000000..e1318dd --- /dev/null +++ b/src/bemused/models.py @@ -0,0 +1,76 @@ +import numpy as np + + +class FrozenWakeAerodynamics: + """Calculate induced flows once in given initial conditions""" + + def __init__(self, bem_model, initial_wind_speed, + initial_rotor_speed, initial_pitch_angle): + self.bem_model = bem_model + # Find the frozen wake state + self.wake_state = bem_model.solve_wake(initial_wind_speed, + initial_rotor_speed, + initial_pitch_angle) + + def forces(self, wind_speed, rotor_speed, pitch_angle, rho): + shape_test = (np.asarray(wind_speed) * + np.asarray(rotor_speed) * + np.asarray(pitch_angle)) + if shape_test.ndim == 0: + # Single value + factors = self.wake_state / [wind_speed, rotor_speed] + factors[:, 1] /= self.bem_model.radii + forces = self.bem_model.forces(wind_speed, rotor_speed, + pitch_angle, rho, factors) + elif shape_test.ndim == 1: + # Multiple values + inputs = np.zeros((len(shape_test), 3)) + inputs[:, 0] = wind_speed + inputs[:, 1] = rotor_speed + inputs[:, 2] = pitch_angle + forces = np.zeros((inputs.shape[0], self.wake_state.shape[0], 2)) + for i in range(forces.shape[0]): + factors = self.wake_state / inputs[i, :2] + factors[:, 1] /= self.bem_model.radii + forces[i] = self.bem_model.forces(*inputs[i], rho=rho, + factors=factors) + else: + raise ValueError("Bad input shapes: {}".format(shape_test.shape)) + return forces + + +class EquilibriumWakeAerodynamics: + """Calculate induced flow for each requested set of conditions""" + + def __init__(self, bem_model): + self.bem_model = bem_model + + def forces(self, wind_speed, rotor_speed, pitch_angle, rho): + shape_test = (np.asarray(wind_speed) * + np.asarray(rotor_speed) * + np.asarray(pitch_angle)) + if shape_test.ndim == 0: + # Single value + wake_state = self.bem_model.solve_wake(wind_speed, + rotor_speed, + pitch_angle) + factors = wake_state / [wind_speed, rotor_speed] + factors[:, 1] /= self.bem_model.radii + forces = self.bem_model.forces(wind_speed, rotor_speed, + pitch_angle, rho, factors) + elif shape_test.ndim == 1: + # Multiple values + inputs = np.zeros((len(shape_test), 3)) + inputs[:, 0] = wind_speed + inputs[:, 1] = rotor_speed + inputs[:, 2] = pitch_angle + forces = np.zeros((inputs.shape[0], len(self.bem_model.radii), 2)) + for i in range(forces.shape[0]): + wake_state = self.bem_model.solve_wake(*inputs[i]) + factors = wake_state / inputs[i, :2] + factors[:, 1] /= self.bem_model.radii + forces[i] = self.bem_model.forces(*inputs[i], rho=rho, + factors=factors) + else: + raise ValueError("Bad input shapes: {}".format(shape_test.shape)) + return forces diff --git a/src/bemused/skeleton.py b/src/bemused/skeleton.py deleted file mode 100644 index e6c84f0..0000000 --- a/src/bemused/skeleton.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -This is a skeleton file that can serve as a starting point for a Python -console script. To run this script uncomment the following lines in the -[options.entry_points] section in setup.cfg: - - console_scripts = - fibonacci = bemused.skeleton:run - -Then run `python setup.py install` which will install the command `fibonacci` -inside your current environment. -Besides console scripts, the header (i.e. until _logger...) of this file can -also be used as template for Python modules. - -Note: This skeleton file can be safely removed if not needed! -""" - -import argparse -import sys -import logging - -from bemused import __version__ - -__author__ = "Rick Lupton" -__copyright__ = "Rick Lupton" -__license__ = "mit" - -_logger = logging.getLogger(__name__) - - -def fib(n): - """Fibonacci example function - - Args: - n (int): integer - - Returns: - int: n-th Fibonacci number - """ - assert n > 0 - a, b = 1, 1 - for i in range(n-1): - a, b = b, a+b - return a - - -def parse_args(args): - """Parse command line parameters - - Args: - args ([str]): command line parameters as list of strings - - Returns: - :obj:`argparse.Namespace`: command line parameters namespace - """ - parser = argparse.ArgumentParser( - description="Just a Fibonnaci demonstration") - parser.add_argument( - '--version', - action='version', - version='bemused {ver}'.format(ver=__version__)) - parser.add_argument( - dest="n", - help="n-th Fibonacci number", - type=int, - metavar="INT") - parser.add_argument( - '-v', - '--verbose', - dest="loglevel", - help="set loglevel to INFO", - action='store_const', - const=logging.INFO) - parser.add_argument( - '-vv', - '--very-verbose', - dest="loglevel", - help="set loglevel to DEBUG", - action='store_const', - const=logging.DEBUG) - return parser.parse_args(args) - - -def setup_logging(loglevel): - """Setup basic logging - - Args: - loglevel (int): minimum loglevel for emitting messages - """ - logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s" - logging.basicConfig(level=loglevel, stream=sys.stdout, - format=logformat, datefmt="%Y-%m-%d %H:%M:%S") - - -def main(args): - """Main entry point allowing external calls - - Args: - args ([str]): command line parameter list - """ - args = parse_args(args) - setup_logging(args.loglevel) - _logger.debug("Starting crazy calculations...") - print("The {}-th Fibonacci number is {}".format(args.n, fib(args.n))) - _logger.info("Script ends here") - - -def run(): - """Entry point for console_scripts - """ - main(sys.argv[1:]) - - -if __name__ == "__main__": - run() diff --git a/tests/benchmark.ipy b/tests/benchmark.ipy new file mode 100644 index 0000000..cbcdd10 --- /dev/null +++ b/tests/benchmark.ipy @@ -0,0 +1,28 @@ +from nose.tools import * +from numpy import pi, array, r_ +from numpy.testing import (assert_array_almost_equal, + assert_array_almost_equal_nulp, + assert_allclose) + +from bem.bem import AerofoilDatabase, BEMModel +from mbwind.blade import Blade +from timeit import Timer + + +# Load blade & aerofoil definitions +blade = Blade('tests/data/Bladed_demo_a_modified/aeroinfo.$PJ') +db = AerofoilDatabase('tests/data/aerofoils.npz') +root_length = 1.25 + +# Create BEM model, interpolating to same output radii as Bladed +model = BEMModel(blade, root_length=root_length, + num_blades=3, aerofoil_database=db) + + +print("BEM solution:") +%timeit model.solve(12.0, 22 * pi/30, 0) + +print() +print("Just calculating forces:") +factors = model.solve(12.0, 22 * pi/30, 0) +%timeit model.forces(12.0, 22 * pi/30, 0, 1.225, factors) diff --git a/tests/data/Bladed_demo_a_modified/README.md b/tests/data/Bladed_demo_a_modified/README.md new file mode 100644 index 0000000..75bd64d --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/README.md @@ -0,0 +1,15 @@ +The Bladed runs in this folder use a modified version of the standard +Bladed demo_a model: + + - The original thicknesses specified in the blade screen weren't + being honoured because the correct interpolation of foils wasn't + set up. So I changed the specified thicknesses to match what Bladed + was actually calculating. + + - The tangential induction factor calculated for the cylinder + sections didn't agree. This is apparently a bug in Bladed when the + sign of the tangential induction factor change [according to + James]. Changing the blade in Bladed to have 21% aerofoil sections + right to the root gives good agreement. + + - All simulations are run without tip loss and gravity \ No newline at end of file diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.$01 b/tests/data/Bladed_demo_a_modified/aeroinfo.$01 new file mode 100644 index 0000000000000000000000000000000000000000..88ea0a2649e1974f918999bbb4bc9600c6895624 GIT binary patch literal 1224 zcmZQzSYWTx8fBNtanhQBp}~I2hecrAymGnc7Tq0o!XIba%Xjzg>-;V2*!N1xIe1l{ zeQBM9BLjnjW4)h+14xfowWPz7-$Hh6*4$t-UY_r?2jOz7W1gRPOW9wy?6p@}S+{TZ z2`fiG)=1|)v4{6-ZP^Vr!{f{bs+n=iVw!`IjHKOz2ZwAx?mE5L&OR?!%$}`W%S%hK z)ZW}f!`|hC`o8@41&%j<-*&EUuRZkXV)Q|fUqrZdf$nLrr-m6STkbn-z1d(l_4X!P zkeSbBezJ4ETVq#c+wGNGzruclLXhpmhs*XF7S45a6)bTPKlJFZYVnFgAb0THXaKu| z8fJ8TQ*&Iq4w zb*8KLT6|J--( z&33!m7q1yNGVHZDByC>4J-C-OqtPkrO^a)X5aW@$nSa3UaJpXyb_X@gxEv?!)EnDl zSI?OacE|rs7i_QoJZfw7?xa`K%r<)+S4$fv-{X6a*{ycs-?hT^)WL6uFaKT)c8A)b zm0)*J!;EL|WSv@hzT3Wxy=4UoV|(Q{whq@jZ5vKCdcDqxw`Y_Rw27W^VsBp2cBf~P z9bDsPEjk?GB?Wc|%iFDBcTmF&1_lME1H#j+nckIKfZQSam)Y*u|3X`iP<@Y;p9Adg ZC34za-g;v1pW8cub|M2%8MFiJ4ghbA`!@gp literal 0 HcmV?d00001 diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.$PJ b/tests/data/Bladed_demo_a_modified/aeroinfo.$PJ new file mode 100644 index 0000000..51ba7d5 --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo.$PJ @@ -0,0 +1,383 @@ + + + 0.2 + + + + + Position + false + + + cdecl + + Continuous + 0 + + + false + 0 + 0 + false + 0 + 0 + + + 6.2831853071795862 + 0.8 + + + 0.3 + + false + + 0 + 0 + + false + + 0 + 0 + + 0 + false + + false + 0 + 0 + 0 + 0 + + + + 0 + 0 + + + + false + false + false + false + false + + + + 0 + 0 + + + Constant + 0 + + + + + + + diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.$TE b/tests/data/Bladed_demo_a_modified/aeroinfo.$TE new file mode 100644 index 0000000..d99e83d --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo.$TE @@ -0,0 +1 @@ + Run completed normally. diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.$VE b/tests/data/Bladed_demo_a_modified/aeroinfo.$VE new file mode 100644 index 0000000000000000000000000000000000000000..a7d4a2490e1e5fa2aa21a5dae8676c737a1e7ca7 GIT binary patch literal 31304 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 literal 0 HcmV?d00001 diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.$me b/tests/data/Bladed_demo_a_modified/aeroinfo.$me new file mode 100644 index 0000000..3591c5e --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo.$me @@ -0,0 +1,3 @@ + + Run completed normally. + diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo.%01 b/tests/data/Bladed_demo_a_modified/aeroinfo.%01 new file mode 100644 index 0000000..89c2c88 --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo.%01 @@ -0,0 +1,17 @@ +FILE aeroinfo.$01 +ACCESS D +FORM U +RECL 4 +FORMAT R*4 +CONTENT AEROINF +CONFIG LIST +NDIMENS 2 +DIMENS 17 18 +GENLAB 'Aerodynamic information' +VARIAB ELRAD AXIALA TANGA TLOSS PHI ALPHA REYN CL CD CM WINDSP DFOUT DFIN DPMOM VP VT INFX +VARUNIT L N N N A A N N N N L/T F/L F/L FL/L L/T L/T N +AXISLAB 'Distance along blade' +AXIUNIT L +AXIMETH 3 +AXIVAL 1.250 2.398 2.398 4.694 4.694 6.991 6.991 10.435 10.435 17.324 17.324 27.657 27.657 36.843 36.843 39.483 39.483 40.000 +NVARS 0 diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo_12ms_0deg.csv b/tests/data/Bladed_demo_a_modified/aeroinfo_12ms_0deg.csv new file mode 100644 index 0000000..17be089 --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo_12ms_0deg.csv @@ -0,0 +1,11 @@ +radius,axiala,tanga,dfout,dfin +1.25,0.21339852,0.006135034,154.91623,1.1145729 +2.398,0.14353901,0.0021245196,217.02072,-1.526298 +4.694,0.14952162,0.02378887,439.96753,-63.382305 +6.991,0.25166097,0.08733288,976.26984,-453.51633 +10.435,0.32524586,0.050317414,1695.584,-523.20404 +17.324,0.30019325,0.01793696,2695.0427,-529.9997 +27.657,0.29537457,0.0067527033,4253.552,-516.28955 +36.843,0.27415124,0.0035253463,5399.2573,-493.51398 +39.483,0.12062935,0.0016660067,3081.2437,-325.206 +40.0,0.004579991,5.911105e-05,0.0,0.0 diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$01 b/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$01 new file mode 100644 index 0000000000000000000000000000000000000000..c87d69d683a4cefb220bea9e3a5492b36e5403e1 GIT binary patch literal 1224 zcmZQzSYXeq9&Y#H{&O1!h6a11Nz3gMr54*4q#W|R61m+jkAIfE!J6KEY1-zFcJ=|z zpQmj@3F4W zKKnz%zIQR9j)uQ`oR2%(91J#m3O2*z%m%8Nam!+w!-0A1c0YLkg59x(F~ok&Z6*8a z#a3R&14`@@{VeSTZkX&l{vw=U2<4ffP9LuJc-hc>;R zwtJ1aY(ZwS0^QKCdcU21!6dJoBZ2m^H~a0X{~z0Xa>`Q2lZ&lf(syPbp1afOFvuNz zHyXh1poSS;-_#tnt6S~ZPZxpRF?ZQfyLI1`?Do_g@=_>RXa6UAfz4vxiF*TI-gZ33 z``2Z^tLqVogq?>#?zkF!0PGHGm~m)&m*d}wd3J3JmV@Kv&7Wqwh!q=cZ=}ETdiJ@) z{?KM!n=R*7?6t1eaJnz=v2CTlo#@&()*v%` z0u1cT^cUMIocroEXF{?4kA3nsdW?tm7I-u{iOKZ19+fdaa`W8}usfXY*MZ$Z4Kpss z2|H!Wn%KE6%m%w7{T8QPU)@1l=}uFv+V9a;8k z8Q2|ahgO2!K@BsWy_0n+IKRMltJ7I4P + + 0.2 + + + + + Position + false + + + cdecl + + Continuous + 0 + + false + + false + 0 + 0 + false + 0 + 0 + + + 6.2831853071795862 + 0.8 + + + 0.3 + + false + + 0 + 0 + + false + + 0 + 0 + + 0 + false + + false + 0 + 0 + 0 + 0 + 0 + + + + + 0 + 0 + + + + false + false + false + false + + + + 0 + 0 + + + Constant + 0 + + + + + + + diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$TE b/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$TE new file mode 100644 index 0000000..364c887 --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$TE @@ -0,0 +1 @@ +Run completed normally. diff --git a/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$VE b/tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg.$VE new file mode 100644 index 0000000000000000000000000000000000000000..5b3e876ab8587a44b20d6fdb2549ab7f6b7216fc GIT binary patch literal 31319 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 E2k_s(nxY7_`sF-N>7)wQsw35YQVur59 z5?y7kP;f~(nQ5SDEvpeE3WcRjas~cjQ+kN@kk#FvA2^&p&JjYAyqi%wq*K77(MuX3 z3Zy^3gIfP$1sHmwwL+MrXiq6TopQrEPbu3fyKFLDMUD@;Lmra?$L3-8#(4bc3gxGzp_oN*SF16}5$gLU z;kF>8Z#{@p^;g(}a@qv0T{?oI6BLFS@pbG5b`qVK8V9B3GM+U@vQw#0HUjxRGrlTb=KZoMPX8RN`L$T3 zp9$?r)gStx^<5pFUK(TT$&=X$b5M^x3j^#jiX3l&!CMyWJ>0=QPpaZ;@Obt*rmeie zHqo5d2yyW?40w&~8d@`-2S>{rQK89V*U_ev`{1ps2@{PL7u&s0< zYeJi{^EQ^KJO_kmB*Tzd8%Vf=P5vUbop$6pH7Pj`RK2w97ovq82H!k!HQ&S11ZO}0 QOEoUrpfao-OWcNm{}dzFzyJUM literal 0 HcmV?d00001 diff --git a/tests/data/Bladed_demo_a_modified/pcoeffs.$PJ b/tests/data/Bladed_demo_a_modified/pcoeffs.$PJ new file mode 100644 index 0000000..6bf2d6a --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/pcoeffs.$PJ @@ -0,0 +1,387 @@ + + + 0.2 + + + + + Position + false + + + cdecl + + Continuous + 0 + + + false + 0 + 0 + false + 0 + 0 + + + 6.2831853071795862 + 0.8 + + + 0.3 + + false + + 0 + 0 + + false + + 0 + 0 + + 0 + false + + false + 0 + 0 + 0 + 0 + 0 + + + + + 0 + 0 + + + + false + false + false + false + false + + + + 0 + 0 + + + Constant + 0 + + + + + + + diff --git a/tests/data/Bladed_demo_a_modified/pcoeffs.$TE b/tests/data/Bladed_demo_a_modified/pcoeffs.$TE new file mode 100644 index 0000000..d99e83d --- /dev/null +++ b/tests/data/Bladed_demo_a_modified/pcoeffs.$TE @@ -0,0 +1 @@ + Run completed normally. diff --git a/tests/data/Bladed_demo_a_modified/pcoeffs.$VE b/tests/data/Bladed_demo_a_modified/pcoeffs.$VE new file mode 100644 index 0000000000000000000000000000000000000000..22e6d47ea40dea1d72f1169d0e27076cac96b9d9 GIT binary patch literal 31462 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 HymRHP>2m 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 literal 0 HcmV?d00001 diff --git a/tests/regression/.gitignore b/tests/regression/.gitignore new file mode 100644 index 0000000..e94e306 --- /dev/null +++ b/tests/regression/.gitignore @@ -0,0 +1,3 @@ +# Regression test outputs +test.out.* +test.err.* diff --git a/tests/regression/demo_a_modified/aerofoils.npz b/tests/regression/demo_a_modified/aerofoils.npz new file mode 100644 index 0000000000000000000000000000000000000000..b63e3159b14cf792c1b7c046485c1f5c05dd083b GIT binary patch literal 14302 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 literal 0 HcmV?d00001 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_0deg.yaml new file mode 100644 index 0000000..08f6760 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 153.387 1.7891 0.210292 -0.0124922 4.772e-05 -1.29394e-06 +2.39815 203.504 -1.20385 0.132365 0.00207861 1.01251e-05 1.76334e-07 +4.69444 311.399 180.199 0.099716 -0.0782523 1.34187e-05 1.05725e-06 +6.99074 750.97 -493.154 0.176553 0.105583 4.64817e-06 -4.85312e-06 +10.4352 29.0672 1138.71 0.00378404 -0.0904384 9.48519e-07 -4.08017e-06 +17.3241 -158.329 2083.66 -0.0122194 -0.0590939 5.34627e-07 -2.85806e-06 +27.6574 713.251 -7.83821 0.0362126 9.17493e-05 1.43678e-06 -5.03571e-06 +36.8426 -1213.51 256.024 -0.0427491 -0.00155828 1.10895e-06 -6.67943e-06 +39.4833 1875.97 1106.16 0.069073 -0.006567 1.91769e-06 -5.15797e-07 +40 0 0 -0.0022371 -0.000470876 -6.86236e-07 -9.73041e-07 + +Pcoeffs: + +CT CQ CP +0.0327712 -0.0823662 -0.517522 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_8deg.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_8deg.yaml new file mode 100644 index 0000000..c0f60c6 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_18rpm_8deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 152.509 -22.3869 0.208657 0.155992 1.06217e-05 -2.01226e-06 +2.39815 204.159 -28.8299 0.132867 0.0498081 2.12057e-05 -4.88663e-06 +4.69444 329.16 131.496 0.106164 -0.0575148 1.48588e-05 -2.22382e-06 +6.99074 517.259 -380.905 0.112879 0.0756975 2.28752e-06 -2.51199e-06 +10.4352 160.335 1159.1 0.0212456 -0.0936995 -2.74049e-06 -4.85947e-06 +17.3241 132.509 2103.66 0.0104611 -0.0610283 -1.34923e-06 -9.10005e-06 +27.6574 -1082.77 363.666 -0.0504394 -0.00389929 1.23802e-06 -5.38174e-06 +36.8426 -1379 693.725 -0.0483204 -0.00420033 -4.97675e-07 -8.00156e-07 +39.4833 1445.58 719.408 0.0522828 -0.00419528 1.80408e-06 -6.0282e-07 +40 0 0 -0.00202172 -0.000525009 -6.33009e-07 -1.09324e-06 + +Pcoeffs: + +CT CQ CP +-0.0797784 -0.112343 -0.705871 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg.yaml new file mode 100644 index 0000000..34d2a7c --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 154.849 1.18667 0.213037 -0.00680313 1.76562e-05 4.67063e-06 +2.39815 217.198 -1.5977 0.14303 0.00228517 2.16986e-05 2.13193e-09 +4.69444 359.413 199.765 0.117397 -0.0723986 1.50319e-05 1.29345e-06 +6.99074 764.505 -424.195 0.18063 0.0746764 6.01271e-06 -8.06982e-06 +10.4352 14.4137 1545.15 0.00187277 -0.100214 1.57973e-06 -4.48021e-06 +17.3241 -231.277 2922.24 -0.0177524 -0.0674394 2.01813e-06 -1.18849e-05 +27.6574 169.146 150.919 0.00834635 -0.0014024 8.63024e-07 -4.46931e-06 +36.8426 -2730.25 490.276 -0.0918545 -0.00233185 6.26353e-07 -4.41845e-06 +39.4833 2624.44 1556.22 0.0999463 -0.00781842 1.18572e-06 -3.42244e-07 +40 0 0 -0.00346433 -0.000577082 -1.00243e-06 -1.42252e-06 + +Pcoeffs: + +CT CQ CP +-0.0594414 -0.128134 -0.983996 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg_to14ms.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg_to14ms.yaml new file mode 100644 index 0000000..0fa1197 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_12ms_22rpm_0deg_to14ms.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 208.481 2.58206 0.213037 -0.00680313 -0.359911 -0.188619 +2.39815 276.053 -1.77466 0.14303 0.00228517 -0.899833 -0.00410531 +4.69444 429.694 245.369 0.117397 -0.0723986 -0.810425 -0.167637 +6.99074 1020.55 -657.001 0.18063 0.0746764 -0.118932 0.962891 +10.4352 37.3408 1638.91 0.00187277 -0.100214 0.0452365 0.417993 +17.3241 -232.06 3029.85 -0.0177524 -0.0674394 0.0789203 0.361913 +27.6574 957.559 9.16529 0.00834635 -0.0014024 0.291046 0.066783 +36.8426 -1808.3 381.633 -0.0918545 -0.00233185 0.449213 0.0448146 +39.4833 2732.55 1614.46 0.0999463 -0.00781842 -0.167093 0.0400289 +40 0 0 -0.00346433 -0.000577082 0.0076735 0.0042832 + +Pcoeffs: + +CT CQ CP +0.0148864 -0.0916347 -0.603175 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_0deg.yaml new file mode 100644 index 0000000..18797c4 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 342.041 2.93352 0.207747 -0.0111366 8.49236e-05 -2.95837e-06 +2.39815 438.332 0.762994 0.125754 -0.000713263 4.96688e-05 5.11587e-06 +4.69444 633.916 365.944 0.0891749 -0.0856768 1.02135e-05 -4.47177e-06 +6.99074 1109.52 -818.89 0.106889 0.0881701 4.43924e-05 4.04557e-05 +10.4352 49.3089 1911.96 0.00285036 -0.0827501 -1.42055e-06 -7.72258e-06 +17.3241 -223.77 3367.82 -0.00770989 -0.0523314 1.43689e-06 -6.02085e-06 +27.6574 2371.25 -331.433 0.0545448 0.00215378 3.49901e-06 -5.80223e-06 +36.8426 -749.533 207.181 -0.0120906 -0.000708664 -1.86502e-06 -4.60612e-06 +39.4833 3031.75 1765.2 0.0485419 -0.00559277 3.91893e-06 -1.17384e-06 +40 0 0 -0.00141337 -0.000376984 -4.10713e-06 -5.82382e-06 + +Pcoeffs: + +CT CQ CP +0.0824507 -0.0495266 -0.253558 diff --git a/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_5deg.yaml b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_5deg.yaml new file mode 100644 index 0000000..2574fc4 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.12343ce.inp=input_18ms_22rpm_5deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 339.234 -27.6115 0.205445 0.10452 7.22729e-05 -9.38247e-06 +2.39815 441.028 -41.4896 0.126659 0.03882 5.02879e-05 -5.11192e-06 +4.69444 654.317 307.024 0.0923685 -0.0721352 2.02191e-05 2.52986e-06 +6.99074 1283.39 -1068.89 0.126402 0.117659 3.97175e-05 8.05936e-06 +10.4352 222.166 1951.66 0.0129741 -0.0853349 7.31856e-08 -9.55954e-06 +17.3241 34.6362 3352.28 0.00120401 -0.0525549 1.77225e-07 -5.50642e-06 +27.6574 233.089 129.839 0.00509509 -0.000801704 1.50293e-06 -4.51425e-06 +36.8426 -3606.77 895.761 -0.0557736 -0.00293737 1.35963e-06 -6.42251e-06 +39.4833 2712.32 1330.52 0.0431844 -0.00419197 2.63337e-06 -7.30348e-07 +40 0 0 -0.00145369 -0.000423747 -1.03957e-06 -1.63117e-06 + +Pcoeffs: + +CT CQ CP +-0.0216033 -0.067676 -0.346476 diff --git a/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_0deg.yaml new file mode 100644 index 0000000..08f6760 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 153.387 1.7891 0.210292 -0.0124922 4.772e-05 -1.29394e-06 +2.39815 203.504 -1.20385 0.132365 0.00207861 1.01251e-05 1.76334e-07 +4.69444 311.399 180.199 0.099716 -0.0782523 1.34187e-05 1.05725e-06 +6.99074 750.97 -493.154 0.176553 0.105583 4.64817e-06 -4.85312e-06 +10.4352 29.0672 1138.71 0.00378404 -0.0904384 9.48519e-07 -4.08017e-06 +17.3241 -158.329 2083.66 -0.0122194 -0.0590939 5.34627e-07 -2.85806e-06 +27.6574 713.251 -7.83821 0.0362126 9.17493e-05 1.43678e-06 -5.03571e-06 +36.8426 -1213.51 256.024 -0.0427491 -0.00155828 1.10895e-06 -6.67943e-06 +39.4833 1875.97 1106.16 0.069073 -0.006567 1.91769e-06 -5.15797e-07 +40 0 0 -0.0022371 -0.000470876 -6.86236e-07 -9.73041e-07 + +Pcoeffs: + +CT CQ CP +0.0327712 -0.0823662 -0.517522 diff --git a/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_8deg.yaml b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_8deg.yaml new file mode 100644 index 0000000..c0f60c6 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_18rpm_8deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 152.509 -22.3869 0.208657 0.155992 1.06217e-05 -2.01226e-06 +2.39815 204.159 -28.8299 0.132867 0.0498081 2.12057e-05 -4.88663e-06 +4.69444 329.16 131.496 0.106164 -0.0575148 1.48588e-05 -2.22382e-06 +6.99074 517.259 -380.905 0.112879 0.0756975 2.28752e-06 -2.51199e-06 +10.4352 160.335 1159.1 0.0212456 -0.0936995 -2.74049e-06 -4.85947e-06 +17.3241 132.509 2103.66 0.0104611 -0.0610283 -1.34923e-06 -9.10005e-06 +27.6574 -1082.77 363.666 -0.0504394 -0.00389929 1.23802e-06 -5.38174e-06 +36.8426 -1379 693.725 -0.0483204 -0.00420033 -4.97675e-07 -8.00156e-07 +39.4833 1445.58 719.408 0.0522828 -0.00419528 1.80408e-06 -6.0282e-07 +40 0 0 -0.00202172 -0.000525009 -6.33009e-07 -1.09324e-06 + +Pcoeffs: + +CT CQ CP +-0.0797784 -0.112343 -0.705871 diff --git a/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_22rpm_0deg.yaml new file mode 100644 index 0000000..34d2a7c --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_12ms_22rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 154.849 1.18667 0.213037 -0.00680313 1.76562e-05 4.67063e-06 +2.39815 217.198 -1.5977 0.14303 0.00228517 2.16986e-05 2.13193e-09 +4.69444 359.413 199.765 0.117397 -0.0723986 1.50319e-05 1.29345e-06 +6.99074 764.505 -424.195 0.18063 0.0746764 6.01271e-06 -8.06982e-06 +10.4352 14.4137 1545.15 0.00187277 -0.100214 1.57973e-06 -4.48021e-06 +17.3241 -231.277 2922.24 -0.0177524 -0.0674394 2.01813e-06 -1.18849e-05 +27.6574 169.146 150.919 0.00834635 -0.0014024 8.63024e-07 -4.46931e-06 +36.8426 -2730.25 490.276 -0.0918545 -0.00233185 6.26353e-07 -4.41845e-06 +39.4833 2624.44 1556.22 0.0999463 -0.00781842 1.18572e-06 -3.42244e-07 +40 0 0 -0.00346433 -0.000577082 -1.00243e-06 -1.42252e-06 + +Pcoeffs: + +CT CQ CP +-0.0594414 -0.128134 -0.983996 diff --git a/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_0deg.yaml new file mode 100644 index 0000000..18797c4 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_0deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 342.041 2.93352 0.207747 -0.0111366 8.49236e-05 -2.95837e-06 +2.39815 438.332 0.762994 0.125754 -0.000713263 4.96688e-05 5.11587e-06 +4.69444 633.916 365.944 0.0891749 -0.0856768 1.02135e-05 -4.47177e-06 +6.99074 1109.52 -818.89 0.106889 0.0881701 4.43924e-05 4.04557e-05 +10.4352 49.3089 1911.96 0.00285036 -0.0827501 -1.42055e-06 -7.72258e-06 +17.3241 -223.77 3367.82 -0.00770989 -0.0523314 1.43689e-06 -6.02085e-06 +27.6574 2371.25 -331.433 0.0545448 0.00215378 3.49901e-06 -5.80223e-06 +36.8426 -749.533 207.181 -0.0120906 -0.000708664 -1.86502e-06 -4.60612e-06 +39.4833 3031.75 1765.2 0.0485419 -0.00559277 3.91893e-06 -1.17384e-06 +40 0 0 -0.00141337 -0.000376984 -4.10713e-06 -5.82382e-06 + +Pcoeffs: + +CT CQ CP +0.0824507 -0.0495266 -0.253558 diff --git a/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_5deg.yaml b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_5deg.yaml new file mode 100644 index 0000000..2574fc4 --- /dev/null +++ b/tests/regression/demo_a_modified/benchmark.out.54da0d4.inp=input_18ms_22rpm_5deg.yaml @@ -0,0 +1,16 @@ +r fx fy a at udot utdot +1.25 339.234 -27.6115 0.205445 0.10452 7.22729e-05 -9.38247e-06 +2.39815 441.028 -41.4896 0.126659 0.03882 5.02879e-05 -5.11192e-06 +4.69444 654.317 307.024 0.0923685 -0.0721352 2.02191e-05 2.52986e-06 +6.99074 1283.39 -1068.89 0.126402 0.117659 3.97175e-05 8.05936e-06 +10.4352 222.166 1951.66 0.0129741 -0.0853349 7.31856e-08 -9.55954e-06 +17.3241 34.6362 3352.28 0.00120401 -0.0525549 1.77225e-07 -5.50642e-06 +27.6574 233.089 129.839 0.00509509 -0.000801704 1.50293e-06 -4.51425e-06 +36.8426 -3606.77 895.761 -0.0557736 -0.00293737 1.35963e-06 -6.42251e-06 +39.4833 2712.32 1330.52 0.0431844 -0.00419197 2.63337e-06 -7.30348e-07 +40 0 0 -0.00145369 -0.000423747 -1.03957e-06 -1.63117e-06 + +Pcoeffs: + +CT CQ CP +-0.0216033 -0.067676 -0.346476 diff --git a/tests/regression/demo_a_modified/demo_a_modified.bladedef b/tests/regression/demo_a_modified/demo_a_modified.bladedef new file mode 100644 index 0000000..599666b --- /dev/null +++ b/tests/regression/demo_a_modified/demo_a_modified.bladedef @@ -0,0 +1,4 @@ +0 1.14815 3.44444 5.74074 9.18519 16.0741 26.4074 35.5926 38.2333 38.75 +2.06667 2.06667 2.75556 3.44444 3.44444 2.75556 1.83704 1.14815 0.688889 0.028704 +0 0 9 13 11 7.800002 3.3 0.3 2.75 4 +21 21 21 21 21 21 15 13 13 13 diff --git a/tests/regression/demo_a_modified/input_12ms_18rpm_0deg.yaml b/tests/regression/demo_a_modified/input_12ms_18rpm_0deg.yaml new file mode 100644 index 0000000..4b665b3 --- /dev/null +++ b/tests/regression/demo_a_modified/input_12ms_18rpm_0deg.yaml @@ -0,0 +1,5 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 12.0 +rotorspeed: 18.0 +pitch: 0.0 diff --git a/tests/regression/demo_a_modified/input_12ms_18rpm_8deg.yaml b/tests/regression/demo_a_modified/input_12ms_18rpm_8deg.yaml new file mode 100644 index 0000000..962158c --- /dev/null +++ b/tests/regression/demo_a_modified/input_12ms_18rpm_8deg.yaml @@ -0,0 +1,5 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 12.0 +rotorspeed: 18.0 +pitch: 8.0 diff --git a/tests/regression/demo_a_modified/input_12ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/input_12ms_22rpm_0deg.yaml new file mode 100644 index 0000000..caadd7e --- /dev/null +++ b/tests/regression/demo_a_modified/input_12ms_22rpm_0deg.yaml @@ -0,0 +1,5 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 12.0 +rotorspeed: 22.0 +pitch: 0.0 diff --git a/tests/regression/demo_a_modified/input_12ms_22rpm_0deg_to14ms.yaml b/tests/regression/demo_a_modified/input_12ms_22rpm_0deg_to14ms.yaml new file mode 100644 index 0000000..f5ded70 --- /dev/null +++ b/tests/regression/demo_a_modified/input_12ms_22rpm_0deg_to14ms.yaml @@ -0,0 +1,6 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 12.0 +windspeed2: 14.0 +rotorspeed: 22.0 +pitch: 0.0 diff --git a/tests/regression/demo_a_modified/input_18ms_22rpm_0deg.yaml b/tests/regression/demo_a_modified/input_18ms_22rpm_0deg.yaml new file mode 100644 index 0000000..005fb3c --- /dev/null +++ b/tests/regression/demo_a_modified/input_18ms_22rpm_0deg.yaml @@ -0,0 +1,5 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 18.0 +rotorspeed: 22.0 +pitch: 0.0 diff --git a/tests/regression/demo_a_modified/input_18ms_22rpm_5deg.yaml b/tests/regression/demo_a_modified/input_18ms_22rpm_5deg.yaml new file mode 100644 index 0000000..f740c6a --- /dev/null +++ b/tests/regression/demo_a_modified/input_18ms_22rpm_5deg.yaml @@ -0,0 +1,5 @@ +blade: demo_a_modified.bladedef +aerofoil: aerofoils.npz +windspeed: 18.0 +rotorspeed: 22.0 +pitch: 5.0 diff --git a/tests/regression/jobconfig b/tests/regression/jobconfig new file mode 100644 index 0000000..c0dc021 --- /dev/null +++ b/tests/regression/jobconfig @@ -0,0 +1,2 @@ +[demo_a_modified] +inputs_args = ('input_*.yaml', '') \ No newline at end of file diff --git a/tests/regression/run-bem.py b/tests/regression/run-bem.py new file mode 100755 index 0000000..5f6bf64 --- /dev/null +++ b/tests/regression/run-bem.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +from numpy import pi, array, r_ +from bem.bem import BEMModel, AerofoilDatabase +import yaml +import sys + + +class BladeDef: + def __init__(self, filename): + with open(filename, 'rt') as f: + bdata = [array([float(x) for x in row.split()]) for row in f] + self.radii, self.chord, self.twist, self.thickness = bdata + + +def build_model(blade_filename, aerofoil_filename): + # Load blade & aerofoil definitions + blade = BladeDef(blade_filename) + db = AerofoilDatabase(aerofoil_filename) + root_length = 1.25 + + # Create BEM model, interpolating to same output radii as Bladed + return BEMModel(blade, root_length=root_length, num_blades=3, + aerofoil_database=db, unsteady=True) + + + # # Same windspeed and rotor speed as the Bladed run + # windspeed = 12 # m/s + # rotorspeed = 22 * (pi/30) # rad/s + + +def find_solution(model, windspeed, rotorspeed, pitch, windspeed2=None): + # Calculate forces and derivatives at different windspeed? + if windspeed2 is None: + windspeed2 = windspeed + + # Along blade + factors = model.solve(windspeed, rotorspeed, pitch) + forces = model.forces(windspeed2, rotorspeed, pitch, 1.225, factors) + xdot = model.inflow_derivatives(windspeed2, rotorspeed, pitch, + factors) + + assert len(model.radii) == len(factors) == len(forces) == len(xdot) + print("\t".join(['r', 'fx', 'fy', 'a', 'at', 'udot', 'utdot'])) + for i in range(len(model.radii)): + row = r_[model.radii[i], forces[i], factors[i], xdot[i]] + print("\t".join(["%g" % xx for xx in row])) + + # Overall rotor + pcoeffs = model.pcoeffs(windspeed2, rotorspeed, pitch) + print() + print("Pcoeffs:") + print() + print("\t".join(['CT', 'CQ', 'CP'])) + print("\t".join(["%g" % xx for xx in pcoeffs])) + + +with open(sys.argv[1], 'rt') as f: + inp = yaml.safe_load(f) + +model = build_model(inp['blade'], inp['aerofoil']) +find_solution(model, inp['windspeed'], pi/30 * inp['rotorspeed'], + pi/180 * inp['pitch'], inp.get('windspeed2', None)) diff --git a/tests/regression/userconfig b/tests/regression/userconfig new file mode 100644 index 0000000..390a54e --- /dev/null +++ b/tests/regression/userconfig @@ -0,0 +1,14 @@ +[user] +benchmark = 12343ce + +[run-bem] +exe = run-bem.py +vcs = git +extract_program = cat +tolerance = \ + (None, 1e-5, 'fx', False), \ + (None, 1e-5, 'fy', False), \ + (1e-5, 1e-3, 'a', False), \ + (1e-5, 1e-2, 'at', False), \ + (1e-4, None, 'udot', False), \ + (1e-4, None, 'utdot', False) \ No newline at end of file diff --git a/tests/test_BEMModel.py b/tests/test_BEMModel.py new file mode 100644 index 0000000..b779593 --- /dev/null +++ b/tests/test_BEMModel.py @@ -0,0 +1,117 @@ +import unittest +import numpy as np +from numpy import pi, array +from numpy.testing import assert_array_almost_equal as assert_aae + +import utils +from bemused import BEMModel + + +class TestBEMModelRealistic(unittest.TestCase): + def setUp(self): + self.model = utils.get_test_model() + + def test_solve_wake_is_same_as_solve(self): + args = (12.2, 12*pi/30, 0) + factors = self.model.solve(*args) + wake = self.model.solve_wake(*args) + assert_aae(factors[:, 0], wake[:, 0] / args[0]) + assert_aae(factors[:, 1], wake[:, 1] / args[1] / self.model.radii) + + def test_solve_wake_is_same_as_solve_with_extra_factors(self): + args = (12.2, 12*pi/30, 0) + wake = self.model.solve_wake(*args) + extra_velocities = wake * 0.43 + extra_velocity_factors = extra_velocities / args[0] + + factors = self.model.solve( + *args, extra_velocity_factors=extra_velocity_factors) + wake = self.model.solve_wake(*args, extra_velocities=extra_velocities) + + assert_aae(factors[:, 0], wake[:, 0] / args[0]) + assert_aae(factors[:, 1], wake[:, 1] / args[1] / self.model.radii) + + def test_lift_drag_one_annulus(self): + alpha = np.random.rand(len(self.model.radii)) * pi + all_lift_drag = self.model.lift_drag(alpha) + one_lift_drag = self.model.lift_drag(alpha[2:3], annuli=slice(2, 3)) + another_lift_drag = self.model.lift_drag(alpha[2:3], annuli=[2]) + self.assertIsInstance(one_lift_drag, np.ndarray) + assert_aae(one_lift_drag, all_lift_drag[2:3, :]) + assert_aae(another_lift_drag, all_lift_drag[2:3, :]) + + def test_force_coefficients_one_annulus(self): + phi = np.random.rand(len(self.model.radii)) * pi + all_coeffs = self.model.force_coefficients(phi, 0) + one_coeffs = self.model.force_coefficients(phi[2:3], 0, slice(2, 3)) + assert_aae(one_coeffs, all_coeffs[2:3, :]) + + def test_inflow_derivatives_with_one_annulus(self): + args = (12.2, 12*pi/30, 0) + factors = self.model.solve(*args) + all_derivs = self.model.inflow_derivatives(*args, factors=factors*1.1) + one_derivs = self.model.inflow_derivatives(*args, + factors=factors[2:3]*1.1, + annuli=slice(2, 3)) + assert_aae(one_derivs, all_derivs[2:3, :]) + + def test_solve_with_one_annulus(self): + args = (12.2, 12*pi/30, 0) + all_factors = self.model.solve(*args) + one_factors = self.model.solve(*args, annuli=slice(2, 3)) + assert_aae(one_factors, all_factors[2:3, :]) + + def test_forces_with_one_annulus(self): + args = (12.2, 12*pi/30, 0) + factors = self.model.solve(*args) + all_forces = self.model.forces(*args, rho=1.225, factors=factors) + one_forces = self.model.forces(*args, rho=1.225, factors=factors[2:3], + annuli=slice(2, 3)) + assert_aae(one_forces, all_forces[2:3, :]) + + def test_one_annulus_fails_with_wrong_shapes(self): + args = (12.2, 12*pi/30, 0) + factors = self.model.solve(*args) + + with self.assertRaises(ValueError): + self.model.inflow_derivatives(*args, + factors=factors[2:4], + annuli=slice(2, 3)) + + # solve() cannot be given mismatching shapes so ok + + with self.assertRaises(ValueError): + self.model.forces(*args, rho=1.225, factors=factors[2:4], + annuli=slice(2, 3)) + + with self.assertRaises(ValueError): + phi = array([5, 4, 3, 2]) * pi / 180 + self.model.force_coefficients(phi[2:4], 0, slice(2, 3)) + + with self.assertRaises(ValueError): + alpha = array([5, 4, 3, 2]) * pi / 180 + self.model.lift_drag(alpha[2:4], annuli=slice(2, 3)) + + +class TestBEMModelSimple(unittest.TestCase): + root_length = 10 + + def setUp(self): + class MockBlade: + x = array([0, 2, 4, 6]) + chord = array([1, 1, 1, 1]) + twist = array([1, 1, 1, 1]) * pi / 180 + thickness = array([1, 1, 1, 1]) + + class MockDatabase: + def for_thickness(self, thickness): + return array([[-2*pi*pi, 0.2], + [+2*pi*pi, 0.2]]) + alpha = array([-pi, pi]) + + self.model = BEMModel(MockBlade(), self.root_length, + 3, MockDatabase()) + + def test_annuli_edges_are_correct(self): + self.assertEqual(list(self.model.boundaries), + [10, 11, 13, 15, 16]) diff --git a/tests/test_aerofoil_database.py b/tests/test_aerofoil_database.py new file mode 100644 index 0000000..42632f3 --- /dev/null +++ b/tests/test_aerofoil_database.py @@ -0,0 +1,39 @@ +import unittest +from numpy import pi +from scipy.interpolate import interp1d +from numpy.testing import assert_array_almost_equal as assert_aae + +from bemused import AerofoilDatabase + + +class AerofoilDatabaseTestCase(unittest.TestCase): + def setUp(self): + self.db = AerofoilDatabase('tests/data/aerofoils.npz') + + # CylinderData: + def test_cylinder_data_read_correctly(self): + lift_drag = self.db.for_thickness(1.0) + assert_aae(lift_drag[:, 0], 0) # lift + assert_aae(lift_drag[:, 1], 1) # drag + + # ThirteenPercentFoil: + def test_thirteen_percent_foil_read_correctly(self): + lift_drag_values = self.db.for_thickness(0.13) + lift_drag = interp1d(self.db.alpha, lift_drag_values, axis=0) + assert_aae(lift_drag(0), [0.420, 0.006]) + assert_aae(lift_drag(10*pi/180), [1.460, 0.016]) + + def test_thirteen_percent_foil_interpolated_by_alpha(self): + # Interpolate between 4 and 6 deg alpha + lift_drag_values = self.db.for_thickness(0.13) + lift_drag = interp1d(self.db.alpha, lift_drag_values, axis=0) + assert_aae(lift_drag(5*pi/180), [(0.890 + 1.100) / 2, + (0.009 + 0.012) / 2]) + + # InterpolatedData: + def interpolated_by_thickness(self): + # Interpolate between 13% and 17% data + lift_drag_values = self.db.for_thickness(0.15) + lift_drag = interp1d(self.db.alpha, lift_drag_values, axis=0) + assert_aae(lift_drag(10*pi/180), [(1.460 + 1.500) / 2, + (0.016 + 0.014) / 2]) diff --git a/tests/test_blade.py b/tests/test_blade.py new file mode 100644 index 0000000..e123a63 --- /dev/null +++ b/tests/test_blade.py @@ -0,0 +1,45 @@ +import unittest +import numpy as np +from numpy import pi +from numpy.testing import assert_array_equal +from bemused import Blade +from io import StringIO + +# NB twist is in degrees +yaml = """ +x: [0.2, 3.2, 4.3] +chord: [2.3, 1.0, 3] +twist: [2, 5, 0.2] +thickness: [100, 100, 43] +""" + + +class TestBlade(unittest.TestCase): + def test_it_works(self): + blade = Blade([1], [2], [3], [4]) + self.assertEqual(blade.x, [1]) + self.assertEqual(blade.chord, [2]) + self.assertEqual(blade.twist, [3]) + self.assertEqual(blade.thickness, [4]) + + def test_yaml_loading(self): + blade = Blade.from_yaml(StringIO(yaml)) + assert_array_equal(blade.x, [0.2, 3.2, 4.3]) + assert_array_equal(blade.chord, [2.3, 1.0, 3]) + assert_array_equal(blade.twist, [x*pi/180 for x in [2, 5, 0.2]]) + assert_array_equal(blade.thickness, [100, 100, 43]) + + def test_resampling(self): + blade = Blade.from_yaml(StringIO(yaml)) + x = [1.2, 3.0, 4.3] + b2 = blade.resample(x) + + assert_array_equal(b2.x, x) + assert_array_equal(b2.chord, np.interp(x, blade.x, blade.chord)) + assert_array_equal(b2.twist, np.interp(x, blade.x, blade.twist)) + assert_array_equal(b2.thickness, + np.interp(x, blade.x, blade.thickness)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_fast_interpolation.py b/tests/test_fast_interpolation.py new file mode 100644 index 0000000..ad5d5d9 --- /dev/null +++ b/tests/test_fast_interpolation.py @@ -0,0 +1,47 @@ +""" +From http://stackoverflow.com/a/13504757 +""" + +import unittest +from scipy.interpolate import interp1d +import numpy as np +from bemused.fast_interpolation import fast_interpolation +import pickle + + +# Simple interpolation along middle axis, at each point within y +def original_interpolation(new_x, x, y): + result = np.empty((y.shape[0], y.shape[2])) + for i in range(y.shape[0]): + for j in range(y.shape[2]): + f = interp1d(x, y[i, :, j], axis=-1, kind='slinear') + result[i, j] = f(new_x[i, j]) + return result + + +class FastInterpolationTestCase(unittest.TestCase): + def test_interpolation(self): + # Interpolate along y + nx, ny, nz = 30, 40, 2 + x = np.arange(0, ny, 1.0) + y = np.random.randn(nx, ny, nz) + new_x = np.random.randint(1, (ny-1)*10, size=(nx, nz))/10.0 + + r1 = original_interpolation(new_x, x, y) + r2 = fast_interpolation(x, y, axis=1) + np.testing.assert_allclose(r1, r2(new_x)) + + def test_picklable(self): + # Interpolate along y + nx, ny, nz = 30, 40, 2 + x = np.arange(0, ny, 1.0) + y = np.random.randn(nx, ny, nz) + new_x = np.random.randint(1, (ny-1)*10, size=(nx, nz))/10.0 + + orig = fast_interpolation(x, y, axis=1) + pickled = pickle.loads(pickle.dumps(orig)) + np.testing.assert_allclose(orig(new_x), pickled(new_x)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_misc_functions.py b/tests/test_misc_functions.py new file mode 100644 index 0000000..f48797f --- /dev/null +++ b/tests/test_misc_functions.py @@ -0,0 +1,109 @@ +import unittest +from numpy import pi, sin, cos, array +import numpy as np +from numpy.testing import assert_array_almost_equal as assert_aae + +from bemused import iterate_induction_factors, inflow +from bemused.bem import _strip_boundaries, _thrust_correction_factor + + +class strip_boundaries_TestCase(unittest.TestCase): + def test_boundaries(self): + # 0 1 2 3 4 5 6 7 + # Input: X X . X . . X X + # Result: | | | | | | + radii = [0, 1, 3, 6, 7] + + expected = [0.0, 0.5, 2.0, 4.5, 6.5, 7.0] + result = _strip_boundaries(radii) + self.assertEqual(list(result), expected) + + +class thrust_correction_factor_TestCase(unittest.TestCase): + def test_no_correction_for_small_a(self): + self.assertEqual(_thrust_correction_factor(0), 1) + + def test_smooth_transition(self): + a_transition = 0.3539 + H1 = _thrust_correction_factor(a_transition) + H2 = _thrust_correction_factor(a_transition + 1e-4) + self.assertLess((H1 - H2), 1e-3) + + +class iterate_induction_factors_TestCase(unittest.TestCase): + def test_there_is_no_induction_if_no_lift(self): + force_coeffs = np.array([[0, 0]]) + factors = iterate_induction_factors(1, force_coeffs, 0.21, 0, + array([[0, 0]])) + self.assertEqual(factors[0, 0], 0) + self.assertEqual(factors[0, 0], 0) + + def test_imitating_lift_coefficient(self): + # Try to set lift coefficient so that thrust force is equal to + # expected value from momentum theory. This should mean the + # calculation is already converged. + + # Choose some values + U = 5.4 # wind speed + w = 1.2 # rotor speed + r = 15.3 # radius + a = 0.14 # induction factor + Nb = 3 # number of blades + c = 1.9 # chord + + def lift_drag(alpha): + # Thrust should be 2 rho A U^2 a (1-a)` + # for annulus, A = 2 pi r dr + # If no drag, then + # thrust on blade = 0.5 rho c (U(1-a)/sin(phi))^2 CL dr cos(phi) + # So CL = (8 pi r a sin^2 phi) / ((1-a) Nb c cos(phi)) + CL = 8*pi*r * a * sin(alpha)**2 / ((1-a) * Nb * c * cos(alpha)) + return [CL[0], 0] + + LSR = w * r / U + solidity = Nb * c / (2*pi*r) + factors = np.array([[a, 0]]) + + Wnorm, phi = inflow(LSR, factors) + cphi, sphi = np.cos(phi[0]), np.sin(phi[0]) + A = array([[cphi, sphi], [-sphi, cphi]]) + force_coeffs = np.array([np.dot(A, lift_drag(phi))]) + factors = iterate_induction_factors(LSR, force_coeffs, + solidity, 0, factors) + assert_aae(a, factors[0, 0]) + assert np.all(factors[:, 1] > 0) + # XXX could check at better + + +class inflow_TestCase(unittest.TestCase): + def assertInflow(self, lsr, factors, extra, expected): + if extra is not None: + extra = np.atleast_2d(extra) + factors = np.atleast_2d(factors) + self.assertEqual(inflow(lsr, factors, extra), expected) + + def test_simple_cases(self): + # Zero LSR -> not rotating. + self.assertInflow(0, (0, 0), None, (1, pi/2)) + + # Zero LSR, axial induction -> no flow or double flow + self.assertInflow(0, (1, 0), None, (0, 0)) + self.assertInflow(0, (-1, 0), None, (2, pi/2)) + + # LSR = 1 -> angle should be 45 deg with no induction + self.assertInflow(1, (0, 0), None, (2**0.5, pi/4)) + + # LSR = 1, axial induction of 1 -> flow should be in-plane + self.assertInflow(1, (1, 0), None, (1, 0)) + + # LSR = 1, axial induction of 1, tangential inflow of +/-1 + # -> flow should be in-plane, zero or double + self.assertInflow(1, (1, -1), None, (0, 0)) + self.assertInflow(1, (1, 1), None, (2, 0)) + + def test_blade_velocities(self): + # Zero LSR, blade moving downwind at windspeed -> no flow + self.assertInflow(0, (0, 0), (1, 0), (0, 0)) + + # Zero LSR, blade moving upwind at windspeed -> double flow + self.assertInflow(0, (0, 0), (-1, 0), (2, pi/2)) diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..116ffde --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,116 @@ +import unittest +import numpy as np +from numpy import pi +from numpy.testing import assert_allclose +from bemused.models import FrozenWakeAerodynamics, EquilibriumWakeAerodynamics +import utils + + +class TestFrozenWakeAerodynamics(unittest.TestCase): + def setUp(self): + self.bem = utils.get_test_model() + + def test_calculated_forces_for_frozen_wake_state(self): + # Initial and later conditions + args0 = (12.2, 12*pi/30, 0.0) + args1 = (13.1, 10*pi/30, 2.3) + + # Expected result; NB *wake* is frozen, not *factors* + factors = self.bem.solve(*args0) + factors[:, 0] *= args0[0] / args1[0] # correct for changing mean wind + factors[:, 1] *= args0[1] / args1[1] # correct for changing rotor spd + expected_forces = self.bem.forces(*args1, rho=1.225, factors=factors) + + # Test + model = FrozenWakeAerodynamics(self.bem, *args0) + forces = model.forces(*args1, rho=1.225) + assert_allclose(forces, expected_forces) + + def test_broadcasting(self): + # Initial and later conditions + args0 = (12.2, 12*pi/30, 0.0) + scaling = np.arange(0.8, 1.21, 0.5) + + # Vary each parameter + for j in range(3): + # Expected result; NB *wake* is frozen, not *factors* + expected = np.zeros((len(scaling), len(self.bem.radii), 2)) + for i in range(len(scaling)): + factors = self.bem.solve(*args0) + args = list(args0) + args[j] *= scaling[i] + + # correct for changing mean wind and rotor speed + factors[:, 0] *= args0[0] / args[0] + factors[:, 1] *= args0[1] / args[1] + + expected[i] = self.bem.forces(*args, rho=1.225, + factors=factors) + + # Test + model = FrozenWakeAerodynamics(self.bem, *args0) + args = list(args0) + args[j] = scaling * args0[j] + forces = model.forces(*args, rho=1.225) + assert_allclose(forces, expected, rtol=1e-6) + + def test_bad_shapes(self): + # Initial and later conditions + args0 = (12.2, 12*pi/30, 0.0) + model = FrozenWakeAerodynamics(self.bem, *args0) + with self.assertRaises(ValueError): + model.forces(np.random.random((4, 2)), + args0[1], args0[2], rho=1.225) + + +class TestEquilibriumWakeAerodynamics(unittest.TestCase): + def setUp(self): + self.bem = utils.get_test_model() + + def test_calculated_forces_for_equilibrium_wake_state(self): + # Initial conditions not used. Later conditions: + args1 = (13.1, 10*pi/30, 2.3) + + # Expected result - wake solved for final state + factors = self.bem.solve(*args1) + expected_forces = self.bem.forces(*args1, rho=1.225, factors=factors) + + # Test + model = EquilibriumWakeAerodynamics(self.bem) + forces = model.forces(*args1, rho=1.225) + assert_allclose(forces, expected_forces) + + def test_broadcasting(self): + # Initial and later conditions + args0 = (12.2, 12*pi/30, 0.0) + scaling = np.arange(0.8, 1.21, 0.5) + + # Vary each parameter + for j in range(3): + # Expected result; NB *wake* is frozen, not *factors* + expected = np.zeros((len(scaling), len(self.bem.radii), 2)) + for i in range(len(scaling)): + args = list(args0) + args[j] *= scaling[i] + factors = self.bem.solve(*args) + expected[i] = self.bem.forces(*args, rho=1.225, + factors=factors) + + # Test + model = EquilibriumWakeAerodynamics(self.bem) + args = list(args0) + args[j] = scaling * args0[j] + forces = model.forces(*args, rho=1.225) + assert_allclose(forces, expected, rtol=1e-5) + + def test_bad_shapes(self): + # Initial and later conditions + args0 = (12.2, 12*pi/30, 0.0) + model = EquilibriumWakeAerodynamics(self.bem) + with self.assertRaises(ValueError): + model.forces(np.random.random((4, 2)), + args0[1], args0[2], rho=1.225) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_results_against_Bladed.py b/tests/test_results_against_Bladed.py new file mode 100644 index 0000000..44a8985 --- /dev/null +++ b/tests/test_results_against_Bladed.py @@ -0,0 +1,111 @@ +import unittest +from numpy import pi, array, r_ +from numpy.testing import (assert_array_almost_equal, + assert_allclose) + +import pandas as pd +import utils + + +# De-duplicate repeated stations in Bladed output +def dedup(x): + return r_[x[::2], x[-1:]] + + +class BEMModel_aeroinfo_base: + def setUp(self): + # Load reference results from Bladed + self.bladed = pd.read_csv(self.BLADED_RUN + '.csv', index_col=0) + + # Create BEM model, interpolating to same output radii as Bladed + self.model = utils.get_test_model(self.bladed.index.values) + + def test_solution_against_Bladed(self): + factors = self.model.solve(*self.ARGS) + a, at = zip(*factors) + + # Bladed results + ba = self.bladed['axiala'] + bat = self.bladed['tanga'] + + assert_array_almost_equal(a, ba, decimal=2) + assert_array_almost_equal(at, bat, decimal=2) + + def test_forces_against_Bladed(self): + # Same windspeed and rotor speed as the Bladed run + factors = self.model.solve(*self.ARGS) + forces = self.model.forces(*self.ARGS, rho=1.225, factors=factors) + mfx, mfy = map(array, zip(*forces)) + + # Bladed results + bfx = self.bladed['dfout'] + bfy = self.bladed['dfin'] + + assert_array_almost_equal(mfx / abs(mfx).max(), + bfx / abs(mfx).max(), decimal=3) + assert_array_almost_equal(mfy / abs(mfy).max(), + bfy / abs(mfy).max(), decimal=2) + + def test_solve_finds_equilibrium_solution(self): + factors = self.model.solve(*self.ARGS) + xdot = self.model.inflow_derivatives(*self.ARGS, factors=factors) + assert_allclose(xdot, 0, atol=1e-4) + + def test_inflow_derivatives_with_one_annulus(self): + factors = self.model.solve(*self.ARGS) + all_derivs = self.model.inflow_derivatives( + *self.ARGS, factors=factors*1.1) + one_derivs = self.model.inflow_derivatives( + *self.ARGS, factors=factors[7:8]*1.1, annuli=slice(7, 8)) + assert_array_almost_equal(one_derivs, all_derivs[7:8, :]) + + +class BEMModel_Test_aeroinfo_12ms(BEMModel_aeroinfo_base, unittest.TestCase): + BLADED_RUN = 'tests/data/Bladed_demo_a_modified/aeroinfo_12ms_0deg' + ARGS = ( + 12.0, # windspeed [m/s] + 22 * (pi/30), # rotor speed [rad/s] + 0 * (pi/180), # pitch angle [rad] + ) + + +class BEMModel_Test_aeroinfo_14ms(BEMModel_aeroinfo_base, unittest.TestCase): + BLADED_RUN = 'tests/data/Bladed_demo_a_modified/aeroinfo_14ms_2deg' + ARGS = ( + 14.0, # windspeed [m/s] + 22 * (pi/30), # rotor speed [rad/s] + 2 * (pi/180), # pitch angle [rad] + ) + + +class BEMModel_Test_pcoeffs(unittest.TestCase): + BLADED_RUN = 'tests/data/Bladed_demo_a_modified/pcoeffs' + + def setUp(self): + # Load reference results from Bladed + self.bladed = pd.read_csv(self.BLADED_RUN + '.csv', index_col=0) + + # Create BEM model + self.model = utils.get_test_model() + + def test_coefficients_against_Bladed(self): + # Same rotor speed and TSRs as the Bladed run + rotorspeed = 22 * (pi/30) # rad/s + + # Don't do every TSR -- just a selection + nth = 4 + TSRs = self.bladed.index.values[::nth] + print('XXX', TSRs) + R = self.model.radii[-1] + windspeeds = (R*rotorspeed/TSR for TSR in TSRs) + coeffs = [self.model.pcoeffs(ws, rotorspeed) for ws in windspeeds] + CT, CQ, CP = zip(*coeffs) + + # Bladed results + bCT = self.bladed['thcoef'][::nth] + bCQ = self.bladed['tocoef'][::nth] + bCP = self.bladed['pocoef'][::nth] + + assert_array_almost_equal(CT, bCT, decimal=2) + assert_array_almost_equal(CQ, bCQ, decimal=2) + assert_array_almost_equal(CP, bCP, decimal=2) diff --git a/tests/test_skeleton.py b/tests/test_skeleton.py deleted file mode 100644 index 825f469..0000000 --- a/tests/test_skeleton.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import pytest -from bemused.skeleton import fib - -__author__ = "Rick Lupton" -__copyright__ = "Rick Lupton" -__license__ = "mit" - - -def test_fib(): - assert fib(1) == 1 - assert fib(2) == 1 - assert fib(7) == 13 - with pytest.raises(AssertionError): - fib(-10) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..ea4edde --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,11 @@ +from bemused import BEMModel, AerofoilDatabase, Blade + +def get_test_model(radii=None): + root_length = 1.25 + blade = Blade.from_yaml('tests/data/Bladed_demo_a_modified/blade.yaml') + if radii is not None: + x = radii - root_length + blade = blade.resample(x) + db = AerofoilDatabase('tests/data/aerofoils.npz') + model = BEMModel(blade, root_length, num_blades=3, aerofoil_database=db) + return model diff --git a/tox.ini b/tox.ini index 88316c6..e234eca 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ # Tox configuration file # Read more under https://tox.readthedocs.org/ -# THIS SCRIPT IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! [tox] minversion = 2.4