From 51629665d22f92770f861daeebcebd47ab32abb1 Mon Sep 17 00:00:00 2001 From: dzalkind <65573423+dzalkind@users.noreply.github.com> Date: Tue, 9 Aug 2022 10:29:17 -0600 Subject: [PATCH] ROSCO 2.6.0 (#162) * FOCAL Updates (#64) * Update headers * fix bullets * make uppercase * Update turbine.py (#56) * Update turbine.py This add several lines for fixing the problem of repeated maximum values in the performance tables. This will cause the error (' the length of x and y is different.') of 'interpolate.interp1d.' * Add comments and catch when there are multiple optimal pitch angles Co-authored-by: dzalkind * increment version * Update for OpenFAST v3.0.0 * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Update Testing (#58) * Update scripts to run on eagle * Update IEA-15MW semi example: use peak shaving w/ ps_percen=0.8 * Add comparison plots to testing scripts * Update submit script for testing * Update for latest eagle runs * Add future to install dependencies * add TMax to self, define tmin in print_results * run tests in CI * generic ROSCO path * default to overwrite * fix path * import platform * separate run_testing * cleanup, specify lite test * don't run testing in after examples, oops. Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Increment Version, OF3.0 (#57) * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * add ROSCO without submodule * move ROSCO source to ROSCO folder * Move cmake-related files to ROSCO * Add back pesky ErrVar * Remove parameters_files * Merge ROSCO and _toolbox gitignore * Fix .gitignore * Remove Examples/DISCON.IN from git * Fix and point example_01 to Tune_Cases/ * Update verbiage around using ofTools vs. weis * Fix and point example_04 to Tune_Cases/ * Clean up example_06 * Clean up example_07 * Only check FlpCornerFreq if using Flp control, fixes example 05 * Make example_04 consistent with others * Let example_05 run independently from 04 * Clean up example_05, wind files * Add schema and update empty tuning yaml inputs, not connected yet * Integrate schema into turbine, controller, and examples * Only check Fl filter parameters if Fl_Mode > 0, fix example_05 * bump version to 2.3 * Compile ROSCO from ROSCO dir * Rename to CI_rosco * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * Make _Toolbox vs_minspeed in rotor frame to match ROSCO * Revert ServoDyn change * change rotor speed constraint to be epsilon * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * Deallocate arrays in ROSCO, check in example_05 * Clean up comments * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * more detailed sp.optimize settings * run MBC3 in parallel * restructure driver, run initialization doe for tuning * Try new dlclose function * Update example_05 to run simple simulation twice and check result * Revert deallocation stuff * Close discon library after every sim run * Test examples on macOS and windows * Run examples instead of testing on other platforms * Skip examples in windows for now * update paths and yaml load funciton * Skip mac testing of examples * provide default U_pc for single omega/zeta case * allow for float or list-like pc tuning inputs * Change name in setup.py * WE_Vw unit fix * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * cleeanup for improved stability * check for doe_logs as string in load_DOE * major restructure for rsched_driver class * cleanup verbosity * run serial by default * load_parallel as linturb_option * specific IEA15MW yaml for multi omega * remove unused module imports * fix error message types * lin_file as input * add comments on inputs to LinearTurbineModel init method * remove relative file paths * provide OpenFAST linearizations for IEA15MW UMaineSemi * put plotting in specific function * fix WE_lambda units * add self in on a few necessary variables * creaete example 12 for robust scheduling * try a few mbc3 locations for import * allow list-like or numpy arrays for omega_pc and zeta_pc schedules * create and use recorder setup function * Pass Through Kp_float (#57) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * fix setup_recorder to work for optimization driver too * Allow pass through of Kp_float = 0 * cleanup om problems, update om0 calc * doe levels as input * negative k_float to account for OF conventions * cleanup print statements * variable name cleanup, use calculated k_float * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * use standard tuning k_float as IC * formatting update * update problem setup methods * cleanup add_dv, enable adding design variables after problem is setup * change optimization step size * more setup restructure * update verbosity * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Mostly a docs update (#61) * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Pass through Kp_float = 0 (#59) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * Allow pass through of Kp_float = 0 * Add flp parameters to schema * Change Fl_Mode default to 0 * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * Allow single pitch tuning values in code, default U_pc to 0 * use nac acceleration for floating feedback * Fix TSR saturation for region 2.5operation * Modify system for constant power operation * Only modify pole for constant power above-rated * Remove GenEff from K calc * Update tuning, use constant power * use load_rosco_yaml * constant power * Fix broken tests * Include Fl_Mode=2 for nacelle pitching feedback * Add FOCAL inputs - hpf on floating feedback - lpf on wind speed estimator - associated schema updates * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Skip filter step if there's an error * Update IEA-15MW test case DISCON with focal inputs * Allow Fl_Mode = 2 in ROSCO * Pass through lpf frequency * Add FOCAL tuning yaml * Set Cp contour number of levels * Add FOCAL params to various writers * Update/tune focal yaml * Add scripts for running FAST, tuning various parameters and cases * Add notebook for FAST plotting * Set up step case for testing * Change doubles to C_doubles * Define real and integer kinds, assign to all of ROSCO * Add ADJUSTL to DISCON error message * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Call yaw, flap, and debug only if enabled in DISOCN.IN * Make avrSWAP a ReKi and set constant kinds * Add DISCONs for testing - revert this later * Fix DISCON comparison, before DISCON's were overwritten by model * Rename DEBUG2.dbg to RootName.dbg2 * Update TestCase DISCONs to new input file * Add API change page in docs * Add link to API change on main page * Fix table headers * Fix title underlines * Fix tables again * Fix tables again * Fix version numbering in docs * Simplify FAST_directory in run_FAST * Versioning (#65) * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * remove git versioning from cmake * use hard coded rosco_version * update intro write method * set nowrap for intel compilers * Add transfer of error message and clear message after each call * update install instructions * Catch nans in ROSCO at end of WSE * fix conda install typo * cleanup docs * Rename DEBUG2.dbg to RootName.dbg2 * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Allow Fl_Mode = 2 in ROSCO * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Update FOCAL tuning yaml * Update TSR * Clean up and doc fix * Remove publish to pypi * Define all constant inputs to functions with kind typing * Generate Test_Case/ inputs automatically * Fix IEA15 DISCON path * Fix example 11 paths * Auto-generate tuning input yaml using schema * Add toolbox_input to doc index * Add toctree * Re-name title of toolbox_input Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> * update listcheck method for numpy arrays * Open Loop Control (#98) * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * Saturate inputs to WSE. Needs some TLC, but seems to work * Reduce saturation limits on speed, torque * Re-organize, set saturation limits. Working at 3 m/s * Initial add of OL control to ROSCO: builds * Update DISCONs with open loop inputs * Fix file reading for OL_Filename * Add open loop control generation and file writing * Use DISCON_dict for more manageable DISCON file writing * Add open loop example, fix constant timeseries * Make open loop example generate power * Handle relative paths and calling from outside the run directory: - Some helper functions borrowed from OpenFAST, f/ext_control - Updated file writing * Clean up: versions, print statements * Fix SysFiles paths in CMakeLists * Tidy up Ext_DLL names * More Ext_DLL name tidying * Test write_registry.py * Update for OL Control * Move preprocessor lines * add zenodo DOI * Regenerated Types * fix shape * revert filepath change * give all types a size, ProcAddr size = 3 * update types * test registry in compile step * specify default shell * update write_registry path * remove default shell * Document API changes, provide OL input example * Fix example 14 (yaw input) * Add error catching to yaw control * Tidy up OL_Input Reading: error catching, generalize * More yaw control fixes, to model * Checkout develop CMakeLists for ROSCO * Update DISCON.INs for TestCases/ * Revert "Checkout develop CMakeLists for ROSCO" This reverts commit 87a491359d73806e3aaa59b4cfdc00f2838875df. * Revert windows cmake stuff to develop * Fix CMake again * Revert "Revert windows cmake stuff to develop" This reverts commit 39df122449b0e2055f2e12e3ce38a0c010c7bd91. * Make last cmake fix - hopefully Co-authored-by: Nikhar Abbas * Restart & registry (#99) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars Co-authored-by: dzalkind * Break up if statement in open loop pitch (#100) * Break up if statement in open loop pitch * Make torque and yaw consistent with pitch: can start after some time * add bld edgewise freq to robust dict_inputs * Fix ccrotor inputs (#104) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Allow default inputs (#110) * Allow defaults for AeroDyn inputs * Allow AeroDyn inputs to be floats, too * ipc (#105) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Add proportional control and cleanup IPC * Add IPC and filtered RootMyc to registry * Better logic for filtering RootMOOP and fix notch filter slopes bug * Add cyclic flap conrol * Fix comments in ColemanTransformInverse * Addd IPC_KP to DISCON.IN * Error checking foro flp or ipc * add mutichannel plotting with tuples * add CMakeFiles to gitignore * Add IPC_KP to api changes * numerical qualifiers for error handling * add IPC gains to schema for pass-through ability * fix variable names * fix ipc gain printing bug * make sure IPC_KP is positive * Update Polars to point to coord files * ignore dbg2 files * Add IEA15MW_OL.yaml * update coord reader/writer * expand pitch_initial to 30 degrees * Add example 13 for IPC * Update cp surfaces and DISCONS * add examples to readme * cleanup and streaamline run_examples * Add IPC tuning vars * Allow IPC to command pitch value below peak shaving saturation limit * shorten simulation time * Fix Material parameter path * Update DISCONs again * Fix OL_Input reading * Set wind speed, rotor speed IC in example 14 * Debug OL reading * Add more debugging lines * Add more debugging lines 2 * Clean up, hone in on debug call * Disable logging level * Update discons - resolve conflict * Print when finished with ROSCO * add control packageg * Use PriPath and RootName to name dbg files * Print AvrSWAP * Revert "Use PriPath and RootName to name dbg files" This reverts commit 062fcaa55b3bf42d44f8a3f163aa883ab9552412. * Disable other examples * Print OL inputs * Allow logging level 3 * Print OL inputs * Make example shorter * Print more stuf * Print shape * Revert "Print OL inputs" This reverts commit 8e2a642bb35e46850f579ca4e69ca6d6fc93f4cc. * Update ROSCO Simulink model with IPC example * refactor flap tuning for normalization methods * improved flap controller filtering * delete extra F_FlpCornerFrerq * Update inputs, reader, and writer for OF 3.1.0 * Make sigma default interp type for multi_sigma * Use openfast 3.1.0 in CI * Fix leak...maybe * Use OF 3.1.0 in testing * Use gfotran for compile * Clean up print statements * Re-enable all examples * Update NREL-5MW AD file * Only check airfoil controls if more than one table * Update BAR models * Revert "Use gfotran for compile" This reverts commit 5a6e2b7e30287a09a09781e2ba12f9b578132e1f. * Install pyFAST for CI * Fix some paths in ex12 * Disable example 12 * Fix example 12 linear path, re-enable * Skip compilers install for mac * Try gfortran-9, no compilers * Try gfortran-10, no compilers * Skip windows compile in pytools * Re-enable windows, use gfortran as FC * Unset FC in windows * Set environment for windows when dependencies installed * Try setting environment in installation * Put conditional env setting in correct place * Try in setup again * Break up tasks: Windows vs. not * Update DISCONs * Update docs with new variables * Add example documentation * Update ROSCO Simulink model for 3.1.0 * Make IEA model float again * Reduce IEA timestep * Match DT to checkpoint time Co-authored-by: dzalkind * Increment version number * Bladed docs (#116) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update version in API change docs * Bladed readthedocs (#117) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 * Tinker with characters in bladed instructions * Add bladed instructions to index * Change bladed toctree label * Do underline stuff * Make toctree label same as file * Remove colons from headers Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update docs to reflect CI process * sigma + ipc (#125) * cleanup api change table * Update inverted notch to move frequency properly * Saturate inv notch corner frequency at 0 * add sigma function * Use IPC_Vramp for IPC cut-in * Add IPC_Vramp DISCON inputs * update registry * Update DISCONs * Update docs for API change * Fix IPC_Vramp data type * update comments Co-authored-by: dzalkind * Flip Ct and Cq table allocation (#130) * Pitch Actuator and IPC updates (#123) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars * Turn runFAST into a class * Refactor/simplify CaseLibrary * Implement initial pitch actuator * Set up steps case * Add actuator variable * Print first time step in debug outs * Fix FOCAL yaml * Set actuator to 0.25 Hz bandwidth * ROSCO 2.5.0 (#115) * FOCAL Updates (#64) * Update headers * fix bullets * make uppercase * Update turbine.py (#56) * Update turbine.py This add several lines for fixing the problem of repeated maximum values in the performance tables. This will cause the error (' the length of x and y is different.') of 'interpolate.interp1d.' * Add comments and catch when there are multiple optimal pitch angles Co-authored-by: dzalkind * increment version * Update for OpenFAST v3.0.0 * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Update Testing (#58) * Update scripts to run on eagle * Update IEA-15MW semi example: use peak shaving w/ ps_percen=0.8 * Add comparison plots to testing scripts * Update submit script for testing * Update for latest eagle runs * Add future to install dependencies * add TMax to self, define tmin in print_results * run tests in CI * generic ROSCO path * default to overwrite * fix path * import platform * separate run_testing * cleanup, specify lite test * don't run testing in after examples, oops. Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Increment Version, OF3.0 (#57) * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * add ROSCO without submodule * move ROSCO source to ROSCO folder * Move cmake-related files to ROSCO * Add back pesky ErrVar * Remove parameters_files * Merge ROSCO and _toolbox gitignore * Fix .gitignore * Remove Examples/DISCON.IN from git * Fix and point example_01 to Tune_Cases/ * Update verbiage around using ofTools vs. weis * Fix and point example_04 to Tune_Cases/ * Clean up example_06 * Clean up example_07 * Only check FlpCornerFreq if using Flp control, fixes example 05 * Make example_04 consistent with others * Let example_05 run independently from 04 * Clean up example_05, wind files * Add schema and update empty tuning yaml inputs, not connected yet * Integrate schema into turbine, controller, and examples * Only check Fl filter parameters if Fl_Mode > 0, fix example_05 * bump version to 2.3 * Compile ROSCO from ROSCO dir * Rename to CI_rosco * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * Make _Toolbox vs_minspeed in rotor frame to match ROSCO * Revert ServoDyn change * change rotor speed constraint to be epsilon * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * Deallocate arrays in ROSCO, check in example_05 * Clean up comments * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * more detailed sp.optimize settings * run MBC3 in parallel * restructure driver, run initialization doe for tuning * Try new dlclose function * Update example_05 to run simple simulation twice and check result * Revert deallocation stuff * Close discon library after every sim run * Test examples on macOS and windows * Run examples instead of testing on other platforms * Skip examples in windows for now * update paths and yaml load funciton * Skip mac testing of examples * provide default U_pc for single omega/zeta case * allow for float or list-like pc tuning inputs * Change name in setup.py * WE_Vw unit fix * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * cleeanup for improved stability * check for doe_logs as string in load_DOE * major restructure for rsched_driver class * cleanup verbosity * run serial by default * load_parallel as linturb_option * specific IEA15MW yaml for multi omega * remove unused module imports * fix error message types * lin_file as input * add comments on inputs to LinearTurbineModel init method * remove relative file paths * provide OpenFAST linearizations for IEA15MW UMaineSemi * put plotting in specific function * fix WE_lambda units * add self in on a few necessary variables * creaete example 12 for robust scheduling * try a few mbc3 locations for import * allow list-like or numpy arrays for omega_pc and zeta_pc schedules * create and use recorder setup function * Pass Through Kp_float (#57) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * fix setup_recorder to work for optimization driver too * Allow pass through of Kp_float = 0 * cleanup om problems, update om0 calc * doe levels as input * negative k_float to account for OF conventions * cleanup print statements * variable name cleanup, use calculated k_float * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * use standard tuning k_float as IC * formatting update * update problem setup methods * cleanup add_dv, enable adding design variables after problem is setup * change optimization step size * more setup restructure * update verbosity * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Mostly a docs update (#61) * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Pass through Kp_float = 0 (#59) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * Allow pass through of Kp_float = 0 * Add flp parameters to schema * Change Fl_Mode default to 0 * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * Allow single pitch tuning values in code, default U_pc to 0 * use nac acceleration for floating feedback * Fix TSR saturation for region 2.5operation * Modify system for constant power operation * Only modify pole for constant power above-rated * Remove GenEff from K calc * Update tuning, use constant power * use load_rosco_yaml * constant power * Fix broken tests * Include Fl_Mode=2 for nacelle pitching feedback * Add FOCAL inputs - hpf on floating feedback - lpf on wind speed estimator - associated schema updates * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Skip filter step if there's an error * Update IEA-15MW test case DISCON with focal inputs * Allow Fl_Mode = 2 in ROSCO * Pass through lpf frequency * Add FOCAL tuning yaml * Set Cp contour number of levels * Add FOCAL params to various writers * Update/tune focal yaml * Add scripts for running FAST, tuning various parameters and cases * Add notebook for FAST plotting * Set up step case for testing * Change doubles to C_doubles * Define real and integer kinds, assign to all of ROSCO * Add ADJUSTL to DISCON error message * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Call yaw, flap, and debug only if enabled in DISOCN.IN * Make avrSWAP a ReKi and set constant kinds * Add DISCONs for testing - revert this later * Fix DISCON comparison, before DISCON's were overwritten by model * Rename DEBUG2.dbg to RootName.dbg2 * Update TestCase DISCONs to new input file * Add API change page in docs * Add link to API change on main page * Fix table headers * Fix title underlines * Fix tables again * Fix tables again * Fix version numbering in docs * Simplify FAST_directory in run_FAST * Versioning (#65) * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * remove git versioning from cmake * use hard coded rosco_version * update intro write method * set nowrap for intel compilers * Add transfer of error message and clear message after each call * update install instructions * Catch nans in ROSCO at end of WSE * fix conda install typo * cleanup docs * Rename DEBUG2.dbg to RootName.dbg2 * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Allow Fl_Mode = 2 in ROSCO * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Update FOCAL tuning yaml * Update TSR * Clean up and doc fix * Remove publish to pypi * Define all constant inputs to functions with kind typing * Generate Test_Case/ inputs automatically * Fix IEA15 DISCON path * Fix example 11 paths * Auto-generate tuning input yaml using schema * Add toolbox_input to doc index * Add toctree * Re-name title of toolbox_input Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> * update listcheck method for numpy arrays * Open Loop Control (#98) * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * Saturate inputs to WSE. Needs some TLC, but seems to work * Reduce saturation limits on speed, torque * Re-organize, set saturation limits. Working at 3 m/s * Initial add of OL control to ROSCO: builds * Update DISCONs with open loop inputs * Fix file reading for OL_Filename * Add open loop control generation and file writing * Use DISCON_dict for more manageable DISCON file writing * Add open loop example, fix constant timeseries * Make open loop example generate power * Handle relative paths and calling from outside the run directory: - Some helper functions borrowed from OpenFAST, f/ext_control - Updated file writing * Clean up: versions, print statements * Fix SysFiles paths in CMakeLists * Tidy up Ext_DLL names * More Ext_DLL name tidying * Test write_registry.py * Update for OL Control * Move preprocessor lines * add zenodo DOI * Regenerated Types * fix shape * revert filepath change * give all types a size, ProcAddr size = 3 * update types * test registry in compile step * specify default shell * update write_registry path * remove default shell * Document API changes, provide OL input example * Fix example 14 (yaw input) * Add error catching to yaw control * Tidy up OL_Input Reading: error catching, generalize * More yaw control fixes, to model * Checkout develop CMakeLists for ROSCO * Update DISCON.INs for TestCases/ * Revert "Checkout develop CMakeLists for ROSCO" This reverts commit 87a491359d73806e3aaa59b4cfdc00f2838875df. * Revert windows cmake stuff to develop * Fix CMake again * Revert "Revert windows cmake stuff to develop" This reverts commit 39df122449b0e2055f2e12e3ce38a0c010c7bd91. * Make last cmake fix - hopefully Co-authored-by: Nikhar Abbas * Restart & registry (#99) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars Co-authored-by: dzalkind * Break up if statement in open loop pitch (#100) * Break up if statement in open loop pitch * Make torque and yaw consistent with pitch: can start after some time * add bld edgewise freq to robust dict_inputs * Fix ccrotor inputs (#104) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Allow default inputs (#110) * Allow defaults for AeroDyn inputs * Allow AeroDyn inputs to be floats, too * ipc (#105) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Add proportional control and cleanup IPC * Add IPC and filtered RootMyc to registry * Better logic for filtering RootMOOP and fix notch filter slopes bug * Add cyclic flap conrol * Fix comments in ColemanTransformInverse * Addd IPC_KP to DISCON.IN * Error checking foro flp or ipc * add mutichannel plotting with tuples * add CMakeFiles to gitignore * Add IPC_KP to api changes * numerical qualifiers for error handling * add IPC gains to schema for pass-through ability * fix variable names * fix ipc gain printing bug * make sure IPC_KP is positive * Update Polars to point to coord files * ignore dbg2 files * Add IEA15MW_OL.yaml * update coord reader/writer * expand pitch_initial to 30 degrees * Add example 13 for IPC * Update cp surfaces and DISCONS * add examples to readme * cleanup and streaamline run_examples * Add IPC tuning vars * Allow IPC to command pitch value below peak shaving saturation limit * shorten simulation time * Fix Material parameter path * Update DISCONs again * Fix OL_Input reading * Set wind speed, rotor speed IC in example 14 * Debug OL reading * Add more debugging lines * Add more debugging lines 2 * Clean up, hone in on debug call * Disable logging level * Update discons - resolve conflict * Print when finished with ROSCO * add control packageg * Use PriPath and RootName to name dbg files * Print AvrSWAP * Revert "Use PriPath and RootName to name dbg files" This reverts commit 062fcaa55b3bf42d44f8a3f163aa883ab9552412. * Disable other examples * Print OL inputs * Allow logging level 3 * Print OL inputs * Make example shorter * Print more stuf * Print shape * Revert "Print OL inputs" This reverts commit 8e2a642bb35e46850f579ca4e69ca6d6fc93f4cc. * Update ROSCO Simulink model with IPC example * refactor flap tuning for normalization methods * improved flap controller filtering * delete extra F_FlpCornerFrerq * Update inputs, reader, and writer for OF 3.1.0 * Make sigma default interp type for multi_sigma * Use openfast 3.1.0 in CI * Fix leak...maybe * Use OF 3.1.0 in testing * Use gfotran for compile * Clean up print statements * Re-enable all examples * Update NREL-5MW AD file * Only check airfoil controls if more than one table * Update BAR models * Revert "Use gfotran for compile" This reverts commit 5a6e2b7e30287a09a09781e2ba12f9b578132e1f. * Install pyFAST for CI * Fix some paths in ex12 * Disable example 12 * Fix example 12 linear path, re-enable * Skip compilers install for mac * Try gfortran-9, no compilers * Try gfortran-10, no compilers * Skip windows compile in pytools * Re-enable windows, use gfortran as FC * Unset FC in windows * Set environment for windows when dependencies installed * Try setting environment in installation * Put conditional env setting in correct place * Try in setup again * Break up tasks: Windows vs. not * Update DISCONs * Update docs with new variables * Add example documentation * Update ROSCO Simulink model for 3.1.0 * Make IEA model float again * Reduce IEA timestep * Match DT to checkpoint time Co-authored-by: dzalkind * Increment version number * Bladed docs (#116) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update version in API change docs * Bladed readthedocs (#117) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 * Tinker with characters in bladed instructions * Add bladed instructions to index * Change bladed toctree label * Do underline stuff * Make toctree label same as file * Remove colons from headers Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update docs to reflect CI process Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Fixed wrong formatting of list items (#122) extra newline required between list elements * Regenerate types, IO with registry * Update registry so first timestep is printed * Update inverted notch to move frequency properly * Saturate inv notch corner frequency at 0 * Add tower damper mode flag * Flip Ct and Cq table allocation * Regen types * Remove print statements used for debugging * Update input files: IEA model has pitch actuator * Add back flap control (no idea when it was deleted) * Update discons, docs with API change Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> Co-authored-by: Gustavo Hylander <74593034+ghylander@users.noreply.github.com> * Add OpenFAST channels that Simulink reads (#135) * RAAW Updates (#133) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars * Turn runFAST into a class * Refactor/simplify CaseLibrary * Implement initial pitch actuator * Set up steps case * Add actuator variable * Print first time step in debug outs * Fix FOCAL yaml * Set actuator to 0.25 Hz bandwidth * ROSCO 2.5.0 (#115) * FOCAL Updates (#64) * Update headers * fix bullets * make uppercase * Update turbine.py (#56) * Update turbine.py This add several lines for fixing the problem of repeated maximum values in the performance tables. This will cause the error (' the length of x and y is different.') of 'interpolate.interp1d.' * Add comments and catch when there are multiple optimal pitch angles Co-authored-by: dzalkind * increment version * Update for OpenFAST v3.0.0 * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Update Testing (#58) * Update scripts to run on eagle * Update IEA-15MW semi example: use peak shaving w/ ps_percen=0.8 * Add comparison plots to testing scripts * Update submit script for testing * Update for latest eagle runs * Add future to install dependencies * add TMax to self, define tmin in print_results * run tests in CI * generic ROSCO path * default to overwrite * fix path * import platform * separate run_testing * cleanup, specify lite test * don't run testing in after examples, oops. Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Increment Version, OF3.0 (#57) * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * add ROSCO without submodule * move ROSCO source to ROSCO folder * Move cmake-related files to ROSCO * Add back pesky ErrVar * Remove parameters_files * Merge ROSCO and _toolbox gitignore * Fix .gitignore * Remove Examples/DISCON.IN from git * Fix and point example_01 to Tune_Cases/ * Update verbiage around using ofTools vs. weis * Fix and point example_04 to Tune_Cases/ * Clean up example_06 * Clean up example_07 * Only check FlpCornerFreq if using Flp control, fixes example 05 * Make example_04 consistent with others * Let example_05 run independently from 04 * Clean up example_05, wind files * Add schema and update empty tuning yaml inputs, not connected yet * Integrate schema into turbine, controller, and examples * Only check Fl filter parameters if Fl_Mode > 0, fix example_05 * bump version to 2.3 * Compile ROSCO from ROSCO dir * Rename to CI_rosco * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * Make _Toolbox vs_minspeed in rotor frame to match ROSCO * Revert ServoDyn change * change rotor speed constraint to be epsilon * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * Deallocate arrays in ROSCO, check in example_05 * Clean up comments * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * more detailed sp.optimize settings * run MBC3 in parallel * restructure driver, run initialization doe for tuning * Try new dlclose function * Update example_05 to run simple simulation twice and check result * Revert deallocation stuff * Close discon library after every sim run * Test examples on macOS and windows * Run examples instead of testing on other platforms * Skip examples in windows for now * update paths and yaml load funciton * Skip mac testing of examples * provide default U_pc for single omega/zeta case * allow for float or list-like pc tuning inputs * Change name in setup.py * WE_Vw unit fix * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * cleeanup for improved stability * check for doe_logs as string in load_DOE * major restructure for rsched_driver class * cleanup verbosity * run serial by default * load_parallel as linturb_option * specific IEA15MW yaml for multi omega * remove unused module imports * fix error message types * lin_file as input * add comments on inputs to LinearTurbineModel init method * remove relative file paths * provide OpenFAST linearizations for IEA15MW UMaineSemi * put plotting in specific function * fix WE_lambda units * add self in on a few necessary variables * creaete example 12 for robust scheduling * try a few mbc3 locations for import * allow list-like or numpy arrays for omega_pc and zeta_pc schedules * create and use recorder setup function * Pass Through Kp_float (#57) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * fix setup_recorder to work for optimization driver too * Allow pass through of Kp_float = 0 * cleanup om problems, update om0 calc * doe levels as input * negative k_float to account for OF conventions * cleanup print statements * variable name cleanup, use calculated k_float * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * use standard tuning k_float as IC * formatting update * update problem setup methods * cleanup add_dv, enable adding design variables after problem is setup * change optimization step size * more setup restructure * update verbosity * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Mostly a docs update (#61) * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Pass through Kp_float = 0 (#59) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * Allow pass through of Kp_float = 0 * Add flp parameters to schema * Change Fl_Mode default to 0 * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * Allow single pitch tuning values in code, default U_pc to 0 * use nac acceleration for floating feedback * Fix TSR saturation for region 2.5operation * Modify system for constant power operation * Only modify pole for constant power above-rated * Remove GenEff from K calc * Update tuning, use constant power * use load_rosco_yaml * constant power * Fix broken tests * Include Fl_Mode=2 for nacelle pitching feedback * Add FOCAL inputs - hpf on floating feedback - lpf on wind speed estimator - associated schema updates * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Skip filter step if there's an error * Update IEA-15MW test case DISCON with focal inputs * Allow Fl_Mode = 2 in ROSCO * Pass through lpf frequency * Add FOCAL tuning yaml * Set Cp contour number of levels * Add FOCAL params to various writers * Update/tune focal yaml * Add scripts for running FAST, tuning various parameters and cases * Add notebook for FAST plotting * Set up step case for testing * Change doubles to C_doubles * Define real and integer kinds, assign to all of ROSCO * Add ADJUSTL to DISCON error message * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Call yaw, flap, and debug only if enabled in DISOCN.IN * Make avrSWAP a ReKi and set constant kinds * Add DISCONs for testing - revert this later * Fix DISCON comparison, before DISCON's were overwritten by model * Rename DEBUG2.dbg to RootName.dbg2 * Update TestCase DISCONs to new input file * Add API change page in docs * Add link to API change on main page * Fix table headers * Fix title underlines * Fix tables again * Fix tables again * Fix version numbering in docs * Simplify FAST_directory in run_FAST * Versioning (#65) * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * remove git versioning from cmake * use hard coded rosco_version * update intro write method * set nowrap for intel compilers * Add transfer of error message and clear message after each call * update install instructions * Catch nans in ROSCO at end of WSE * fix conda install typo * cleanup docs * Rename DEBUG2.dbg to RootName.dbg2 * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Allow Fl_Mode = 2 in ROSCO * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Update FOCAL tuning yaml * Update TSR * Clean up and doc fix * Remove publish to pypi * Define all constant inputs to functions with kind typing * Generate Test_Case/ inputs automatically * Fix IEA15 DISCON path * Fix example 11 paths * Auto-generate tuning input yaml using schema * Add toolbox_input to doc index * Add toctree * Re-name title of toolbox_input Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> * update listcheck method for numpy arrays * Open Loop Control (#98) * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * Saturate inputs to WSE. Needs some TLC, but seems to work * Reduce saturation limits on speed, torque * Re-organize, set saturation limits. Working at 3 m/s * Initial add of OL control to ROSCO: builds * Update DISCONs with open loop inputs * Fix file reading for OL_Filename * Add open loop control generation and file writing * Use DISCON_dict for more manageable DISCON file writing * Add open loop example, fix constant timeseries * Make open loop example generate power * Handle relative paths and calling from outside the run directory: - Some helper functions borrowed from OpenFAST, f/ext_control - Updated file writing * Clean up: versions, print statements * Fix SysFiles paths in CMakeLists * Tidy up Ext_DLL names * More Ext_DLL name tidying * Test write_registry.py * Update for OL Control * Move preprocessor lines * add zenodo DOI * Regenerated Types * fix shape * revert filepath change * give all types a size, ProcAddr size = 3 * update types * test registry in compile step * specify default shell * update write_registry path * remove default shell * Document API changes, provide OL input example * Fix example 14 (yaw input) * Add error catching to yaw control * Tidy up OL_Input Reading: error catching, generalize * More yaw control fixes, to model * Checkout develop CMakeLists for ROSCO * Update DISCON.INs for TestCases/ * Revert "Checkout develop CMakeLists for ROSCO" This reverts commit 87a491359d73806e3aaa59b4cfdc00f2838875df. * Revert windows cmake stuff to develop * Fix CMake again * Revert "Revert windows cmake stuff to develop" This reverts commit 39df122449b0e2055f2e12e3ce38a0c010c7bd91. * Make last cmake fix - hopefully Co-authored-by: Nikhar Abbas * Restart & registry (#99) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars Co-authored-by: dzalkind * Break up if statement in open loop pitch (#100) * Break up if statement in open loop pitch * Make torque and yaw consistent with pitch: can start after some time * add bld edgewise freq to robust dict_inputs * Fix ccrotor inputs (#104) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Allow default inputs (#110) * Allow defaults for AeroDyn inputs * Allow AeroDyn inputs to be floats, too * ipc (#105) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Add proportional control and cleanup IPC * Add IPC and filtered RootMyc to registry * Better logic for filtering RootMOOP and fix notch filter slopes bug * Add cyclic flap conrol * Fix comments in ColemanTransformInverse * Addd IPC_KP to DISCON.IN * Error checking foro flp or ipc * add mutichannel plotting with tuples * add CMakeFiles to gitignore * Add IPC_KP to api changes * numerical qualifiers for error handling * add IPC gains to schema for pass-through ability * fix variable names * fix ipc gain printing bug * make sure IPC_KP is positive * Update Polars to point to coord files * ignore dbg2 files * Add IEA15MW_OL.yaml * update coord reader/writer * expand pitch_initial to 30 degrees * Add example 13 for IPC * Update cp surfaces and DISCONS * add examples to readme * cleanup and streaamline run_examples * Add IPC tuning vars * Allow IPC to command pitch value below peak shaving saturation limit * shorten simulation time * Fix Material parameter path * Update DISCONs again * Fix OL_Input reading * Set wind speed, rotor speed IC in example 14 * Debug OL reading * Add more debugging lines * Add more debugging lines 2 * Clean up, hone in on debug call * Disable logging level * Update discons - resolve conflict * Print when finished with ROSCO * add control packageg * Use PriPath and RootName to name dbg files * Print AvrSWAP * Revert "Use PriPath and RootName to name dbg files" This reverts commit 062fcaa55b3bf42d44f8a3f163aa883ab9552412. * Disable other examples * Print OL inputs * Allow logging level 3 * Print OL inputs * Make example shorter * Print more stuf * Print shape * Revert "Print OL inputs" This reverts commit 8e2a642bb35e46850f579ca4e69ca6d6fc93f4cc. * Update ROSCO Simulink model with IPC example * refactor flap tuning for normalization methods * improved flap controller filtering * delete extra F_FlpCornerFrerq * Update inputs, reader, and writer for OF 3.1.0 * Make sigma default interp type for multi_sigma * Use openfast 3.1.0 in CI * Fix leak...maybe * Use OF 3.1.0 in testing * Use gfotran for compile * Clean up print statements * Re-enable all examples * Update NREL-5MW AD file * Only check airfoil controls if more than one table * Update BAR models * Revert "Use gfotran for compile" This reverts commit 5a6e2b7e30287a09a09781e2ba12f9b578132e1f. * Install pyFAST for CI * Fix some paths in ex12 * Disable example 12 * Fix example 12 linear path, re-enable * Skip compilers install for mac * Try gfortran-9, no compilers * Try gfortran-10, no compilers * Skip windows compile in pytools * Re-enable windows, use gfortran as FC * Unset FC in windows * Set environment for windows when dependencies installed * Try setting environment in installation * Put conditional env setting in correct place * Try in setup again * Break up tasks: Windows vs. not * Update DISCONs * Update docs with new variables * Add example documentation * Update ROSCO Simulink model for 3.1.0 * Make IEA model float again * Reduce IEA timestep * Match DT to checkpoint time Co-authored-by: dzalkind * Increment version number * Bladed docs (#116) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update version in API change docs * Bladed readthedocs (#117) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 * Tinker with characters in bladed instructions * Add bladed instructions to index * Change bladed toctree label * Do underline stuff * Make toctree label same as file * Remove colons from headers Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update docs to reflect CI process Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Fixed wrong formatting of list items (#122) extra newline required between list elements * Regenerate types, IO with registry * Update registry so first timestep is printed * Update inverted notch to move frequency properly * Saturate inv notch corner frequency at 0 * Add tower damper mode flag * Add Azimuth tracking controller in Simulink * Always enable GenDOF, add options for simp_step * Add sweep for IPC gains and FA damper * Fix NumCoords in FAST_writer * Add turbulent case to runFAST/CaseLibrary * Add peak shaving sweep function * Increase default IPC_IntSat, make input parameter in future * Flip Ct and Cq table allocation (#129) * Flip Ct and Cq table allocation * Regen types * Remove print statements used for debugging * Update input files: IEA model has pitch actuator * Add back flap control (no idea when it was deleted) * Update discons, docs with API change * Add user-defined hh case * Fix AddF0 and RayleighDamp in FAST_reader * Add max_torque_factor for constant power control, flexible upper limit * Make update discons relative to tuning yaml * Update AddF0 and NumCoords in FAST_reader/writer * Remove matlab/rotor position control stuff Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> Co-authored-by: Gustavo Hylander <74593034+ghylander@users.noreply.github.com> * Pass through (#136) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars * Turn runFAST into a class * Refactor/simplify CaseLibrary * Implement initial pitch actuator * Set up steps case * Add actuator variable * Print first time step in debug outs * Fix FOCAL yaml * Set actuator to 0.25 Hz bandwidth * ROSCO 2.5.0 (#115) * FOCAL Updates (#64) * Update headers * fix bullets * make uppercase * Update turbine.py (#56) * Update turbine.py This add several lines for fixing the problem of repeated maximum values in the performance tables. This will cause the error (' the length of x and y is different.') of 'interpolate.interp1d.' * Add comments and catch when there are multiple optimal pitch angles Co-authored-by: dzalkind * increment version * Update for OpenFAST v3.0.0 * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Update Testing (#58) * Update scripts to run on eagle * Update IEA-15MW semi example: use peak shaving w/ ps_percen=0.8 * Add comparison plots to testing scripts * Update submit script for testing * Update for latest eagle runs * Add future to install dependencies * add TMax to self, define tmin in print_results * run tests in CI * generic ROSCO path * default to overwrite * fix path * import platform * separate run_testing * cleanup, specify lite test * don't run testing in after examples, oops. Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Increment Version, OF3.0 (#57) * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * add ROSCO without submodule * move ROSCO source to ROSCO folder * Move cmake-related files to ROSCO * Add back pesky ErrVar * Remove parameters_files * Merge ROSCO and _toolbox gitignore * Fix .gitignore * Remove Examples/DISCON.IN from git * Fix and point example_01 to Tune_Cases/ * Update verbiage around using ofTools vs. weis * Fix and point example_04 to Tune_Cases/ * Clean up example_06 * Clean up example_07 * Only check FlpCornerFreq if using Flp control, fixes example 05 * Make example_04 consistent with others * Let example_05 run independently from 04 * Clean up example_05, wind files * Add schema and update empty tuning yaml inputs, not connected yet * Integrate schema into turbine, controller, and examples * Only check Fl filter parameters if Fl_Mode > 0, fix example_05 * bump version to 2.3 * Compile ROSCO from ROSCO dir * Rename to CI_rosco * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * Make _Toolbox vs_minspeed in rotor frame to match ROSCO * Revert ServoDyn change * change rotor speed constraint to be epsilon * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * Deallocate arrays in ROSCO, check in example_05 * Clean up comments * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * more detailed sp.optimize settings * run MBC3 in parallel * restructure driver, run initialization doe for tuning * Try new dlclose function * Update example_05 to run simple simulation twice and check result * Revert deallocation stuff * Close discon library after every sim run * Test examples on macOS and windows * Run examples instead of testing on other platforms * Skip examples in windows for now * update paths and yaml load funciton * Skip mac testing of examples * provide default U_pc for single omega/zeta case * allow for float or list-like pc tuning inputs * Change name in setup.py * WE_Vw unit fix * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * cleeanup for improved stability * check for doe_logs as string in load_DOE * major restructure for rsched_driver class * cleanup verbosity * run serial by default * load_parallel as linturb_option * specific IEA15MW yaml for multi omega * remove unused module imports * fix error message types * lin_file as input * add comments on inputs to LinearTurbineModel init method * remove relative file paths * provide OpenFAST linearizations for IEA15MW UMaineSemi * put plotting in specific function * fix WE_lambda units * add self in on a few necessary variables * creaete example 12 for robust scheduling * try a few mbc3 locations for import * allow list-like or numpy arrays for omega_pc and zeta_pc schedules * create and use recorder setup function * Pass Through Kp_float (#57) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * fix setup_recorder to work for optimization driver too * Allow pass through of Kp_float = 0 * cleanup om problems, update om0 calc * doe levels as input * negative k_float to account for OF conventions * cleanup print statements * variable name cleanup, use calculated k_float * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * use standard tuning k_float as IC * formatting update * update problem setup methods * cleanup add_dv, enable adding design variables after problem is setup * change optimization step size * more setup restructure * update verbosity * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Mostly a docs update (#61) * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Pass through Kp_float = 0 (#59) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * Allow pass through of Kp_float = 0 * Add flp parameters to schema * Change Fl_Mode default to 0 * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * Allow single pitch tuning values in code, default U_pc to 0 * use nac acceleration for floating feedback * Fix TSR saturation for region 2.5operation * Modify system for constant power operation * Only modify pole for constant power above-rated * Remove GenEff from K calc * Update tuning, use constant power * use load_rosco_yaml * constant power * Fix broken tests * Include Fl_Mode=2 for nacelle pitching feedback * Add FOCAL inputs - hpf on floating feedback - lpf on wind speed estimator - associated schema updates * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Skip filter step if there's an error * Update IEA-15MW test case DISCON with focal inputs * Allow Fl_Mode = 2 in ROSCO * Pass through lpf frequency * Add FOCAL tuning yaml * Set Cp contour number of levels * Add FOCAL params to various writers * Update/tune focal yaml * Add scripts for running FAST, tuning various parameters and cases * Add notebook for FAST plotting * Set up step case for testing * Change doubles to C_doubles * Define real and integer kinds, assign to all of ROSCO * Add ADJUSTL to DISCON error message * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Call yaw, flap, and debug only if enabled in DISOCN.IN * Make avrSWAP a ReKi and set constant kinds * Add DISCONs for testing - revert this later * Fix DISCON comparison, before DISCON's were overwritten by model * Rename DEBUG2.dbg to RootName.dbg2 * Update TestCase DISCONs to new input file * Add API change page in docs * Add link to API change on main page * Fix table headers * Fix title underlines * Fix tables again * Fix tables again * Fix version numbering in docs * Simplify FAST_directory in run_FAST * Versioning (#65) * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * remove git versioning from cmake * use hard coded rosco_version * update intro write method * set nowrap for intel compilers * Add transfer of error message and clear message after each call * update install instructions * Catch nans in ROSCO at end of WSE * fix conda install typo * cleanup docs * Rename DEBUG2.dbg to RootName.dbg2 * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Allow Fl_Mode = 2 in ROSCO * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Update FOCAL tuning yaml * Update TSR * Clean up and doc fix * Remove publish to pypi * Define all constant inputs to functions with kind typing * Generate Test_Case/ inputs automatically * Fix IEA15 DISCON path * Fix example 11 paths * Auto-generate tuning input yaml using schema * Add toolbox_input to doc index * Add toctree * Re-name title of toolbox_input Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> * update listcheck method for numpy arrays * Open Loop Control (#98) * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * Saturate inputs to WSE. Needs some TLC, but seems to work * Reduce saturation limits on speed, torque * Re-organize, set saturation limits. Working at 3 m/s * Initial add of OL control to ROSCO: builds * Update DISCONs with open loop inputs * Fix file reading for OL_Filename * Add open loop control generation and file writing * Use DISCON_dict for more manageable DISCON file writing * Add open loop example, fix constant timeseries * Make open loop example generate power * Handle relative paths and calling from outside the run directory: - Some helper functions borrowed from OpenFAST, f/ext_control - Updated file writing * Clean up: versions, print statements * Fix SysFiles paths in CMakeLists * Tidy up Ext_DLL names * More Ext_DLL name tidying * Test write_registry.py * Update for OL Control * Move preprocessor lines * add zenodo DOI * Regenerated Types * fix shape * revert filepath change * give all types a size, ProcAddr size = 3 * update types * test registry in compile step * specify default shell * update write_registry path * remove default shell * Document API changes, provide OL input example * Fix example 14 (yaw input) * Add error catching to yaw control * Tidy up OL_Input Reading: error catching, generalize * More yaw control fixes, to model * Checkout develop CMakeLists for ROSCO * Update DISCON.INs for TestCases/ * Revert "Checkout develop CMakeLists for ROSCO" This reverts commit 87a491359d73806e3aaa59b4cfdc00f2838875df. * Revert windows cmake stuff to develop * Fix CMake again * Revert "Revert windows cmake stuff to develop" This reverts commit 39df122449b0e2055f2e12e3ce38a0c010c7bd91. * Make last cmake fix - hopefully Co-authored-by: Nikhar Abbas * Restart & registry (#99) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars Co-authored-by: dzalkind * Break up if statement in open loop pitch (#100) * Break up if statement in open loop pitch * Make torque and yaw consistent with pitch: can start after some time * add bld edgewise freq to robust dict_inputs * Fix ccrotor inputs (#104) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Allow default inputs (#110) * Allow defaults for AeroDyn inputs * Allow AeroDyn inputs to be floats, too * ipc (#105) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Add proportional control and cleanup IPC * Add IPC and filtered RootMyc to registry * Better logic for filtering RootMOOP and fix notch filter slopes bug * Add cyclic flap conrol * Fix comments in ColemanTransformInverse * Addd IPC_KP to DISCON.IN * Error checking foro flp or ipc * add mutichannel plotting with tuples * add CMakeFiles to gitignore * Add IPC_KP to api changes * numerical qualifiers for error handling * add IPC gains to schema for pass-through ability * fix variable names * fix ipc gain printing bug * make sure IPC_KP is positive * Update Polars to point to coord files * ignore dbg2 files * Add IEA15MW_OL.yaml * update coord reader/writer * expand pitch_initial to 30 degrees * Add example 13 for IPC * Update cp surfaces and DISCONS * add examples to readme * cleanup and streaamline run_examples * Add IPC tuning vars * Allow IPC to command pitch value below peak shaving saturation limit * shorten simulation time * Fix Material parameter path * Update DISCONs again * Fix OL_Input reading * Set wind speed, rotor speed IC in example 14 * Debug OL reading * Add more debugging lines * Add more debugging lines 2 * Clean up, hone in on debug call * Disable logging level * Update discons - resolve conflict * Print when finished with ROSCO * add control packageg * Use PriPath and RootName to name dbg files * Print AvrSWAP * Revert "Use PriPath and RootName to name dbg files" This reverts commit 062fcaa55b3bf42d44f8a3f163aa883ab9552412. * Disable other examples * Print OL inputs * Allow logging level 3 * Print OL inputs * Make example shorter * Print more stuf * Print shape * Revert "Print OL inputs" This reverts commit 8e2a642bb35e46850f579ca4e69ca6d6fc93f4cc. * Update ROSCO Simulink model with IPC example * refactor flap tuning for normalization methods * improved flap controller filtering * delete extra F_FlpCornerFrerq * Update inputs, reader, and writer for OF 3.1.0 * Make sigma default interp type for multi_sigma * Use openfast 3.1.0 in CI * Fix leak...maybe * Use OF 3.1.0 in testing * Use gfotran for compile * Clean up print statements * Re-enable all examples * Update NREL-5MW AD file * Only check airfoil controls if more than one table * Update BAR models * Revert "Use gfotran for compile" This reverts commit 5a6e2b7e30287a09a09781e2ba12f9b578132e1f. * Install pyFAST for CI * Fix some paths in ex12 * Disable example 12 * Fix example 12 linear path, re-enable * Skip compilers install for mac * Try gfortran-9, no compilers * Try gfortran-10, no compilers * Skip windows compile in pytools * Re-enable windows, use gfortran as FC * Unset FC in windows * Set environment for windows when dependencies installed * Try setting environment in installation * Put conditional env setting in correct place * Try in setup again * Break up tasks: Windows vs. not * Update DISCONs * Update docs with new variables * Add example documentation * Update ROSCO Simulink model for 3.1.0 * Make IEA model float again * Reduce IEA timestep * Match DT to checkpoint time Co-authored-by: dzalkind * Increment version number * Bladed docs (#116) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update version in API change docs * Bladed readthedocs (#117) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 * Tinker with characters in bladed instructions * Add bladed instructions to index * Change bladed toctree label * Do underline stuff * Make toctree label same as file * Remove colons from headers Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update docs to reflect CI process Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Fixed wrong formatting of list items (#122) extra newline required between list elements * Regenerate types, IO with registry * Update registry so first timestep is printed * Update inverted notch to move frequency properly * Saturate inv notch corner frequency at 0 * Add tower damper mode flag * Add Azimuth tracking controller in Simulink * Always enable GenDOF, add options for simp_step * Add sweep for IPC gains and FA damper * Fix NumCoords in FAST_writer * Add turbulent case to runFAST/CaseLibrary * Add peak shaving sweep function * Increase default IPC_IntSat, make input parameter in future * Flip Ct and Cq table allocation (#129) * Flip Ct and Cq table allocation * Regen types * Remove print statements used for debugging * Update input files: IEA model has pitch actuator * Add back flap control (no idea when it was deleted) * Update discons, docs with API change * Add user-defined hh case * Fix AddF0 and RayleighDamp in FAST_reader * Add max_torque_factor for constant power control, flexible upper limit * Make update discons relative to tuning yaml * Update AddF0 and NumCoords in FAST_reader/writer * Remove matlab/rotor position control stuff * Add initial DISCON pass through schema * Add TODO items for pull requests * Add initial pass through capability, example * Add PassThrough example yaml * Make cp filename relative to FAST_directory throughout * Remove brackets, regen input docs * Add to PassThrough example yaml * Add example 15 to testing, fix yaml * Tidy comments in CaseLibrary Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> Co-authored-by: Gustavo Hylander <74593034+ghylander@users.noreply.github.com> * Robust control updates (#139) * change default interp type * improve nested MPI handling * fix cl bug, sm calc more robust, allow single plant for interp * add load from pickle capabilites * add re-try logic * save v_above_rated to controller object * fix BAR cp_ct_cq surface path * update discons * remove rogue DISCON * External Control Interface (#141) * Add ExtControl manually from f/ext_control, compiling * Fix Cp plots * Update multiple discons * Make FAST_Directory relative to yaml * Set control to ROSCO, sweep max torque * Fix c_float type in registry, building * Update DISCON writer, fix reader * Add example for running Ext control, running * Max example actually call extdll, update inputs, running * Add ExtControl module! * Add ROSCO_Helpers, GetNewUnit, shorten ReadSetParameters, Functions * Update write_registry with GetNewUnit * Tidy up RootName: remove hard-coded index * Close Cp file, fixes checkpoint test * Clean up examples, add 16 to CI testing * Change regtest name in CI * Update DISCONs * Add API change documentation * Add summary of API changes, finish external interface * Tidy code, point to location for using ExtDLL * F/zmq (#145) * cleanup api change table * zeromq client and f90 files - initia lcommit * add zmq install reqs * remove yaw-by-ipc * typo fix * add zeroMQ interface to registry and source - enable zeroMQ for yaw control * cleanup to compile, move ZMQ_Mode to flags, write DISCONS * only set ZMQ_Client if flag is enabled * fix bug in discons * minor updates and add sim with wind direction * change to dict inputs for control interface * add zmq example * Revert "remove yaw-by-ipc" This reverts commit 2ca2859884313aa6d8fdd217a7fede73802a5fcf. * Enable Yaw-by-ipc again * update and execute zeromq example * add zmq to dependencies * sudo for apt-get * libzmq3-dev typo * setup cmake * rename example 16 to 17 * read empty line * update types, regen discons * fix build path * remove non-ubuntu examples, type in cmake command * Set ROSCO=True * typo fix and update DISCONs * document API changes * newlines and in-text code * updated removed inputs, fix intext code syntax * run example 17 * Add description to example 17 * zeromq -> pyzmq, cleanup * better table for new/modified/removed * fix real(8) * cleanup zmqVar conventions and uses. Call zmq using a time period * Update DISCONS * run all examples * Fix Y_uSwitch description * update comm address example * execute with runpy instead of importlib * move zmq interface classes to control_interface * Properly shutdown ZMQ interface * add IEA15MW_ExtInterface.yaml * Incorporate runFAST stuff from WEIS, clean up * Remove specific rosco_dll * Publish artifacts from examples * Clean up examples following runFAST business * move archive artifacts * Fix build dir in example 17 * Switch install/develop in pytools CI * archive even if exampless fail * add zmq build instructions * cleanup and rename sim * Remove BITS_IN_ADDR stuff * Pass case_inputs and rosco_dll to runFAST object * Pass correct DLL_FileName for external control * Tidy example 15 commenting Co-authored-by: dzalkind * Generalize update discons * Update PR template * Update update discons * Nyquist plotting (#157) * change default interp type * improve nested MPI handling * fix cl bug, sm calc more robust, allow single plant for interp * add load from pickle capabilites * add re-try logic * save v_above_rated to controller object * fix BAR cp_ct_cq surface path * update discons * remove rogue DISCON * Add linear model visualization tools * plot nyquist * remove plt.show() * fix indexing, windspeed * Comments (#150) * cleanup api change table * Update comments to describe zmq modules and functions better * comment and cleanup controller-related moduels * cleanup and comments for turbine modules * psuedo functions for weird interpolation steps * minor sim cleanup * More comments for robust tuning procedures * add descriptions and comments for robust scheduling * Add some comments * Add Ct_max comment Co-authored-by: dzalkind * Update ROSCO_walkthrough to use new yaml reader (#159) * Update ROSCO_walkthrough to use new yaml reader * Add CI for notebooks * Make parameter_filename a relative path * Generalize dylib extension in RW * Update to openfast==3.2.0, no API changes! (#160) * Updates in preparation for ROSCO 2.6.0 (#161) * ROSCO 2.5.0 (#115) * FOCAL Updates (#64) * Update headers * fix bullets * make uppercase * Update turbine.py (#56) * Update turbine.py This add several lines for fixing the problem of repeated maximum values in the performance tables. This will cause the error (' the length of x and y is different.') of 'interpolate.interp1d.' * Add comments and catch when there are multiple optimal pitch angles Co-authored-by: dzalkind * increment version * Update for OpenFAST v3.0.0 * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Update Testing (#58) * Update scripts to run on eagle * Update IEA-15MW semi example: use peak shaving w/ ps_percen=0.8 * Add comparison plots to testing scripts * Update submit script for testing * Update for latest eagle runs * Add future to install dependencies * add TMax to self, define tmin in print_results * run tests in CI * generic ROSCO path * default to overwrite * fix path * import platform * separate run_testing * cleanup, specify lite test * don't run testing in after examples, oops. Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * Increment Version, OF3.0 (#57) * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * increment version * Update for OpenFAST v3.0.0 * Install OF3.0.0 for tests * update for OF v3.0.0 * add publish to pypi on release * Update to ROSCO v2.3.0 * add ROSCO without submodule * move ROSCO source to ROSCO folder * Move cmake-related files to ROSCO * Add back pesky ErrVar * Remove parameters_files * Merge ROSCO and _toolbox gitignore * Fix .gitignore * Remove Examples/DISCON.IN from git * Fix and point example_01 to Tune_Cases/ * Update verbiage around using ofTools vs. weis * Fix and point example_04 to Tune_Cases/ * Clean up example_06 * Clean up example_07 * Only check FlpCornerFreq if using Flp control, fixes example 05 * Make example_04 consistent with others * Let example_05 run independently from 04 * Clean up example_05, wind files * Add schema and update empty tuning yaml inputs, not connected yet * Integrate schema into turbine, controller, and examples * Only check Fl filter parameters if Fl_Mode > 0, fix example_05 * bump version to 2.3 * Compile ROSCO from ROSCO dir * Rename to CI_rosco * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * Make _Toolbox vs_minspeed in rotor frame to match ROSCO * Revert ServoDyn change * change rotor speed constraint to be epsilon * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * Deallocate arrays in ROSCO, check in example_05 * Clean up comments * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * Add omega vs. windspeed functionality * Fix tests: 5MW U_pc and 06 example dir * more detailed sp.optimize settings * run MBC3 in parallel * restructure driver, run initialization doe for tuning * Try new dlclose function * Update example_05 to run simple simulation twice and check result * Revert deallocation stuff * Close discon library after every sim run * Test examples on macOS and windows * Run examples instead of testing on other platforms * Skip examples in windows for now * update paths and yaml load funciton * Skip mac testing of examples * provide default U_pc for single omega/zeta case * allow for float or list-like pc tuning inputs * Change name in setup.py * WE_Vw unit fix * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * cleeanup for improved stability * check for doe_logs as string in load_DOE * major restructure for rsched_driver class * cleanup verbosity * run serial by default * load_parallel as linturb_option * specific IEA15MW yaml for multi omega * remove unused module imports * fix error message types * lin_file as input * add comments on inputs to LinearTurbineModel init method * remove relative file paths * provide OpenFAST linearizations for IEA15MW UMaineSemi * put plotting in specific function * fix WE_lambda units * add self in on a few necessary variables * creaete example 12 for robust scheduling * try a few mbc3 locations for import * allow list-like or numpy arrays for omega_pc and zeta_pc schedules * create and use recorder setup function * Pass Through Kp_float (#57) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * fix setup_recorder to work for optimization driver too * Allow pass through of Kp_float = 0 * cleanup om problems, update om0 calc * doe levels as input * negative k_float to account for OF conventions * cleanup print statements * variable name cleanup, use calculated k_float * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * use standard tuning k_float as IC * formatting update * update problem setup methods * cleanup add_dv, enable adding design variables after problem is setup * change optimization step size * more setup restructure * update verbosity * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Mostly a docs update (#61) * rename for clarity * docs major refresh * fix FA_AccF units in debug file * docs that build locally * remove gitmodules * furo theme * furo in requirements * move readthedocs config file, remove furo import in conf.py * add docs requirements file * typo * move index out of source folder * trying to get furo to work * import date * fix versions and titles, cleanup readthedocs requirements * typo fix, remove extras * more cleanup * bump version * no furo extension, "hack" to load RT version * proper toctree paths * specify method * add mock modules * fix typos * update python install requirements * running locally * move index to main docs dir again * update to build locally * error during warnings * automated version * cleanup * remove old docs * re-add docs * simplify * fix figure path * try alabaster * remove archived docs * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * Pass through Kp_float = 0 (#59) * Pass through kp_float, if desired * Minor fixes: schema error and InputReader defaults * Allow pass through of Kp_float = 0 * Add flp parameters to schema * Change Fl_Mode default to 0 * Add defaults to omega_,zeta_ pc and vs, allow to be numbers * Allow single pitch tuning values in code, default U_pc to 0 * use nac acceleration for floating feedback * Fix TSR saturation for region 2.5operation * Modify system for constant power operation * Only modify pole for constant power above-rated * Remove GenEff from K calc * Update tuning, use constant power * use load_rosco_yaml * constant power * Fix broken tests * Include Fl_Mode=2 for nacelle pitching feedback * Add FOCAL inputs - hpf on floating feedback - lpf on wind speed estimator - associated schema updates * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Skip filter step if there's an error * Update IEA-15MW test case DISCON with focal inputs * Allow Fl_Mode = 2 in ROSCO * Pass through lpf frequency * Add FOCAL tuning yaml * Set Cp contour number of levels * Add FOCAL params to various writers * Update/tune focal yaml * Add scripts for running FAST, tuning various parameters and cases * Add notebook for FAST plotting * Set up step case for testing * Change doubles to C_doubles * Define real and integer kinds, assign to all of ROSCO * Add ADJUSTL to DISCON error message * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Call yaw, flap, and debug only if enabled in DISOCN.IN * Make avrSWAP a ReKi and set constant kinds * Add DISCONs for testing - revert this later * Fix DISCON comparison, before DISCON's were overwritten by model * Rename DEBUG2.dbg to RootName.dbg2 * Update TestCase DISCONs to new input file * Add API change page in docs * Add link to API change on main page * Fix table headers * Fix title underlines * Fix tables again * Fix tables again * Fix version numbering in docs * Simplify FAST_directory in run_FAST * Versioning (#65) * use sphinx-rtd-theme * master doc and sphinx rtd theme * index back to root folder * only ignore install folders * remove hidden toctree * furo theme * update paths * convert rt version to string * update sphinx settings * move conf * furo theme * remove git versioning from cmake * use hard coded rosco_version * update intro write method * set nowrap for intel compilers * Add transfer of error message and clear message after each call * update install instructions * Catch nans in ROSCO at end of WSE * fix conda install typo * cleanup docs * Rename DEBUG2.dbg to RootName.dbg2 * Fix Fl_Mode == 2 * Fix Fl_Mode == 2 again * Allow Fl_Mode = 2 in ROSCO * Set notch and check frequencies when Fl_Mode = 1 (fixes bug) * Update FOCAL tuning yaml * Update TSR * Clean up and doc fix * Remove publish to pypi * Define all constant inputs to functions with kind typing * Generate Test_Case/ inputs automatically * Fix IEA15 DISCON path * Fix example 11 paths * Auto-generate tuning input yaml using schema * Add toolbox_input to doc index * Add toctree * Re-name title of toolbox_input Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> * update listcheck method for numpy arrays * Open Loop Control (#98) * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * create rosco types yaml * Add more descriptions, add integer_c default, cleanup * updates for allocatability and shapes * python scripts to write ROSCO_types.f90 * reemove superfulous modules * use types from ROSCO registry * Saturate inputs to WSE. Needs some TLC, but seems to work * Reduce saturation limits on speed, torque * Re-organize, set saturation limits. Working at 3 m/s * Initial add of OL control to ROSCO: builds * Update DISCONs with open loop inputs * Fix file reading for OL_Filename * Add open loop control generation and file writing * Use DISCON_dict for more manageable DISCON file writing * Add open loop example, fix constant timeseries * Make open loop example generate power * Handle relative paths and calling from outside the run directory: - Some helper functions borrowed from OpenFAST, f/ext_control - Updated file writing * Clean up: versions, print statements * Fix SysFiles paths in CMakeLists * Tidy up Ext_DLL names * More Ext_DLL name tidying * Test write_registry.py * Update for OL Control * Move preprocessor lines * add zenodo DOI * Regenerated Types * fix shape * revert filepath change * give all types a size, ProcAddr size = 3 * update types * test registry in compile step * specify default shell * update write_registry path * remove default shell * Document API changes, provide OL input example * Fix example 14 (yaw input) * Add error catching to yaw control * Tidy up OL_Input Reading: error catching, generalize * More yaw control fixes, to model * Checkout develop CMakeLists for ROSCO * Update DISCON.INs for TestCases/ * Revert "Checkout develop CMakeLists for ROSCO" This reverts commit 87a491359d73806e3aaa59b4cfdc00f2838875df. * Revert windows cmake stuff to develop * Fix CMake again * Revert "Revert windows cmake stuff to develop" This reverts commit 39df122449b0e2055f2e12e3ce38a0c010c7bd91. * Make last cmake fix - hopefully Co-authored-by: Nikhar Abbas * Restart & registry (#99) * Convert WE saved variables to WE type * Put restart flag in localvars * Use saved filter params from LocalVar * save pitcomt last * Move IPC saved variables to localvars * Saved pi controller variables to localvar * Save RootMyb_Last to localvar * ROSCO_IO - initial commit. Include restart and debug functions * Use ROSCO IO and call restart functions * Remove debug from function.f90 * Save ACC Infile info * update for restart capabilities * add rosco_io with restart and debug functions * cleanup debug call * use registry generate types and IO * delete DFController * fix timestep mismatch * remove unnecessaray istatus check * close files * add reg test for restart * add restart option to run_openfast * add testing to CI, ignore generate files * fix fastcall * remove extra commas * specify gfortran-10 * testing flag cleanup * Use lv_strings to generate debug output * Revert "testing flag cleanup" This reverts commit 6f295563832413e96347acd82716f9aadac6d46c. * Revert "specify gfortran-10" This reverts commit 4c3154491523bcd542133ca2bce5803cdc94523f. * minor cleanup * Use kind from constants * Add some comments for clarity * put debug in if statements * separate reg tests from oother tests * Fl_Mode>0 * Remove hard coded values * Add filtered signals and WE_Vw to debug varrs * cd for regtest * Check logging level before calling debug * add fl_pitcom and pc_minpit to debugvars Co-authored-by: dzalkind * Break up if statement in open loop pitch (#100) * Break up if statement in open loop pitch * Make torque and yaw consistent with pitch: can start after some time * add bld edgewise freq to robust dict_inputs * Fix ccrotor inputs (#104) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Allow default inputs (#110) * Allow defaults for AeroDyn inputs * Allow AeroDyn inputs to be floats, too * ipc (#105) * remove interpolation of blade chord and twist * rename aerodynblade inputs * Update surface and DISCON.INs * Fix performance table paths * Add proportional control and cleanup IPC * Add IPC and filtered RootMyc to registry * Better logic for filtering RootMOOP and fix notch filter slopes bug * Add cyclic flap conrol * Fix comments in ColemanTransformInverse * Addd IPC_KP to DISCON.IN * Error checking foro flp or ipc * add mutichannel plotting with tuples * add CMakeFiles to gitignore * Add IPC_KP to api changes * numerical qualifiers for error handling * add IPC gains to schema for pass-through ability * fix variable names * fix ipc gain printing bug * make sure IPC_KP is positive * Update Polars to point to coord files * ignore dbg2 files * Add IEA15MW_OL.yaml * update coord reader/writer * expand pitch_initial to 30 degrees * Add example 13 for IPC * Update cp surfaces and DISCONS * add examples to readme * cleanup and streaamline run_examples * Add IPC tuning vars * Allow IPC to command pitch value below peak shaving saturation limit * shorten simulation time * Fix Material parameter path * Update DISCONs again * Fix OL_Input reading * Set wind speed, rotor speed IC in example 14 * Debug OL reading * Add more debugging lines * Add more debugging lines 2 * Clean up, hone in on debug call * Disable logging level * Update discons - resolve conflict * Print when finished with ROSCO * add control packageg * Use PriPath and RootName to name dbg files * Print AvrSWAP * Revert "Use PriPath and RootName to name dbg files" This reverts commit 062fcaa55b3bf42d44f8a3f163aa883ab9552412. * Disable other examples * Print OL inputs * Allow logging level 3 * Print OL inputs * Make example shorter * Print more stuf * Print shape * Revert "Print OL inputs" This reverts commit 8e2a642bb35e46850f579ca4e69ca6d6fc93f4cc. * Update ROSCO Simulink model with IPC example * refactor flap tuning for normalization methods * improved flap controller filtering * delete extra F_FlpCornerFrerq * Update inputs, reader, and writer for OF 3.1.0 * Make sigma default interp type for multi_sigma * Use openfast 3.1.0 in CI * Fix leak...maybe * Use OF 3.1.0 in testing * Use gfotran for compile * Clean up print statements * Re-enable all examples * Update NREL-5MW AD file * Only check airfoil controls if more than one table * Update BAR models * Revert "Use gfotran for compile" This reverts commit 5a6e2b7e30287a09a09781e2ba12f9b578132e1f. * Install pyFAST for CI * Fix some paths in ex12 * Disable example 12 * Fix example 12 linear path, re-enable * Skip compilers install for mac * Try gfortran-9, no compilers * Try gfortran-10, no compilers * Skip windows compile in pytools * Re-enable windows, use gfortran as FC * Unset FC in windows * Set environment for windows when dependencies installed * Try setting environment in installation * Put conditional env setting in correct place * Try in setup again * Break up tasks: Windows vs. not * Update DISCONs * Update docs with new variables * Add example documentation * Update ROSCO Simulink model for 3.1.0 * Make IEA model float again * Reduce IEA timestep * Match DT to checkpoint time Co-authored-by: dzalkind * Increment version number * Bladed docs (#116) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update version in API change docs * Bladed readthedocs (#117) * Added image of Bladed control screen setup * Delete Bladed control screen.png * Adding image of Bladed control screen * Add files via upload * Minor edit 1 * Minor change 2 * Minor change 3 * Minor change 4 * Change 6 * Change 7 * Change 8 * Minor change 9 * Tinker with characters in bladed instructions * Add bladed instructions to index * Change bladed toctree label * Do underline stuff * Make toctree label same as file * Remove colons from headers Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Update docs to reflect CI process Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> * Fixed wrong formatting of list items (#122) extra newline required between list elements * Flip Ct and Cq table allocation (#129) * Clean up merge and regen discons Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> Co-authored-by: Gustavo Hylander <74593034+ghylander@users.noreply.github.com> * Clean up merge and regen discons * Update docs, input file writing with minor edits before 2.6.0 * Increment version number * Update toolbox inputs on docs * Fix version numbering Co-authored-by: Nikhar Abbas Co-authored-by: Xianping Du <38188001+Seager1989@users.noreply.github.com> Co-authored-by: nikhar-abbas <40865984+nikhar-abbas@users.noreply.github.com> Co-authored-by: WillC-DNV <100850534+WillC-DNV@users.noreply.github.com> Co-authored-by: Gustavo Hylander <74593034+ghylander@users.noreply.github.com> --- .github/pull_request_template.md | 10 + .github/workflows/CI_rosco-pytools.yml | 52 +- .gitignore | 4 + Examples/ROSCO_walkthrough.ipynb | 310 +++-- Examples/example_01.py | 2 +- Examples/example_04.py | 8 +- Examples/example_05.py | 9 +- Examples/example_07.py | 2 +- Examples/example_10.py | 1 - Examples/example_11.py | 2 +- Examples/example_12.py | 27 +- Examples/example_13.py | 5 +- Examples/example_14.py | 2 +- Examples/example_15.py | 45 + Examples/example_16.py | 67 ++ Examples/example_17.py | 134 +++ Examples/run_examples.py | 39 +- ROSCO/CMakeLists.txt | 68 +- ROSCO/rosco_registry/rosco_types.yaml | 162 ++- ROSCO/rosco_registry/write_registry.py | 74 +- ROSCO/src/Constants.f90 | 2 +- ROSCO/src/ControllerBlocks.f90 | 3 - ROSCO/src/Controllers.f90 | 218 +++- ROSCO/src/DISCON.F90 | 31 +- ROSCO/src/ExtControl.f90 | 135 +++ ROSCO/src/Filters.f90 | 48 +- ROSCO/src/Functions.f90 | 159 +-- ROSCO/src/ROSCO_Helpers.f90 | 1041 +++++++++++++++++ ROSCO/src/ROSCO_IO.f90 | 266 +++-- ROSCO/src/ROSCO_Types.f90 | 59 +- ROSCO/src/ReadSetParameters.f90 | 955 ++------------- ROSCO/src/SysFiles/SysGnuLinux.f90 | 2 +- ROSCO/src/SysFiles/SysGnuWin.f90 | 2 +- ROSCO/src/SysFiles/SysIFL.f90 | 2 +- ROSCO/src/SysFiles/SysIVF.f90 | 2 +- ROSCO/src/ZeroMQInterface.f90 | 77 ++ ROSCO/src/zmq_client.c | 95 ++ .../{regtest.py => test_checkpoint.py} | 0 ROSCO_toolbox/__init__.py | 2 +- ROSCO_toolbox/control_interface.py | 281 ++++- ROSCO_toolbox/controller.py | 167 ++- ROSCO_toolbox/inputs/toolbox_schema.yaml | 451 ++++++- ROSCO_toolbox/linear/lin_util.py | 76 +- ROSCO_toolbox/linear/lin_vis.py | 87 ++ ROSCO_toolbox/linear/linear_models.py | 53 +- ROSCO_toolbox/linear/robust_scheduling.py | 135 ++- ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py | 571 +++++---- .../ofTools/case_gen/runFAST_pywrapper.py | 662 +++++------ ROSCO_toolbox/ofTools/case_gen/run_FAST.py | 359 +++--- ROSCO_toolbox/ofTools/fast_io/FAST_reader.py | 12 +- ROSCO_toolbox/ofTools/fast_io/FAST_wrapper.py | 65 +- ROSCO_toolbox/ofTools/fast_io/FAST_writer.py | 7 +- .../ofTools/fast_io/update_discons.py | 46 + ROSCO_toolbox/sim.py | 278 ++++- ROSCO_toolbox/turbine.py | 49 +- ROSCO_toolbox/utilities.py | 91 +- Test_Cases/BAR_10/BAR_10_DISCON.IN | 55 +- .../DISCON-UMaineSemi.IN | 55 +- .../IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat | 4 + Test_Cases/NREL-5MW/DISCON.IN | 55 +- .../NRELOffshrBsline5MW_Onshore_ServoDyn.dat | 2 +- Test_Cases/Wind/NoShr_3-15_50s.wnd | 17 + Test_Cases/update_discons.py | 54 - Test_Cases/update_rosco_discons.py | 29 + Tune_Cases/BAR.yaml | 2 +- Tune_Cases/IEA15MW.yaml | 8 +- Tune_Cases/IEA15MW_ExtInterface.yaml | 59 + Tune_Cases/IEA15MW_FOCAL.yaml | 3 +- Tune_Cases/NREL5MW.yaml | 2 +- Tune_Cases/NREL5MW_PassThrough.yaml | 64 + docs/source/api_change.rst | 72 +- docs/source/install.rst | 14 +- docs/source/toolbox_input.rst | 436 ++++++- environment.yml | 3 + setup.py | 2 +- 75 files changed, 5855 insertions(+), 2593 deletions(-) create mode 100644 Examples/example_15.py create mode 100644 Examples/example_16.py create mode 100644 Examples/example_17.py create mode 100644 ROSCO/src/ExtControl.f90 create mode 100644 ROSCO/src/ROSCO_Helpers.f90 create mode 100644 ROSCO/src/ZeroMQInterface.f90 create mode 100644 ROSCO/src/zmq_client.c rename ROSCO_testing/{regtest.py => test_checkpoint.py} (100%) create mode 100644 ROSCO_toolbox/linear/lin_vis.py create mode 100644 ROSCO_toolbox/ofTools/fast_io/update_discons.py create mode 100644 Test_Cases/Wind/NoShr_3-15_50s.wnd delete mode 100644 Test_Cases/update_discons.py create mode 100644 Test_Cases/update_rosco_discons.py create mode 100644 Tune_Cases/IEA15MW_ExtInterface.yaml create mode 100644 Tune_Cases/NREL5MW_PassThrough.yaml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c57c562d..c809806c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,6 +13,16 @@ _Select the appropriate type(s) that describe this PR_ - [ ] Maintenance update - [ ] Other (please describe) +TODO Items General: +- [ ] Add example/test for new feature +- [ ] Update registry +- [ ] Run testing + +TODO Items API Change: +- [ ] Update docs with API change +- [ ] Run update_rosco_discons.py in Test_Cases/ +- [ ] Update DISCON schema + ## Github issues addressed, if one exists ## Examples/Testing, if applicable diff --git a/.github/workflows/CI_rosco-pytools.yml b/.github/workflows/CI_rosco-pytools.yml index 7ea77301..141c16ee 100644 --- a/.github/workflows/CI_rosco-pytools.yml +++ b/.github/workflows/CI_rosco-pytools.yml @@ -92,34 +92,44 @@ jobs: python-version: 3.8 environment-file: environment.yml + # setup cmake + - name: Setup Workspace + run: cmake -E make_directory ${{runner.workspace}}/ROSCO/ROSCO/build # Install dependencies of ROSCO toolbox - name: Add dependencies ubuntu specific - if: false == contains( matrix.os, 'windows') - run: | - conda install -y wisdem - - name: Add dependencies windows specific - if: true == contains( matrix.os, 'windows') run: | - conda install -y m2w64-toolchain libpython conda install -y wisdem + - name: Add pyFAST dependency - if: false == contains( matrix.os, 'windows') run: | git clone http://github.com/OpenFAST/python-toolbox cd python-toolbox python -m pip install -e . + # Install ZeroMQ + - name: Install ZeroMQ + run: sudo apt-get install libzmq3-dev # Install ROSCO toolbox - name: Install ROSCO toolbox run: | - python setup.py install --compile-rosco + python setup.py develop + + - name: Configure and Build ROSCO - unix + working-directory: ${{runner.workspace}}/ROSCO/ROSCO/build + run: | + cmake \ + -DCMAKE_INSTALL_PREFIX:PATH=${{runner.workspace}}/ROSCO/ROSCO/install \ + -DZMQ_CLIENT=ON \ + -DCMAKE_Fortran_COMPILER:STRING=${{env.FORTRAN_COMPILER}} \ + .. + cmake --build . --target install # Install OpenFAST - name: Install OpenFAST run: | - conda install openfast==3.1.0 + conda install openfast==3.2.0 # Run examples - name: Run examples @@ -127,6 +137,21 @@ jobs: cd Examples python run_examples.py + # Test walkthrough notebook + - name: Test walkthrough notebook + run: | + cd Examples + treon ROSCO_walkthrough.ipynb + + # Archive artifacts + - name: Archive production artifacts + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: ROSCO-artifacts + path: | + ${{runner.workspace}} + run_testing: name: Run Testing runs-on: ${{ matrix.os }} @@ -156,8 +181,7 @@ jobs: # Install dependencies of ROSCO toolbox - - name: Add dependencies ubuntu specific - if: false == contains( matrix.os, 'windows') + - name: Add dependencies run: | conda install -y wisdem @@ -165,12 +189,12 @@ jobs: # Install ROSCO toolbox - name: Install ROSCO toolbox run: | - python setup.py develop --compile-rosco + python setup.py install --compile-rosco # Install OpenFAST - name: Install OpenFAST run: | - conda install openfast==3.1.0 + conda install openfast==3.2.0 # Run ROSCO Testing - name: Run ROSCO testing @@ -182,4 +206,4 @@ jobs: - name: Run regression testing run: | cd ROSCO_testing - python regtest.py + python test_checkpoint.py diff --git a/.gitignore b/.gitignore index 44e74d47..e633e4cd 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ examples/cp_ct_cq_lut.p # Files Generated in Examples Examples/DISCON.IN +Examples/DISCON_zmq.IN Examples/*.p # Exclude testing results @@ -86,3 +87,6 @@ ROSCO_testing/results/ *.autosave *.mat +# OpenFAST outputs +outputs/ + diff --git a/Examples/ROSCO_walkthrough.ipynb b/Examples/ROSCO_walkthrough.ipynb index 0cf1ad1a..8f078be6 100644 --- a/Examples/ROSCO_walkthrough.ipynb +++ b/Examples/ROSCO_walkthrough.ipynb @@ -22,17 +22,40 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 1, "metadata": { "slideshow": { "slide_type": "subslide" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using ofTools in ROSCO_toolbox...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/dzalkind/opt/anaconda3/envs/rosco-env/lib/python3.8/site-packages/openmdao/utils/general_utils.py:130: OMDeprecationWarning:simple_warning is deprecated. Use openmdao.utils.om_warnings.issue_warning instead.\n", + "/Users/dzalkind/opt/anaconda3/envs/rosco-env/lib/python3.8/site-packages/openmdao/utils/notebook_utils.py:171: UserWarning:Tabulate is not installed. Run `pip install openmdao[notebooks]` to install required dependencies. Using ASCII for outputs.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING: Be sure to pip install simpy and marmot-agents for offshore BOS runs\n" + ] + } + ], "source": [ "# Load necessary modules\n", "# Python Modules\n", - "import yaml\n", + "import os, platform\n", "import matplotlib.pyplot as plt \n", "import pprint\n", "import numpy as np\n", @@ -44,7 +67,10 @@ "from ROSCO_toolbox import utilities as ROSCO_utilities\n", "from ROSCO_toolbox import controller as ROSCO_controller\n", "from ROSCO_toolbox import control_interface as ROSCO_ci\n", - "from ROSCO_toolbox.ofTools.fast_io.output_processing import output_processing\n" + "from ROSCO_toolbox.ofTools.fast_io.output_processing import output_processing\n", + "\n", + "from ROSCO_toolbox.inputs.validation import load_rosco_yaml\n", + "\n" ] }, { @@ -80,8 +106,8 @@ "outputs": [], "source": [ "# Load yaml file \n", - "parameter_filename = 'NREL5MW_example.yaml'\n", - "inps = yaml.safe_load(open(parameter_filename))\n", + "parameter_filename = '../Tune_Cases/NREL5MW.yaml'\n", + "inps = load_rosco_yaml(parameter_filename)\n", "path_params = inps['path_params']\n", "turbine_params = inps['turbine_params']\n", "controller_params = inps['controller_params']" @@ -100,16 +126,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'TSR_operational': None,\n", + "{'TSR_operational': 0,\n", " 'bld_edgewise_freq': 6.2831853,\n", " 'bld_flapwise_freq': 0.0,\n", " 'max_pitch_rate': 0.1745,\n", " 'max_torque_rate': 1500000.0,\n", - " 'ptfm_freq': 0.2325,\n", " 'rated_power': 5000000.0,\n", " 'rated_rotor_speed': 1.26711,\n", " 'rotor_inertia': 38677040.613,\n", - " 'twr_freq': 0.4499,\n", " 'v_max': 25.0,\n", " 'v_min': 3.0,\n", " 'v_rated': 11.4}\n" @@ -169,7 +193,7 @@ "Loading wind turbine data for NREL's ROSCO tuning and simulation processeses\n", "-----------------------------------------------------------------------------\n", "Loading FAST model: NREL-5MW.fst \n", - "Loading rotor performace data from text file: /Users/dzalkind/Tools/ROSCO_toolbox/Test_Cases/NREL-5MW/Cp_Ct_Cq.NREL5MW.txt\n", + "Loading rotor performace data from text file: /Users/dzalkind/Tools/ROSCO/Test_Cases/NREL-5MW/Cp_Ct_Cq.NREL5MW.txt\n", "Loading rotor performace data from text file: Cp_Ct_Cq.NREL5MW.txt\n" ] } @@ -193,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "slideshow": { "slide_type": "subslide" @@ -210,14 +234,14 @@ "Total Inertia: 43702538.1 [kg m^2]\n", "Rotor Radius: 63.0 [m]\n", "Rated Rotor Speed: 1.3 [rad/s]\n", - "Max Cp: 0.48\n", + "Max Cp: 0.47\n", "------------------------------------------\n", " \n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -249,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "slideshow": { "slide_type": "subslide" @@ -287,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "slideshow": { "slide_type": "subslide" @@ -296,7 +320,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ4AAAEGCAYAAABVSfMhAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAA24UlEQVR4nO3deXhU5dn48e+dQAKBsEMIe8AAslcii1qNiorWrbYq1iJWW6pv6fq2Fdu3b7W1v9LNrtZWrRasa0VfrVJR0GgVUFbZlyQC2UggQDay5/79cU50iDPJJJnJmUnuz3XNNXPOnOec+5mcyT3nOc95jqgqxhhjTEeJ8ToAY4wxXYslHmOMMR3KEo8xxpgOZYnHGGNMh7LEY4wxpkN18zqASDdo0CAdM2aM12EEraKigl69enkdRkh0hbps3rz5mKoO7uh4bL/2TmeqC/ivT0v7tSWeFowZM4ZNmzZ5HUbQMjIySE9P9zqMkOgKdRGRQx0fje3XXupMdQH/9Wlpv7amNmOMMR3KEo8xxpgOZYnHGGNMh7LEY4wxpkNZ4jHGGNOhLPEYY4zpUJZ4jDHGdCi7jseYMMk5foq39h8lIS6WAV4HY0wEscRjTIhU1dazIbuYt/Yf5a39R8k+WgFA+oTB3JricXDGRBBLPMa0w5GSKtbuLeSNPUW8k3mM6roG4rvFMHfcQBbOGc0F4weTMqgXb731ltehGhMxLPEY0wqqyu6CUl7bVciaPYXsyi8FYOSAntw0axQXThzC7JQB9Oge63GkxkQuSzzGtKChQdly+ASv7jzC6t1HyDleSYzAWaP6c9f8icw7cwhnDOmNiHgdqjFhUV1Xz/GKGorLazhWXv3R8yWTktq0Pks8xvjR0KBsPnyClz/IZ9XOIxwtqyYuNoZzzxjI19LPYN6kJAb1jvc6TGPaTFU5eaqWorJqisqqKCx1no+WVXO0rJpj5dUfvS6tqvO7jqF9e9C3Ddu2xGOMS1XZlnOSl7cX8Mr2Ao6UVhHfLYaLJg7h8qnJXDhhMIk9unsdpjEtqm9QisqqyD9ZxZGSKgpKKikoqeJIaRVFpc5zYWk1NXUNnyjbO74bgxPjGdw7nglDEznvjEEM6h3PoMR4BvaKY2DveAb1jmNQ73gS4mJ5660DrY7PEo/p8g4Xn+L5rbn839Y8DhafIi42hvPHD+buKyYy78wkesXb18REluq6evJOVJJzopK8E5XknTzlPlc6yaa0ivoGPa1Mz+6xDO3bg6Q+8Zw1qj9D+/RgSB9nekhiD4YkxjOkTzwJceHf3+0bZbqk0qpaXv6ggOe35LLp0AlEYE7KQP7rwjO4bPJQ+va0IxvjrZJTtRwsruBgcQWHik9xsLiC3OOV5Jw4xZHSKtQnr8TGCEP79GB4/57MThnAsH49Se7Xg2F9ezK0r/Pcp2e3iDkPaYnHdBmqysaDJ3h642FW7SigqraBM4b05vvzJ3DtjOEM69fT6xBNF1NdV8/BY6fIPlpO9rEKso9WkH2snA+PVXDyVO1pyyb1iWf0gF7MHTeQkf0TGDkggZH9ezJyQAJDEuPpFhs9A9FY4jGdXnF5Nc9tzuWZjTlkH6ugd3w3rjtrBDemjWTaiL4R8yvQdF41dQ1kHS1n35Ey1uyv4cnDm8gsKufQ8VOnNYkl9Yln7KDeXDE1mZSBvRg9MIExg3oxsn8CPeM6Txd9Szym09qWc5IV6w7y8vYCauobSBvdnzvTx/GZackd0o5tuqbi8mp25ZeyK7+UvUdK2XekjMyicurcBBMjkDKonPFJiXxmWjJnDOnNuMG9GTOoF727yPnErlFL02VU1dbzyvYCVqw/yAe5JfSKi2XBrJEsnDOa1KREr8Mznczxiho+yDnJ9twSduaXsCuvhPySqo/eH9a3BxOT+3DhxCFMHJrIxKF9yNm9iXkXpXsXdASwxGM6hRMVNfxjwyGWrz/IsfIaxg3uxU+umcxnPzXcukCbkKitb2B3filbDp9gW85JtuWc5FDxKQBEIGVQL9LGDGDq8L5MHt6Hycl96ZvwyX2vYK817VriMVHtUHEFf3vnQ57dlENVbQPpEwbz5fPGcu4ZA+3cjWmXUzV1bD18ko0Hj7Px4HG2Hj7JqZp6wDkXM2NkPxacPYoZI/sxdUTfLtNMFgqefFIiMgB4BhgDHARuUNUTfpabD/weiAUeUdVlzZUXkUuAZUAcUAN8T1XfcMtkAMlApbv6S1W1KCwVNGG3p6CUP72RyaqdBXSLEa6dMZwvf3osE4Zac5ppm9r6Bj7IOcm7mcW8m3mMrTknqK1XRGDi0D5cP3MEZ6cMYObo/iT3tR6Q7eFVil4KrFXVZSKy1J2+y3cBEYkFHgAuAXKBjSLykqrubqb8MeAqVc0XkSnAamC4z2pvVtVN4a6cCZ+deSX8Ye0BXttdSO/4btxxwThuPWcMSX16eB2aiUJ5Jyt5Y28RGXuL2JBdTEVNPSIwZVhfbjsvhTljBzJzdH/6WHNtSHmVeK4B0t3Xy4EMmiQeYBaQqarZACLytFtud6DyqrrVp/wuoIeIxKtqdchrYDrUBzkn+cPaA6zdW0SfHt345sWp3HZuit82dGMCqXcHfF27p4g39hayv7AcgFEDEvjsWcM5d9wg5o4bSL+EOI8j7dxEVVteKtQbFTmpqv18pk+oav8my3wemK+qX3anFwKzVXVJK8rfoarz3OkMYCBQD6wE7tMAlReRxcBigKSkpJlPP/10O2vcccrLy+ndu7fXYYREeXk5pSSw8kANmwvr6dUdLhvTnXmjupPQPbrO3wT6u1x44YWbVTWtI2Loqvt1fYOy/0QDG4/UsamwntIaJVZgfP8Ypg3uxvTBsST3kg47J9iZvqPgvz4t7ddhO+IRkTXAUD9v/TDYVfiZF1SWFJHJwC+AS31m36yqeSKSiJN4FgIr/JVX1YeAhwDS0tI0PT09yJC9l5GRQTTFG0j+yUruevxt3s2vJCGuG9+eN57bP50StSdwI+Hv0pX268ZRKl7Ymsdru45QXFFDz+6xXDRxKPOnDOWCCYM9az6LhH0hlNpSn7B9ixuPNPwRkUIRSVbVAhFJBvyd5M8FRvpMjwDy3dcBy4vICOAF4BZVzfKJJ899LhORJ3Ga8vwmHuOdkspaHngzk7+vO0hDfQO3npPC1y4cx0C7BYEJQs7xU6zcksvzW/I4fPwUCXGxXHxmEle4ycYuHI4MXv0VXgIW4fRAWwS86GeZjUCqiKQAecAC4AvNlReRfsArwN2q+m7jikSkG9BPVY+JSHfgSmBN6Ktl2qq+Qfnnphx+tXofx0/VcN2nRjA3sZjPXz7J69BMhKupa+DfOwt48r3DvPfhcUTgnHED+da8VOZPGWrJJgJ59RdZBjwrIrcDh4HrAURkGE636StUtU5EluD0TIsFHlXVXc2VB5YAZwA/EpEfufMuBSqA1W7SicVJOg+Hu5ImOBsPHufef+1iZ14paaP7s/zqWUwZ3peMjAyvQzMRrLC0iifeO8yT7x3mWHk1owcm8N1Lx/PZs0Yw3AZ8jWieJB5VLQYu9jM/H7jCZ3oVsKoV5e8D7guw2ZltjdeER1FZFT97ZQ8vbssnuW8Pfr9gBldPH2YXfppmfZBzkof/k82rO49Qr8qFE4Zwy9zRnJ86mJgY23eigR2Dmg7X0KA8symH/7dqD9V1DXzjojO4I32cNYmYZm08eJw/vpHJ2/uPktijG7eeM4YvzhnNmEG9vA7NtJJ9002Hyiwq5wcv7OD9D48zO2UAP79uKmMHd56upSa0VJV1WcX8/L1K9r26noG94rhr/kQWzh0dtT0cjSUe00Fq6xt4MCOLP72RSY/uMfzic1O5IW2kNauZgHbmlfDTl3fz3ofH6Rcv/OjKSXxh1qhOdV+arsoSjwm7zKIyvv3MB+zIK+HKacn871WTGJJoQ9wY/wpLq/jlq/t4fmsuAxLi+Mk1kxl66kMuPS/F69BMiFjiMWHT0KCsWH+Qn/97Lwlxsfzli2cxf0qy12GZCFVZU89Db2fzl7eyqG9QFn96LF+76Az69OhORsZBr8MzIWSJx4TFkZIqvvfcB/znwDEunDCYX3x+mh3lmIDWZR7j+yu3k3uikiumDmXp/DMZNTDB67BMmFjiMSH3+u5CvvvPD6ipa+C+a6dw8+xRdi7H+FVeXceyf+/hHxsOkzKoF08vnsOcsQO9DsuEmSUeEzJ19Q386rV9/PWtbKYO78vvF8ywHmsmoHczj/H957aTX1LJl89L4b8vnWAdB7oISzwmJIrKqvj6k1t578Pj3Dx7FP971STiu9k/EfNJ1XX13PfyHh7fcIixg3rx3B1zmTl6gNdhmQ5kice023vZxSx5aitlVbXcf8N0rjtrhNchmQhVUFLJnf/Ywrack9x+Xgrfu2wCPbrbD5SuxhKPaTNV5e/rDnLfK3sYPSCBf9w+2249bQJ6L7uYrz25hcqaeuvh2MVZ4jFtUlffwL3/2s3jGw5xyaQk7r9hOol2e2DjR+MPlJ+9sodRAxJ46itzSE2yHyhdmSUe02plVbUseXIrb+0/ylfPH8td8yfa4IzGr5q6BpY+v53nt+Qx78wk7r9xumc3YDORwxKPaZXcE6e4/e+byDxazs+vm8pNs0Z5HZKJUFW19Sx5cgtr9hTxrXmpfOOiVPuBYgBLPKYVtuee5La/b6K6rp7lX5rFeamDvA7JRKjKmnoWP76J/xw4xk+vncLCOaO9DslEEEs8Jijrs4r58vKN9O8Vx1NfmW1t9Cag8uo6bvv7RjYdPM6vPj+N69NGtlzIdCmWeEyLMvYV8dXHNzNqQAL/+PJskvrY0DfGv5LKWhY9+j478kr43YJPcfX0YV6HZCKQJR7TrFd3FvD1p7YyPimRx2+fzYBecV6HZCJUaVUtX3h4A/sLy/jzzWdx2eShXodkIlSMFxsVkQEi8rqIHHCf+wdYbr6I7BORTBFZ2lJ5ERkjIpUiss19/MWnzEwR2eGu6w9ig4e16IWtuXztya1MG9GPJ78yx5KOCaiuvoElT25l35EyHlqYZknHNMuTxAMsBdaqaiqw1p0+jYjEAg8AlwOTgJtEZFIQ5bNUdYb7uMNn/oPAYiDVfcwPcZ06lafeP8x3nv2A2SkDWHHbLPr2tC6wJrD7XtnD2/uPct+1U7hw4hCvwzERzqvEcw2w3H29HLjWzzKzgExVzVbVGuBpt1yw5T8iIslAH1Vdr6oKrGipTFf2wtZc7n5+BxdOGMKjt55NL7vFsGnG4xsO8fd1B7n9vBQWWPd6EwSv/qMkqWoBgKoWiIi/n0jDgRyf6VxgdhDlU0RkK1AK/I+q/sddV26TdQ0PFJyILMY5OiIpKYmMjIzW1M1T5eXl7Yp3a1Edf9xazZkDYlgwspwN7/4ndMG1UnvrEkkioS7h2K93HavnN5urmD44lnN7FZKRUdTudfoTCZ9fqHSmukDb6hO2xCMiawB/Db0/DHYVfuZpC2UKgFGqWiwiM4H/E5HJrV2Xqj4EPASQlpam6enpwUUcATIyMmhrvOuyjvHgmo1MHdGPJ748m94eH+m0py6RJhLqEur9OutoOd944F1ShyTyjzvnhnXIpEj4/EKlM9UF2lafsP1nUdV5gd4TkUIRSXaPVpIBfz+TcgHfCwBGAPnua7/lVbUaqHZfbxaRLGC8u64RAdZlgG05J/nK8k2MGZjA8i+d7XnSMZGtpLKWLy/fRPfYGB5ZlGbj9JlW8eocz0vAIvf1IuBFP8tsBFJFJEVE4oAFbrmA5UVksNspAREZi9OJINttlisTkTlub7ZbAmyzS9p3pIxbH3ufgb3jefz22fRLsN5rpnk/+dduDh8/xV8XzmTkALtFtWkdrxLPMuASETkAXOJOIyLDRGQVgKrWAUuA1cAe4FlV3dVceeB8YLuIfAA8B9yhqsfd9+4EHgEygSzg3+GtYnQoLK3ilkffIy42hn/cbheHmpa9ua+IlVtyufOCcaSNsRu4mdbzpD1FVYuBi/3Mzweu8JleBaxqRfmVwMoA29wETGl71J1PVW09i1dsoqyqjufuOIdRA+2Xq2leaVUtP3h+B6lDevP1i8/wOhwTpbw64jEeU1W+99x2tueV8LsbZzBpWB+vQzJR4Oer9lBYWsWvrp9utzY3bWaJp4t64M1M/vVBPt+9dAKX2lXmJgjvHDjGU+/n8JVPj2XGyH5eh2OimCWeLujVnUf49Wv7uXbGMP4rfZzX4ZgoUFFdx9LntzN2UC++fcl4r8MxUc76zHYxu/JL+PYz25g+sh/LPjcNG7LOBOMXr+4l72Ql//zqXHp0tyY20z52xNOFnDxVw+IVm+nbszsPL5xp/0BMUDYdPM6K9Ye49Zwx1ovNhIQd8XQRqspdK7dTVFbFc3ecwxDrNm2CoKr8cvU+hiTG873LJngdjukk7Iini3jivcOs3lXI9y+byHQ7MWyCtD67mPc/PM6d6eNIiLPfqSY0LPF0AfuOlPHTl3dzwfjB3H5eitfhmCihqvxuzQGGJMZzk406bUKoxZ8wIjIY+Aowxnd5Vb0tfGGZUKmsqefrT20hsUd3fn39dGJirDOBCc76LOdo556rJtn5QBNSwRw7vwj8B1gD1Ic3HBNq972ym/2F5ay4bRaDE+O9DsdEicajnaQ+8XaPHRNywSSeBFW9K+yRmJB7dWcBT7x3mK+eP5bzxw/2OhwTRdZnFfP+wePce/VkO9oxIRfMOZ6XReSKlhczkaSwtIrvP7edaSP68t+XWm8kEzxV5bdr9jO0Tw9uPHtkywWMaaVgEs83cZJPpYiUikiZiJSGOzDTPve8tIvqugZ+v+BTxHWzPiQmeOuyitl48AT/deE4O9oxYdFiU5uqJnZEICZ0Xtt1hH/vPML3508gZVAvr8MxUcQ5t+Mc7dyQZkc7JjwCJh4Rmaiqe0XkLH/vq+qW8IVl2qqyTrn3xV1MHJrIVz491utwTJR5N9M52vnpNXZux4RPc0c83wEWA7/x854CF4UlItMuK/fXUFhWx4NfPIvusdbEZlpnW84JhvfryQ12bseEUcDEo6qL3ecLOy4c0x5bD59g7eE6bpk7mk+N6u91OCYKLbkolS+dm2L32jFhFdQYGCIyBZgEfDTAl6quCFdQpvVq6xu4+/kd9IsXvmtjapl26BVvQ+OY8GqxLUZEfgz80X1cCPwSuLo9GxWRASLyuogccJ/9/jwXkfkisk9EMkVkaUvlReRmEdnm82gQkRnuexnuuhrfG9KeOkSah/+Tzd4jZSycFEdij+5eh2M6mIjEisgwERnV+PA6JmMCCeYkwOeBi4EjqvolYDrQ3kvglwJrVTUVWOtOn0ZEYoEHgMtxjrZuEpFJzZVX1SdUdYaqzgAWAgdVdZvPam9ufF9Vi9pZh4hxuPgUv19zgPmTh3JWkv1a7WpE5OtAIfA68Ir7eNnToIxpRjCJp1JVG4A6EekDFAHt7S51DbDcfb0cuNbPMrOATFXNVtUa4Gm3XLDlbwKeamecUeGXq/cSI8I9V0/2OhTjjW8CE1R1sqpOdR/TvA7KmECC+Xm8SUT6AQ8Dm4Fy4P12bjdJVQsAVLUgQLPXcCDHZzoXmN2K8jfycaJq9JiI1AMrgftUVf0FJyKLcXr0kZSUREZGRnC18kB2ST0vb6/iqnHd2bt1A+Xl5REdb2tYXYKWA5S0tFA07ddN2b4QudpUH1UN+oEzQvW0IJddA+z087gGONlk2RN+yl8PPOIzvRD4o/u62fI4CWpHk3nD3edE4DXglmDqMXPmTI1UDQ0NeuNf1+lZP3lNSytrVFX1zTff9DaoEOoKdQE2aSu+g/4ewN+Ad4C7cS6D+A7wnebKRPJ+7U9X2Beilb/6tLRfN3cBaSzQU1XL3ek5QJz7OlFVy1pIaPOaWXehiCSrc7SSjNN811Qu4HsxwQgg333dUvkFNGlmU9U897lMRJ7EacqL6p55GfuOsiHbGcjROhR0aYfdR5z7MCaiNdfU9gucf+i/dKefwjli6QFsAdozYvVLwCJgmfv8op9lNgKpIpIC5OEkky+0VF5EYnCOls73mdcN6Keqx0SkO3AlzhFZ1KpvUJb9ey9jBibYTbq6OFW91+sYjGmN5hLPxcDZPtMnVfUqERGc+/O0xzLgWRG5HeeX2vUAIjIMp3ntClWtE5ElwGogFnhUVXc1V951PpCrqtk+8+KB1W7SicVJOg+3sw6eWrkll32FZTzwhbNsENAuSkR+p6rfEpF/4YwmchpVbddlD8aES3OJJ0ZV63ym7wJQVRWR3u3ZqKoW4yS2pvPzgSt8plcBq4It776XAcxpMq8CmNmemCNJZU0997+2n+kj+3HF1KFeh2O887j7/GtPozCmlZpLPHG+53JU9TUAEemLzwgGpuM9tu5DjpRW8fsFM3AOQE1XpKqb3ee3vI7FmNZoro3mYeAZ3yugRWQ0zrmeqG6mimbHK2p48M0s5p05hNljB3odjokAIpIqIs+JyG4RyW58eB2XMYE0N0jo/SJyCnhHRHrhtCFXAMtU9cGOCtCc7m/vZFNeU8f350/0OhQTOR4Dfgz8FmdYqy8BdihsIlazZ6VV9S+qOgoYDaSo6mhLOt4pr67j8fWHmD95KOOT7P585iM9VXUtIKp6SFXvwW5bYiJYUAN7NV7LY7z11HuHKa2q444LxnkdioksVe5lBAfcnqB5QKcaBNd0LtYPN0rU1DXwt3c+ZM7YAUwf2c/rcExk+RaQAHwDp/fmQpzr24yJSDaUcZR4cVseR0qrWPa5qV6HYiKMqm50X5bjnN8xJqI1N2TOdc0VVNXnQx+O8aehQXno7WwmDk3kgvGDvQ7HRAgROQ8Yq+5NGUXkOWCA+/Z9qvqGZ8EZ04zmjniuauY9BSzxdJA39hZxoKic391o1+2Y09wLfN1negJwK9AL+AFgicdEpOa6U9she4T469tZDO/Xk89MS/Y6FBNZ+qjqbp/pA40XlYrIzz2KyZgWBXWOR0Q+A0zGZ8QCVf1JuIIyH9t86DgbD57gx1dNonus9QUxp+nnO6Gqvs3jSR0bijHBa/E/mYj8Beemal/HuSjtepzrekwH+Mtb2fRL6M6NZ49seWHT1ex1fxSeRkSuBPZ5EI8xQQnmiOccVZ0mIttV9V4R+Q12fqdDZBaV8fruQr5xcSoJcdYB0XzCt4FXROTzOLcqAac79Tk4t/4wJiIF03ZT6T6fcm9bUAukhC8k0+ixdw8S3y2GRXPtANN8kqpmAtNwblMyxn28jXOX4P3eRWZM84L5Gf2yiPQDfoXzq0qBR8IZlHFuffDStnw+MzWZgb3jvQ7HRChVrQYe9ToOY1qjxcSjqj91X64UkZeBHqpaEt6wzKodBZRV13GDndsxxnQywfZqOwfnML6bO03jRWsmPJ7ZlMOYgQnMThnQ8sLGGBNFWkw8IvI4MA7YBtS7sxWwxBMm2UfLef/D43x//gS7YNQY0+kEc8STBkxS1U/c072tRGQA8AzOUdRB4AZVPeFnufnA74FY4BFVXebOvx64BzgTmKWqm3zK3A3cjpMkv6Gqq935M4G/Az1xbqf9zVDWKZSe3ZRLbIzw+bNGeB2KiWAisgPnR+An3sK5S/20Dg7JmKAEk3h2AkOBghBudymwVlWXichSd/ou3wVEJBZ4ALgEyAU2ishL7pXaO4HrgL82KTMJWIBzseswYI2IjFfVeuBBYDGwASfxzAf+HcI6hURtfQMrt+Ry4YQhDOljdxg3zbIu0yYqBZN4BgG7ReR9oLpxpqpe3Y7tXgOku6+XAxk0STzALCBTVbMBRORpt9xuVd3jzvO33qfdnj4fikgmMEtEDuIML7LeLbcCuJYITDxv7i3iaFm1XTBqWqSqh7yOwZi2CCbx3BOG7SapagGAqhaIiL+bVg0Hcnymc4HZLax3OM4RjW+Z4TjXHuX6me+XiCzGOToiKSmJjIyMFjYbOn/eXEXfeCHmyG4yiva0unx5eXmHxhtOVpfgiMgc4I84Tc9xOE3TFarap8lynu3X7WX7QuRqS32C6U79logkAWe7s95X1aKWyonIGpwmuqZ+GGRs/s6qt3ROJlCZVq1LVR8CHgJIS0vT9PT0FjYbGoWlVex47Q0Wnz+Wiy+a2KZ1ZGRk0FHxhpvVJWh/wmli/ifOOdlbgDOaLuTVfh0Kti9ErrbUJ5hebTfgXDyagfMP/I8i8j1Vfa65cqo6r5l1FopIsnu0kwz4S2S5gG970wggv4VwA5XJdV+3Zl0d7rnNudQ3KDekWTObaR1VzRSRWPd85mMiss7rmIwJJJghc34InK2qi1T1FpxzLz9q53Zf4uNb8y4CXvSzzEYgVURSRCQO5xfdS0Gsd4GIxItICpCKc4RWAJSJyBxxTgzdEmCbnlFVnt2Uw+yUAaQM6uV1OCa6nHK/I9tE5Jci8m2ce/IYE5GCSTwxTZrWioMs15xlwCUicgCn11pjN+lhIrIKQFXrgCXAamAP8Kyq7nKX+6yI5AJzcQZJXO2W2QU8C+wGXgW+5v4CBLgTZ6ifTCCLCOtYsCH7OIeKT1mnAtMWC3G+k0uACpyj/s95GpExzQimc8Gr7j/2p9zpG3G6I7eZqhYDF/uZnw9c4TO9yt+2VPUF4IUA6/4Z8DM/8zcBU9oedXg9tzmXxPhuXD7FbvZmgudedvAzVf0iUIVzV1JjIlownQu+JyKfA87FOcfzkPuP34RIbX0Dr+8+wqWTh9IzLtbrcEwUUdV6ERksInGqWuN1PMYEI6ix2lR1JbAyzLF0WRuyiymtqmP+FH+dAI1p0UHgXRF5CaepDQBVvd+ziIxpRsDEIyLvqOp5IlLG6V2PG4fj6BOgqGmlV3ceISEulk+nDvI6FBOd8t1HDJDocSzGtChg4lHV89xn25HDqKFBeW13IekTBtOjuzWzmdZTVTuvY6JKUKNTq+rCluaZttmac4KjZdVcNtma2UzbiMi/+OQF0SXAJuCvqlrV8VEZE1gw3aIn+06ISDec+7qbEHh15xHiYmO4aKK/UYOMCUo2UA487D5KgUJgvDttTERp7hzP3cAPgJ4iUto4G6jBHXbDtI+q8uquI5x7xkASe3T3OhwTvT6lquf7TP9LRN5W1fNFZJdnURkTQMAjHlX9OdAXWKGqfdxHoqoOVNW7Oy7Ezmt3QSk5xyutN5tpr8EiMqpxwn3d2FPFulibiNPsOR5VbRCR6R0VTFezeucRYgTmnZnkdSgmuv038I6IZOG0SqQA/yUivXBuO2JMRAnmOp4NInK2qm4MezRdzKu7jjArZQADe8d7HYqJYqq6SkRSgYk4iWevT4eC33kWmDEBBNO54EJgvYhkich2EdkhItvDHVhnl3W0nP2F5dabzbSbiCQA3wOWqOo2YKSI2N1JTcQK5ojn8rBH0QWt3nUEwBKPCYXHgM04g+aCcxuQfwIvexaRMc1o8YjHvb1uP+Aq99HPbrnbfqt3FTJ9RF+G9evpdSgm+o1T1V/i3GkXVa3E/80PjYkILSYeEfkm8AQwxH38Q0S+Hu7AOrP8k5V8kHOSy6w3mwmNGhHpiXsRqYiMA6q9DcmYwIJparsdmK2qFQAi8gtgPc493k0bvOY2s823ZjYTGvfg3H9qpIg8gTOS/Jc8jciYZgSTeASo95muxw7j2+W13YWkDunN2MG9vQ7FdAKq+pqIbAbm4Hw3v6mqxzwOy5iAgkk8jwHvicgLODv1NcDfwhpVJ1ZVW8+mQydYNHe016GYTkJE1qrqxcArfuYZE3GCuRHc/SKSAZznzvqSqm4Na1Sd2OZDJ6ipa+CccXYLBNM+ItIDSAAGiUh/Pm6J6AMM8ywwY1oQzHU8jQTn5GW7m9lEZICIvC4iB9zn/gGWmy8i+0QkU0SW+sy/XkR2iUiDiKT5zL9ERDa71xptFpGLfN7LcNe1zX14MirnuqxjxMYIZ6cM8GLzpnP5Kk436onuc+PjReABD+MyplnB9Gr7X5xhN/rjjP/0mIj8Tzu3uxRYq6qpwFp3uul2Y3G+PJcDk4CbRGSS+/ZO4Drg7SbFjgFXqepUYBHweJP3b1bVGe6jqJ11aJN1WcVMH9GX3vFB3fzVmIBU9feqmgJ8V1XHqmqK+5iuqn/yOj5jAgnmv99NOKPfVgGIyDJgC3BfO7Z7DZDuvl4OZAB3NVlmFpCpqtnudp92y+1W1T3uvNMKNGkC3AX0EJF4VY2IrqVlVbVszy3hzgvGeR2K6URU9Y8icg4wBp/vtKqu8CwoY5oRTOI5CPQAGsd+igey2rndJFUtAFDVggDNXsOBHJ/pXGB2K7bxOWBrk6TzmIjUAyuB+1S16c2zABCRxcBigKSkJDIyMlqx2cC2FdVR36AklOeSkVEQknU2VV5eHrJ4vWZ1CY6IPA6MA7bxcQ9UBVY0WS4s+3VHsH0hcrWlPsEknmpgl4i8jrMzX4IzEu4fAFT1G/4KicgawN+FKj8MMjZ/55L8Jgo/254M/AK41Gf2zaqaJyKJOIlnIU2+mB9tRPUh3HsOpaWlaXp6epAhN++dl3cT1+0Qt12dHrbbXGdkZBCqeL1mdQlaGjAp0A+pRuHarzuC7QuRqy31CSbxvOA+PtpOMCtW1XmB3hORQhFJdo92kgF/51tygZE+0yOA/Ja2KyIj3HhvUdWPjsxUNc99LhORJ3Ga8jq0KWJdVjEzR/UPW9IxXdZOnB954TmMNibEgulOvVxE4nBuowuwT1Vr27ndl3BO/i9zn1/0s8xGIFVEUoA8YAHwheZWKiL9cK5luFtV3/WZ3w1njLljItIduBJY0846tMrxihp2F5Ty3UvHt7ywMa0zCNgtIu/jM1SOql7tXUjGBNZi4hGRdJwOAAdxmr9GisgiVW3ao6w1lgHPisjtwGHgendbw4BHVPUKVa0TkSXAaiAWeFRVd7nLfRZnyJ7BwCsisk1VLwOWAGcAPxKRH7nbuhSoAFa7SScWJ+l06L3oN2QXAzDXrt8xoXeP1wEY0xrBNLX9BrhUVfcBiMh44ClgZls3qqrFwCeuqlbVfOAKn+lVwCo/yzVt/mucfx+Be9u1Od5QWJd1jF5xsUwb0dfLMEwnpKpveR2DMa0RTOLp3ph0AFR1v3vkYFphXVYxs1IG0D22NdfsGhOYiJThv8ONAKqqfTo4JGOCEkzi2Swif+PjizFvxrk62gTpSEkV2UcruOnsUV6HYjoRVU30OgZj2iKYxHMH8DXgGzi/pN4G/hzOoDqb9dnOQMFzxw30OBJjjPFes4lHRGKAzao6Bbi/Y0LqfNZlFtO3Z3cmJVvLhzHGNHvCQVUbgA9ExNqI2khVWZdVzNyxA4mJsdsYGWNMME1tyTgjF7yP0y0ZsGsEgnX4+CnyTlby1QvGeh2KMcZEhGASz71hj6ITW5flXL9zjp3fMcYYoJnE495k6g6cCzJ3AH9T1bqOCqyzWJdVzJDEeMbZba6NMQZo/hzPcpzBB3fg3BPnNx0SUSeiqryXXcycsQM/cQsHY4zpqpprapvk3lAN9zqe9zsmpM6jsLSaorJqPjWqn9ehGGNMxGjuiOejgUCtia1ttueeBLBhcowxxkdzRzzTRaTUfS1AT3fahuMI0s68EmIEJiVb4jHGmEYBE4+q2k1j2ml7XgnjkxLpGWcfpTHGNLIRK8NEVdmRW8LU4Xa0Y4wxvizxhEl+SRXFFTV2fscYY5qwxBMmO9yOBVNH9PM0DmOMiTSWeMJke24J3WKEiUNt5HpjjPFliSdMduSVMGFoIj26W8cCY4zx5UniEZEBIvK6iBxwn/sHWG6+iOwTkUwRWeoz/3oR2SUiDSKS5jN/jIhUisg29/EXn/dmisgOd11/kDAOJaCqbM8tsfM7xhjjh1dHPEuBtaqaCqx1p08jIrHAAzjD9UwCbhKRSe7bO4HrcG5K11SWqs5wH3f4zH8QWAykuo/5oapMUznHKymprGXq8H7h2oQxxkQtrxLPNThjweE+X+tnmVlApqpmq2oN8LRbDlXdo6r7gt2YiCQDfVR1vaoqsCLANkNie95JwEYsMMYYf4K5LUI4JKlqAYCqFojIED/LDAdyfKZzgdlBrDtFRLYCpcD/qOp/3HXlNlnX8EArEJHFOEdHJCUlkZGREcRmP/bKvhq6CRzZt4VjBzp2cNDy8vJWxxuprC6h1d792kuR8PmFSmeqC7StPmFLPCKyBhjq560fBrsKP/O0hTIFwChVLRaRmcD/icjk1q5LVR8CHgJIS0vT9PT04CJ2/XX/BiYPr2PeRee1qlwoZGRk0Np4I5XVJbTau197KRI+v1DpTHWBttUnbIlHVecFek9ECkUk2T3aSQaK/CyWC4z0mR4B5LewzWqg2n29WUSygPHuuka0Zl1t1dCg7Mwr4ZpPDQvH6o0xJup5dY7nJWCR+3oR8KKfZTYCqSKSIiJxwAK3XEAiMtjtlICIjMXpRJDtNuuVicgctzfbLQG22W4Hiysoq65jmnUsMMYYv7xKPMuAS0TkAHCJO42IDBORVfDRrRiWAKuBPcCzqrrLXe6zIpILzAVeEZHV7nrPB7aLyAfAc8Adqnrcfe9O4BEgE8gC/h2Oiu3IKwFgqnUsMMYYvzzpXKCqxcDFfubnA1f4TK8CVvlZ7gXgBT/zVwIrA2xzEzCl7VEHZ3tuCfHdYkgdYre6NsYYf2zkghDbkVvC5GF96BZrH60xxvhj/x1DqL5B2ZlfwjQbGNQYYwKyxBNC2UfLOVVTb/fgMcaYZljiCaHtuU7HAhuxwBhjArPEE0I78kpIiItl7GDrWGCMMYFY4gmhHXklTBnWl9iYjh0mxxhjooklnhCpb1B25Zcwxc7vGGNMsyzxhEjuiVNU1TbYHUeNMaYFlnhCJPtoBQBjB/fyOBJjjIlslnhCJOtoOQDjrGOBMcY0yxJPiGQdraB/Qnf694rzOhRjjIlolnhCJOtouR3tGGNMECzxhEj20QpLPMYYEwRLPCFQcqqWY+XV1rHAGGOCYIknBLKOWccCY4wJliWeELCu1MYYEzxLPCGQdbSc7rHCyAEJXodijDERzxJPCGQfLWfUgAS6283fjDGmRZ78pxSRASLyuogccJ/7B1huvojsE5FMEVnqM/96EdklIg0ikuYz/2YR2ebzaBCRGe57Ge66Gt8bEqr6ZFmPNmOMCZpXP9GXAmtVNRVY606fRkRigQeAy4FJwE0iMsl9eydwHfC2bxlVfUJVZ6jqDGAhcFBVt/kscnPj+6paFIqK1NU3cKi4gnFDLPEYY0wwvEo81wDL3dfLgWv9LDMLyFTVbFWtAZ52y6Gqe1R1XwvbuAl4KjThBpZzopLaemXsIOtYYIwxwejm0XaTVLUAQFULAjR7DQdyfKZzgdmt2MaNuInKx2MiUg+sBO5TVfVXUEQWA4sBkpKSyMjICLiRbUV1AJTk7iejPKsV4YVHeXl5s/FGE6tLaLVmv440kfD5hUpnqgu0rT5hSzwisgYY6uetHwa7Cj/z/CYKP9ueDZxS1Z0+s29W1TwRScRJPAuBFf7Kq+pDwEMAaWlpmp6eHnBb+9/OAvZy/aXn0zehezDhhVVGRgbNxRtNrC6h1Zr9OtJEwucXKp2pLtC2+oQt8ajqvEDviUihiCS7RzvJgL/zLbnASJ/pEUB+kJtfQJNmNlXNc5/LRORJnKY8v4mnNbKKKhjUOy4iko4xxkQDr87xvAQscl8vAl70s8xGIFVEUkQkDieZvNTSikUkBrge55xQ47xuIjLIfd0duBKng0K7ZR8rZ+wg61hgjDHB8irxLAMuEZEDwCXuNCIyTERWAahqHbAEWA3sAZ5V1V3ucp8VkVxgLvCKiKz2Wff5QK6qZvvMiwdWi8h2YBuQBzwciopkHa1g3BDrWGCMMcHypHOBqhYDF/uZnw9c4TO9CljlZ7kXgBcCrDsDmNNkXgUws11B+3GioobjFTV2DY8xxrSCXWrfDtnu4KA2RpsxxgTPEk87ZLmDg9oRjzHGBM8STztkHS0nLjaGEf1tcFBjjAmWJZ52yD5awZhBCcTG+LvkyBhjjD+WeNoh66h1pTbGmNayxNNGtfUNHC4+ZV2pjTGmlSzxtNHh46eoa1A74jHGmFayxNNGWUVOV2q7HYIxxrSOJZ42yj7mdKW2a3iMMaZ1LPG0UVZROYMT4+nTwwYHNcaY1rDE00bZxyoYZ0c7xhjTapZ42ijraDljbcQCY4xpNUs8bXC8ooaTp2ptqBxjjGkDSzxtUFVbz5XTkpk+oq/XoRhjTNTx5LYI0W5Yv5786QtneR2GMcZEJTviMcYY06Es8RhjjOlQlniMMcZ0KE8Sj4gMEJHXReSA+9w/wHLzRWSfiGSKyFKf+b8Skb0isl1EXhCRfj7v3e0uv09ELvOZP1NEdrjv/UFE7F4GxhjjAa+OeJYCa1U1FVjrTp9GRGKBB4DLgUnATSIyyX37dWCKqk4D9gN3u2UmAQuAycB84M/uegAeBBYDqe5jfniqZowxpjleJZ5rgOXu6+XAtX6WmQVkqmq2qtYAT7vlUNXXVLXOXW4DMMJnvU+rarWqfghkArNEJBnoo6rrVVWBFQG2aYwxJsy86k6dpKoFAKpaICJD/CwzHMjxmc4FZvtZ7jbgGZ8yG5qUGQ7Uuq+bzvdLRBbjHB2RlJRERkZGc3WJKOXl5VEVb3OsLqFl+3Vk6Ex1gbbVJ2yJR0TWAEP9vPXDYFfhZ5422cYPgTrgiRbKtLiu095QfQh4CCAtLU3T09ODCDcyZGRkEE3xNsfqElq2X0eGzlQXaFt9wpZ4VHVeoPdEpFBEkt2jnWSgyM9iucBIn+kRQL7POhYBVwIXu81nzZXJ5ePmuE+sqzmbN28+JiKHglk2QgwCjnkdRIh0hbqM7uhAwPZrj3WmuoD/+jS7X3vV1PYSsAhY5j6/6GeZjUCqiKQAeTidBr4ATm834C7gAlU91WS9T4rI/cAwnE4E76tqvYiUicgc4D3gFuCPwQSqqoPbUD/PiMgmVU3zOo5QsLqEj+3X3ulMdYG21cerzgXLgEtE5ABwiTuNiAwTkVUAbueBJcBqYA/wrKrucsv/CUgEXheRbSLyF7fMLuBZYDfwKvA1Va13y9wJPILT4SAL+HfYa2mMMeYT5ONWKtMZdKZfU1YX06gzfX6dqS4QXUc8Jnwe8jqAELK6mEad6fPrTHWBNtTHjniMMcZ0KDviMcYY06Es8RhjjOlQlniilIg8KiJFIrLTZ949IpLn9vTbJiJXeBljsERkpIi8KSJ7RGSXiHzTnR/UYLKRpJm6ROXfxgu2b0emUO7bdo4nSonI+UA5sEJVp7jz7gHKVfXXXsbWWu5FxMmqukVEEoHNOGPp3QocV9Vl7ujk/VX1Lu8ibVkzdbmBKPzbeMH27cgUyn3bjniilKq+DRz3Oo5QUNUCVd3ivi7DuW5rOMENJhtRmqmLCZLt25EplPu2JZ7OZ4l7n6JHo+HwvSkRGQN8CmeEidMGkwX8DSYbsZrUBaL8bxMBovrzs337Y5Z4OpcHgXHADKAA+I2n0bSSiPQGVgLfUtVSr+NpDz91ieq/TQSI6s/P9u3TWeLpRFS1UFXrVbUBeBjnnkZRQUS64+zMT6jq8+7sQrddubF92d9gshHHX12i+W8TCaL587N9+5Ms8XQijTuy67PAzkDLRhIREeBvwB5Vvd/nrcbBZCHwYLIRJVBdovVvEymi9fOzfTvAuqxXW3QSkaeAdJwhyQuBH7vTM3DuNXQQ+GpjO3IkE5HzgP8AO4AGd/YPcNqPnwVGAYeB61U1ok86N1OXm4jCv40XbN+OTKHcty3xGGOM6VDW1GaMMaZDWeIxxhjToSzxGGOM6VCWeIwxxnQoSzzGGGM6lCWeTkpEfisi3/KZXi0ij/hM/0ZEviMiV7uDFLZm3X8Xkc/7mT9HRN5zR6jd4w7sGDYicquI/Cmc2zCRx/bt6NfN6wBM2KwDrgd+JyIxONdE9PF5/xycIS/ew7mYLRSWAzeo6gciEgtMCNF6jfFl+3aUsyOezutdnC8gwGScq4nLRKS/iMQDZwJbfX9Zub/2/iAi60Qku/GXnzj+JCK7ReQVAg9oOARnrCbcITR2u+XvEZHHReQNce4/8pXGAiLyPRHZ6A4weK/P/C+KyPvuL8y/ul92RORLIrJfRN4Czg3dx2WiiO3bUc6OeDopVc0XkToRGYXzJV2PM4T5XKAE2K6qNc4oGKdJBs4DJuL8WnwOZxiMCcBUIAnYDTzqZ7O/BfaJSAbwKrBcVavc96YBc4BeOP8UXgGmAKk4YzsJ8JI492I5CtwInKuqtSLyZ+BmEXkduBeY6dbhTWBrmz8kE5Vs345+lng6t8ZfhucA9+N8Oc/B2bHXBSjzf+5gf7tFJMmddz7wlKrWA/ki8oa/gqr6ExF5ArgU+ALOUBrp7tsvqmolUCkib+J8Ic9zl238gvXG+bJOw/kCbnT/efTEGURxNpChqkcBROQZYHxrPhDTadi+HcUs8XRu63C+jFNxmiNygP8GSvH/qw6g2ue170/GoMZWUtUs4EEReRg4KiIDA5RXd/0/V9W/+r4hIl/H+UV5d5P51wYbh+n0bN+OYnaOp3N7F7gS5xa79e4ghP1wmiTWt2I9bwMLRCRWnJFoL/S3kIh8Rj5u30gF6oGT7vQ1ItLD/bKmAxuB1cBt4tzfAxEZLiJDgLXA593XjfenH40zsGK6iAwUZ3j261tRB9O52L4dxeyIp3PbgdPj58km83qr6rFWrOcF4CK37H7grQDLLQR+KyKngDrgZlWtd7+v7wOv4IzG+1NVzcdp2jgTWO8uUw58UVV3i8j/AK+J02upFviaqm4QpxvrepwTvVuA2FbUw3Qetm9HMRud2oSd+4UqV9Vfex2LMaFk+3bbWFObMcaYDmVHPMYYYzqUHfEYY4zpUJZ4jDHGdChLPMYYYzqUJR5jjDEdyhKPMcaYDvX/AcBmysyNKwZAAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -310,12 +334,13 @@ "source": [ "# Plot gain schedule\n", "fig, axs = plt.subplots(1, 2, sharey=True)\n", - "axs[0].plot(controller.v[len(controller.vs_gain_schedule.Kp):], controller.pc_gain_schedule.Kp)\n", + "axs[0].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Kp)\n", "axs[0].set_xlabel('Wind Speed')\n", "axs[0].set_ylabel('Proportional Gain')\n", "axs[0].grid('True')\n", "\n", - "axs[1].plot(controller.v[len(controller.vs_gain_schedule.Ki):], controller.pc_gain_schedule.Ki)\n", + "\n", + "axs[1].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Ki)\n", "axs[1].set_xlabel('Wind Speed')\n", "axs[1].set_ylabel('Integral Gain')\n", "axs[1].grid('True')\n" @@ -323,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": { "slideshow": { "slide_type": "slide" @@ -361,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": { "slideshow": { "slide_type": "subslide" @@ -379,7 +404,7 @@ }, { "data": { - "image/png": "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NpZWrVqxbdu2S/ZTVV588UWaNGlCly5dOHToEEeOHLlkvwULFrBgwQKaN29OixYt2L59O7t27brkWJ6u7nFf37p1a2rVqkVgYCD9+vXj999/T/e1AejTp0/K31u2bOHaa68lJCSEqVOnsnXr1ksez92OHTuoWbMm9erVA6B///4sX748Zfudd94JQMuWLdm3b1+6xzLGF2LizvPo1LUMm7KOSiULMWd4B0bedA2FgwJ9/th5/k5qdy/3aOSXxw0MDKRTp0506tSJkJAQJk2adMmHtqrStWtXvvnmm4vWnzt3jkcffZSwsDCqVavG6NGjOXfuXJoftgBFixala9euzJ49m+nTp6dZ4bZ48eLceeed3HnnnQQEBDB//nzuuuuui/aZOnUq0dHRrF27lqCgIIKDgz1e36+qvPDCCzz88MNpvg6NGjVi/fr1JCcnExDgfDdJTk5m48aNNGjQgMjIyEuek4ik+dpcUKxYsZS/BwwYwKxZs2jatCkTJ05k6dKlacZzIe70FCpUCHD+DRMTs34Q0Ji0qCpzNkYxes5WTp9P4h83XcPQ62oRFJh9FZKsFpOP7dix46Jv0hs2bKBGjRoAlChRglOnTgHQtm1bVqxYQUREBOBcZbRz586UD+Py5csTFxfH9987J1mlS5emVKlS/P7774DzQe5u8ODBPP7447Rq1YqyZcteEteKFSv4+++/AYiPjyc8PJwaNWpcFBM4Vx1VrFiRoKAglixZkvLNPfV+N910ExMmTCAuLg6AQ4cOcfSo+60tUKdOHZo3b87rr7+esu7111+nRYsW1KlTB4DVq1ezd+9ekpOTmTZtGh07dkzztfHk1KlTVK5cmYSEhItek9TxXlC/fn327duXcuyvvvoq5azMGH85evIcQ79ayxPfbqBGuWLMe7wjj3Wuk63JAfLZGYQ/xMXFMWLECI4fP06BAgWoU6cO48aNA2Do0KF0796dypUrs2TJEiZOnEi/fv04f/484Hx41qtXjyFDhhASEkJwcDCtWrVKOfaXX37JQw89RNGiRbnpppsuetyWLVtSsmRJBg4c6DGu3bt388gjj6CqJCcnc+utt3LXXXchInTo0IHGjRvTvXt3nnvuOXr06EFoaCjNmjWjfv36AJQrV+6i/d599122bdtGu3btAOfsZMqUKVSsWPGixx0/fjwjRoygTp06qCrt2rVj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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -421,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": { "slideshow": { "slide_type": "subslide" @@ -432,14 +457,121 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running simulation for NREL-5MW wind turbine.\n" + " \n", + "------------------------------------------------------------------------------\n", + "Running ROSCO-v2.5.0\n", + "A wind turbine controller framework for public use in the scientific field \n", + "Developed in collaboration: National Renewable Energy Laboratory \n", + " Delft University of Technology, The Netherlands \n", + "------------------------------------------------------------------------------\n", + "Generator speed: 9.5 RPM, Pitch angle: 0.0 deg, Power: 0.0 kW, Est. wind Speed: 10.0 m/s\n", + "Generator speed: 673.4 RPM, Pitch angle: 0.1 deg, Power: 340.7 kW, Est. wind Speed: 4.6 m/s\n", + "Generator speed: 751.3 RPM, Pitch angle: 0.1 deg, Power: 853.4 kW, Est. wind Speed: 7.1 m/s\n", + "Generator speed: 800.7 RPM, Pitch angle: 0.1 deg, Power: 1072.2 kW, Est. wind Speed: 6.8 m/s\n", + "Generator speed: 781.4 RPM, Pitch angle: 0.1 deg, Power: 1190.5 kW, Est. wind Speed: 6.8 m/s\n", + "Generator speed: 765.6 RPM, Pitch angle: 0.1 deg, Power: 1094.1 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 767.7 RPM, Pitch angle: 0.1 deg, Power: 1073.3 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 770.1 RPM, Pitch angle: 0.1 deg, Power: 1084.5 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 770.2 RPM, Pitch angle: 0.1 deg, Power: 1088.2 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 770.1 RPM, Pitch angle: 0.1 deg, Power: 1087.4 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 770.2 RPM, Pitch angle: 0.1 deg, Power: 1088.7 kW, Est. wind Speed: 6.9 m/s\n", + "Generator speed: 838.4 RPM, Pitch angle: 0.1 deg, Power: 1408.9 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 882.6 RPM, Pitch angle: 0.1 deg, Power: 1542.6 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 882.8 RPM, Pitch angle: 0.1 deg, Power: 1674.7 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 873.5 RPM, Pitch angle: 0.1 deg, Power: 1638.1 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 872.7 RPM, Pitch angle: 0.1 deg, Power: 1616.3 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 874.2 RPM, Pitch angle: 0.1 deg, Power: 1617.5 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 875.0 RPM, Pitch angle: 0.1 deg, Power: 1620.4 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 875.4 RPM, Pitch angle: 0.1 deg, Power: 1621.0 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 875.8 RPM, Pitch angle: 0.1 deg, Power: 1620.9 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 876.3 RPM, Pitch angle: 0.1 deg, Power: 1623.7 kW, Est. wind Speed: 7.8 m/s\n", + "Generator speed: 953.2 RPM, Pitch angle: 0.1 deg, Power: 2054.2 kW, Est. wind Speed: 8.8 m/s\n", + "Generator speed: 992.5 RPM, Pitch angle: 0.1 deg, Power: 2266.6 kW, Est. wind Speed: 8.7 m/s\n", + "Generator speed: 987.9 RPM, Pitch angle: 0.1 deg, Power: 2362.9 kW, Est. wind Speed: 8.7 m/s\n", + "Generator speed: 980.6 RPM, Pitch angle: 0.1 deg, Power: 2320.8 kW, Est. wind Speed: 8.7 m/s\n", + "Generator speed: 980.3 RPM, Pitch angle: 0.1 deg, Power: 2302.8 kW, Est. wind Speed: 8.7 m/s\n", + "Generator speed: 981.8 RPM, Pitch angle: 0.1 deg, Power: 2304.0 kW, Est. wind Speed: 8.7 m/s\n", + "Generator speed: 982.7 RPM, Pitch angle: 0.1 deg, Power: 2306.8 kW, Est. wind Speed: 8.8 m/s\n", + "Generator speed: 983.3 RPM, Pitch angle: 0.1 deg, Power: 2307.5 kW, Est. wind Speed: 8.8 m/s\n", + "Generator speed: 983.8 RPM, Pitch angle: 0.1 deg, Power: 2307.5 kW, Est. wind Speed: 8.8 m/s\n", + "Generator speed: 984.4 RPM, Pitch angle: 0.1 deg, Power: 2311.6 kW, Est. wind Speed: 8.8 m/s\n", + "Generator speed: 1069.7 RPM, Pitch angle: 0.1 deg, Power: 2876.5 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1102.9 RPM, Pitch angle: 0.1 deg, Power: 3163.7 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1095.1 RPM, Pitch angle: 0.1 deg, Power: 3221.5 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1088.9 RPM, Pitch angle: 0.1 deg, Power: 3176.5 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1089.1 RPM, Pitch angle: 0.1 deg, Power: 3160.3 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1090.5 RPM, Pitch angle: 0.1 deg, Power: 3162.0 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1091.5 RPM, Pitch angle: 0.1 deg, Power: 3164.7 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1092.1 RPM, Pitch angle: 0.1 deg, Power: 3165.4 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1092.7 RPM, Pitch angle: 0.1 deg, Power: 3165.5 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1093.4 RPM, Pitch angle: 0.1 deg, Power: 3171.1 kW, Est. wind Speed: 9.7 m/s\n", + "Generator speed: 1183.2 RPM, Pitch angle: 2.4 deg, Power: 4016.0 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1175.0 RPM, Pitch angle: 1.6 deg, Power: 4018.1 kW, Est. wind Speed: 10.6 m/s\n", + "Generator speed: 1172.9 RPM, Pitch angle: 1.5 deg, Power: 4066.9 kW, Est. wind Speed: 10.6 m/s\n", + "Generator speed: 1173.1 RPM, Pitch angle: 1.5 deg, Power: 4089.5 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.6 RPM, Pitch angle: 1.5 deg, Power: 4104.5 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 1.6 deg, Power: 4097.0 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 1.6 deg, Power: 4096.7 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.6 RPM, Pitch angle: 1.6 deg, Power: 4095.1 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.6 RPM, Pitch angle: 1.6 deg, Power: 4093.6 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 1.6 deg, Power: 4099.1 kW, Est. wind Speed: 10.7 m/s\n", + "Generator speed: 1174.1 RPM, Pitch angle: 2.6 deg, Power: 4772.0 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1174.7 RPM, Pitch angle: 3.1 deg, Power: 4862.6 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1174.0 RPM, Pitch angle: 3.1 deg, Power: 4886.5 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 3.0 deg, Power: 4885.5 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 3.0 deg, Power: 4882.0 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.5 RPM, Pitch angle: 3.0 deg, Power: 4879.1 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.5 RPM, Pitch angle: 3.0 deg, Power: 4875.4 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.5 RPM, Pitch angle: 3.1 deg, Power: 4872.5 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.5 RPM, Pitch angle: 3.1 deg, Power: 4870.1 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 3.1 deg, Power: 4876.1 kW, Est. wind Speed: 11.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5001.4 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 5.9 deg, Power: 5000.0 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.8 RPM, Pitch angle: 5.9 deg, Power: 5008.7 kW, Est. wind Speed: 12.6 m/s\n", + "Generator speed: 1173.9 RPM, Pitch angle: 8.1 deg, Power: 5000.9 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 8.1 deg, Power: 5000.0 kW, Est. wind Speed: 13.6 m/s\n", + "Generator speed: 1173.9 RPM, Pitch angle: 8.1 deg, Power: 5009.0 kW, Est. wind Speed: 13.7 m/s\n", + "Generator speed: 1173.9 RPM, Pitch angle: 10.0 deg, Power: 5001.1 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 10.0 deg, Power: 5000.0 kW, Est. wind Speed: 14.7 m/s\n", + "Generator speed: 1173.9 RPM, Pitch angle: 10.0 deg, Power: 5009.4 kW, Est. wind Speed: 14.7 m/s\n", + "Generator speed: 1173.8 RPM, Pitch angle: 11.6 deg, Power: 5001.6 kW, Est. wind Speed: 15.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.6 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Generator speed: 1173.7 RPM, Pitch angle: 11.6 deg, Power: 5000.0 kW, Est. wind Speed: 15.7 m/s\n", + "Shutting down ../ROSCO/build/libdiscon.dylib\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, "metadata": { @@ -450,7 +582,15 @@ ], "source": [ "# Specify controller dynamic library path and name\n", - "lib_name = ('../ROSCO/build/libdiscon.dylib')\n", + "\n", + "if platform.system() == 'Windows':\n", + " ext = 'dll'\n", + "elif platform.system() == 'Darwin':\n", + " ext = 'dylib'\n", + "else:\n", + " ext = 'so'\n", + " \n", + "lib_name = (f'../ROSCO/build/libdiscon.{ext}')\n", "\n", "# Load the simulator and controller interface\n", "controller_int = ROSCO_ci.ControllerInterface(lib_name)\n", @@ -501,7 +641,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 11, "metadata": { "slideshow": { "slide_type": "subslide" @@ -509,26 +649,55 @@ }, "outputs": [ { - "ename": "AssertionError", - "evalue": "File, ../Test_Cases/5MW_Step/5MW_Step.outb, does not exists", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;31m# Load output info and data\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mallinfo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malldata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload_fast_out\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilenames\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;31m# Define Plot cases\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Users/dzalkind/Tools/ROSCO_toolbox/ofTools/fast_io/output_processing.py\u001b[0m in \u001b[0;36mload_fast_out\u001b[0;34m(self, filenames, tmin, tmax, verbose)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfastout\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 70\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfilename\u001b[0m \u001b[0;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilenames\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 71\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misfile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"File, %s, does not exists\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mfilename\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 72\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilename\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'r'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 73\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mverbose\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAssertionError\u001b[0m: File, ../Test_Cases/5MW_Step/5MW_Step.outb, does not exists" - ] + "data": { + "image/png": "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csklo6Kd//DDD+dPmzZtysSJE6dOmzZtyqOPPprXtczJJ588fsKECdPat5ubm+XMM88cO2rUqOmHHXbY5DVr1gSiXfvqq68eNnjw4MMmT548ddy4cdP+9Kc/FQNcfvnlI3/60592rBRw/PHHT7joootGt29fccUVI771rW8NbY+9oKDg8OHDh8+YPHny1OOOO25i13v8+Mc/Luu8b/jw4TO2b9/uAxCRWeeee+6Y9mNtbW0UFRXNnD9//vjuvqcAL7/8cvbnP//5kbHKxHLTTTeVTJw4cerEiROnTpgwYdq9995b2NNrdXbSSSeN37NnT0quh3aoszY4Y7oYOXJk6+rVq1dGO/bXv/615Mgjj2y47777is8///yoqy8cfvjhzQ899NC6K664ovxgrt1u8ODBbY8//vi68vLytnfeeSfzzDPPnLhr167324/ffffdhTk5OeHO5/zud78rLSgoCG3evHn5woULi66++uoRjz/++Ppo1//yl7+886c//enOZcuWZRx77LFTP//5z1fPnTu34cEHHywCdoXDYaqrq30NDQ0dv7Tfeeed3BtvvHHLDTfcsB3g/PPPLz/rrLNqL7vssupYnyWarKysyJo1a7IaGhokNzdXH3744fyysrK2A5134oknNp144olNByoXzYcffui/4YYbhi5ZsmRVSUlJuLa21tOecHvrpZdeWtcX1zF9z2pwxsQpEonw2GOPFd1zzz0bX3nllfympqZoy4Zw5JFHtsycObO1p/eZO3duc3l5eRvArFmzWoLBoKe5uVkAamtrPb///e/Lrr/++u2dz3nssccKv/CFL+wFuOyyy6pff/31vEgksv/FO5kxY0ZrZmZmZM+ePd6TTz65YfHixbkAixcvzpo0aVJzTk5OePfu3d7m5mb58MMPM4877rgeJZdoTjnllNp//vOfhQD3339/8fnnn9+x2OaLL76YfcQRR0yeMmXK1COOOGLy0qVLM9zPmNdey7v66quHXXjhheVz5syZNGLEiBk///nPO2qff/jDH4pnzJgxxa0ljw6FQmzfvt2fk5MTKSgoCAMUFBREJk+eHARYsWJFxgknnDBh2rRpU2bNmjXpvffeywQnif/Xf/3XqKOPPnriiBEjZjz++OO5F154YfnYsWOnnX/++eXt9+tcOzWpxf5RTEr5zoNLR36woz77wCXjN3FIXtP/u2DmlgOXdGzZsiVj8uTJU9u3b7rpps0LFixoePbZZ3NHjhzZOm3atNajjz66/p///GfBpZdeWnMwsVRWVgamTJkyNTc3N/yzn/1s64IFCxpilb/77ruLpk6d2pSVlaUAV1999fCrrrpqZ25u7j7Za+fOnYExY8YEAfx+P7m5ueGdO3f6hg4dGop2XYBXX301e/To0S3Dhw8PAfh8Pl27dm3gpZdeyjnmmGMat27d6n/hhRdyi4qKQpMmTWrOzMzss3bSz372s1XXXXfd0Isuuqhm1apV2Zdffvne119/PRdg5syZLW+//fZqv9/Pv//977zvfve7I55++ukPu15j3bp1ma+//vqampoa75QpU6Z/5zvf2b1ixYqMBx98sHjRokWrMzIy9DOf+cyo2267reTLX/7y3tLS0raRI0fOmDt3bv0nP/nJ6ksuuaQW4Itf/OLohQsXbpoxY0brCy+8kPOVr3xl1JtvvvkBQG1tre+NN9744L777iu86KKLJrzwwgurZ82a1XzYYYdNef3117OOO+645r76npi+ZwnOmC66e4x47733Fl9wwQVVABdffHHVvffeW3IwCW7UqFFtGzZseH/IkCHhV155JfvCCy8cv3LlyuXFxcVRq1qLFi3K/PGPfzz8qaeeWgvw+uuvZ23YsCHjjjvu2NK1jS3apOkiEjUh3XbbbWX33HPPoMrKysBDDz20tn3/rFmzGl588cWcN954I/c73/nOzs2bNwdee+21nIKCgvCcOXNiJuJ47tt5/9FHH91cWVmZ8ec//7n41FNPre1crqqqynvRRReN2bhxY6aIaFtbW9Sa8umnn16TlZWlWVlZoeLi4rbKykrfU089lbd8+fLsmTNnTgFoaWnxDB48OOTz+Xj55ZfXvvTSS9nPPPNM/rXXXjty0aJFOdddd92O9957L/fCCy8c137dYDDYcb8zzzyzxuPxcOSRRzaVlJS0zZkzpxlg4sSJzR9++GGGJbjUZgnOpJSDqWn1p1AoxJNPPln07LPPFt54441DVZWamhpfdXW154orrhi1fPny7LKysmCs9hj3l3EY4IQTTmgaNWpU6/LlyzM3btwY+N///d9hAAsXLtx44oknNn344Yf+Cy64YPwdd9yxYdq0aa0Ar7zySu7y5cuzhw8fPiMUCklVVZVvzpw5k95+++01Q4YMCW7YsCEwbty4tra2NhoaGryDBw8Of/3rXx/+7LPPFgC0J+32Nri777678Iorrhhz2mmnLcvOztZjjz224fXXX89dvXp11lFHHdU8duzY4E033VSWm5sbvuyyy/Z097l++ctfDrr77rsHATz11FNrS0pKQtu3b98nATc2NnpLS0v3aTdcsGBBzXXXXTfymWeeWbNr166O30XXXHPN8JNOOqn+2Wef/XDNmjWBk08+eRJRZGRkdCRMr9dLKBQSVZULL7xw76233rq1a3mPx8P8+fOb5s+f3/Txj3+87otf/GL5D3/4wx15eXmh7tpF22utXq+XQCCgna8VCoWiJl6TOqwNzpg4PPLII/mTJ09u2rFjx/tbt25dtm3btmULFiyovu+++woffPDBjatXr155oM4G27Zt84VCzhPDlStXBjZu3JgxadKk1s997nM1q1evXrl69eqVJ554YtOePXu8Z5xxxoTrr7++8vTTT29sP/+aa67ZvWvXrve3bt267OWXX15dXl7e+vbbb68Bp6Zx5513lgDcddddRccee2y9x+Ph5ptv3tp+7a7xXHrppTUzZsxovPXWW0sATjrppIbnnnuusLCwMOzz+SgrKwvX1dV533vvvdz58+c3dj2/3fe+973d7fcoLy9vO+WUUxqefvrpgurqag84nWImT57c5PPt+/f0V77ylT3f+ta3trXXitrV1dV5R4wYEQT405/+VBrre9rVggUL6h577LGirVu3+gB27tzp/eCDDwIbN270v/rqqx2PvhctWpQ9fPjwYHFxcWTEiBHBO++8swicdtY33ngj62DuaVKXJThjumhvg2t//fznPx983333FX/iE5+o6Vzu/PPPr/773/9e0vX8e+65p7CsrOywJUuW5Jx33nkTjj/++AkAzzzzTO7kyZOnTZo0aeoFF1ww7qabbtpUVlYW7nr+b37zm8GbN2/O+NWvfjWsPYb2X9jdueqqq/ZUV1f7Ro0aNf3mm28e8tvf/rYyns96/fXXb7/11luHhMNh5syZ01xTU+ObPXt2x+PIyZMnN+fm5oZjteV1dfTRRzdfccUVu4455pjJkydPnrpw4cJBd95558au5caNG9f2ox/9aL+prK655pod119//Ygjjzxycji837cnplmzZrX88Ic/3HrKKadMnDhx4tSTTz554pYtW/zBYFC+/e1vjxgzZsy0yZMnT33wwQeLbrnlli0A999///q77rqrdNKkSVMnTJgw7aGHHio8qJualGULnpqkW7p06caZM2d2+wjMmHS0dOnS0pkzZ5YnO45DmdXgjDHGDEiW4IwxxgxIluCMMcYMSJbgTCqIRCIR63JtBgz35zn2VDIm4SzBmVSwfPfu3QWW5MxA4K4HVwAsT3Yshzob6G2SLhQKfXHHjh2379ixw1b0NgNBx4reyQ7kUGfDBIwxxgxI9teyMcaYAckSnDHGmAHJEpwxxpgByRKcMcaYAckSnDHGmAHp/wMk2t7+Z9XjYwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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6/TnzjvvTJ82bdrMadOmzZw6deqsRx55JGWwdXV1+umnT6muro7Za6Cpw/QYnPrEGz9+fPv27du3RVv28MMPpy9YsMD36KOPpl100UVRr8Ywf/781ieffHL3tddeW3g0dXfIysoKPPfcc7sLCwsDH3zwQdx55503rbKyclPH8gcffDDF4/GEum7zm9/8JiM5OTl44MCBLffcc0/qDTfckP/cc8/tjVb/ddddV/HTn/60YvPmze5TTjll5lVXXVW3ZMkS3xNPPJEKVIZCIerq6hw+n69zp/3BBx9477jjjoO33357GcBFF11UeP755zd85StfqevvtUQTHx8f3rFjR7zP5xOv12ueeuqppOzs7MCRtlu6dGnL0qVLW460XjR79uxx3n777bkbNmz4OD09PdTQ0GDrSLhD9cYbb+w+FvWo4actOKX6EA6HWbVqVepDDz1U/NZbbyW1tLREu1QICxYsaJs3b177YJ9nyZIlrYWFhQGAhQsXtvn9fltra6sANDQ02H77299m33bbbWVdt1m1alXKV7/61RqAr3zlK3Vr165NDIfDvSvvYs6cOe1xcXHh6upq+4oVK3zr16/3Aqxfvz5++vTprR6PJ1RVVWVvbW2VPXv2xJ166qmDSi7RnHHGGQ3/+Mc/UgAee+yxtIsuuqjzApuvv/56wgknnFA0Y8aMmSeccELRxo0b3ZHXmNjRyrvhhhvGXXLJJYWLFy+enp+fP+dnP/tZZ+vz7rvvTpszZ86MSCt5QjAYpKyszOnxeMLJyckhgOTk5HBRUZEfYOvWre5PfepTU2fNmjVj4cKF0z/66KM4sJL4F77whYKTTjppWn5+/pznnnvOe8kllxROmjRp1kUXXVTY8XxdW6cqtuk/SY2qG5/YOH5neVPCkdccuGk5iS2/unjewSOvaTl48KC7qKhoZsfjO++888DKlSt9r7zyinf8+PHts2bNaj/ppJOa/vGPfyRfeeWV9UcTS0lJiWvGjBkzvV5v6L/+678OrVy50tff+g8++GDqzJkzW+Lj4w3ADTfckHf99ddXeL3ebtmroqLCNXHiRD+A0+nE6/WGKioqHLm5ucFo9QKsWbMmYcKECW15eXlBAIfDYXbt2uV64403PCeffHLzoUOHnK+99po3NTU1OH369Na4uLhjdpz0S1/6Uu2tt96ae9lll9V//PHHCVdffXXN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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "([
,
],\n", + " [array([,\n", + " ,\n", + " ,\n", + " ], dtype=object),\n", + " array([,\n", + " ,\n", + " ], dtype=object)])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "op = output_processing()\n", "\n", - "# Define openfast output filenames\n", - "filenames = [\"../Test_Cases/5MW_Step/5MW_Step.outb\"]\n", - "\n", + "# Define openfast output filenames, please fill in your own .outb\n", + "# filenames = [\"../Test_Cases/5MW_Step/5MW_Step.outb\"]\n", + "filenames = ['../Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi.outb']\n", "# Load output info and data\n", - "allinfo, alldata = op.load_fast_out(filenames)\n", + "fast_out = op.load_fast_out(filenames)\n", "\n", "# Define Plot cases \n", "cases = {}\n", @@ -536,7 +705,7 @@ "cases['Rotor Performance'] = ['RtVAvgxh', 'RtTSR', 'RtAeroCp']\n", "\n", "# Plot, woohoo!\n", - "fast_io.plot_fast_out(cases, allinfo, alldata)" + "op.plot_fast_out(fast_out, cases)" ] }, { @@ -559,47 +728,22 @@ "slide_type": "subslide" } }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "# Define openfast output filenames\n", - "filenames = [\"../Test_Cases/5MW_Turb_NR/5MW_Turb_NR.out\", # Note that we can load txt or binary outputs\n", - " \"../Test_Cases/5MW_Turb_NR_ps/5MW_Turb_NR_ps.outb\"]\n", + "# # Define openfast output filenames\n", + "# filenames = [\"../Test_Cases/5MW_Turb_NR/5MW_Turb_NR.out\", # Note that we can load txt or binary outputs\n", + "# \"../Test_Cases/5MW_Turb_NR_ps/5MW_Turb_NR_ps.outb\"]\n", "\n", - "# Load output info and data\n", - "allinfo, alldata = fast_io.load_output(filenames)\n", + "# # Load output info and data\n", + "# fast_out = op.load_fast_out(filenames)\n", "\n", - "# Define Plot cases \n", - "cases = {}\n", - "cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed']\n", - "cases['Peak Shaving'] = ['Wind1VelX', 'BldPitch1', 'TwrBsMyt']\n", + "# # Define Plot cases \n", + "# cases = {}\n", + "# cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed']\n", + "# cases['Peak Shaving'] = ['Wind1VelX', 'BldPitch1', 'TwrBsMyt']\n", "\n", - "# Plot, woohoo!\n", - "fast_io.plot_fast_out(cases, allinfo, alldata)" + "# # Plot, woohoo!\n", + "# op.plot_fast_out(fast_out, cases)" ] }, { @@ -629,9 +773,9 @@ "metadata": { "celltoolbar": "Slideshow", "kernelspec": { - "display_name": "test-env", + "display_name": "rosco-env", "language": "python", - "name": "test-env" + "name": "rosco-env" }, "language_info": { "codemirror_mode": { @@ -643,9 +787,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.8.13" } }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/Examples/example_01.py b/Examples/example_01.py index 6c4c3e39..14cf3bbd 100644 --- a/Examples/example_01.py +++ b/Examples/example_01.py @@ -34,7 +34,7 @@ path_params['FAST_InputFile'], os.path.join(tune_dir,path_params['FAST_directory']), dev_branch=True, - rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) ) # Print some basic turbine info diff --git a/Examples/example_04.py b/Examples/example_04.py index 229c176b..69da8392 100644 --- a/Examples/example_04.py +++ b/Examples/example_04.py @@ -34,11 +34,12 @@ controller = ROSCO_controller.Controller(controller_params) # Load turbine data from OpenFAST and rotor performance text file +cp_filename = os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) turbine.load_from_fast( path_params['FAST_InputFile'], os.path.join(tune_dir,path_params['FAST_directory']), dev_branch=True, - rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + rot_source='txt',txt_filename= cp_filename ) # Tune controller @@ -46,7 +47,10 @@ # Write parameter input file param_file = os.path.join(this_dir,'DISCON.IN') -write_DISCON(turbine,controller,param_file=param_file, txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename'])) +write_DISCON(turbine,controller, +param_file=param_file, +txt_filename=cp_filename +) # Plot gain schedule fig, ax = plt.subplots(2,2,constrained_layout=True,sharex=True) diff --git a/Examples/example_05.py b/Examples/example_05.py index deb50bd0..9555aeb3 100644 --- a/Examples/example_05.py +++ b/Examples/example_05.py @@ -55,11 +55,12 @@ # controller = ROSCO_controller.Controller(controller_params) # Load turbine data from OpenFAST and rotor performance text file +cp_filename = os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) turbine.load_from_fast( path_params['FAST_InputFile'], os.path.join(tune_dir,path_params['FAST_directory']), dev_branch=True, - rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + rot_source='txt',txt_filename=cp_filename ) # Tune controller @@ -68,7 +69,11 @@ # Write parameter input file param_filename = os.path.join(this_dir,'DISCON.IN') -write_DISCON(turbine,controller,param_file=param_filename, txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename'])) +write_DISCON( + turbine,controller, + param_file=param_filename, + txt_filename=cp_filename + ) # Load controller library diff --git a/Examples/example_07.py b/Examples/example_07.py index 4626e794..3fc16f85 100644 --- a/Examples/example_07.py +++ b/Examples/example_07.py @@ -45,7 +45,7 @@ path_params['FAST_InputFile'], os.path.join(tune_dir,path_params['FAST_directory']), dev_branch=True, - rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) ) # Tune controller controller.tune_controller(turbine) diff --git a/Examples/example_10.py b/Examples/example_10.py index c710711f..7a7b0693 100644 --- a/Examples/example_10.py +++ b/Examples/example_10.py @@ -34,7 +34,6 @@ # Load turbine data from openfast model turbine = ROSCO_turbine.Turbine(turbine_params) -# turbine.load_from_fast(path_params['FAST_InputFile'],path_params['FAST_directory'],dev_branch=True,rot_source='txt',txt_filename=path_params['rotor_performance_filename']) turbine.load_from_fast(path_params['FAST_InputFile'], \ os.path.join(this_dir,path_params['FAST_directory']),dev_branch=True) diff --git a/Examples/example_11.py b/Examples/example_11.py index f8a9b199..6b1e9680 100644 --- a/Examples/example_11.py +++ b/Examples/example_11.py @@ -46,7 +46,7 @@ os.path.join(this_dir,path_params['FAST_directory']), dev_branch=True, rot_source='txt', - txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + txt_filename=os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) ) # Tune controller diff --git a/Examples/example_12.py b/Examples/example_12.py index 4f421af6..9d8c593d 100644 --- a/Examples/example_12.py +++ b/Examples/example_12.py @@ -17,7 +17,8 @@ import numpy as np import matplotlib.pyplot as plt from ROSCO_toolbox.inputs.validation import load_rosco_yaml -from ROSCO_toolbox.linear.robust_scheduling import rsched_driver +from ROSCO_toolbox.linear.robust_scheduling import rsched_driver, load_linturb +from ROSCO_toolbox.linear.lin_vis import lin_plotting from ROSCO_toolbox import turbine as ROSCO_turbine from ROSCO_toolbox import controller as ROSCO_controller @@ -123,6 +124,28 @@ def run_example(): if False: plt.show() else: - plt.savefig(os.path.join(example_out_dir, '12_RobustSched.png')) + fig.savefig(os.path.join(example_out_dir, '12_RobustSched.png')) + + # ---- Plot nyquist ---- + # Re-load and trimlinturb for plotting + linturb = load_linturb(options['linturb_options']['linfile_path'], load_parallel=True) # + linturb.trim_system(desInputs=['collective'], desOutputs=['RtSpeed']) + + # Plotting parameters + u = 12 + omega = 0.1 + k_float = 0.0 + controller.zeta_pc = controller.zeta_pc[0] + + # plot + lv = lin_plotting(controller, turbine, linturb) + xlim=ylim=[-2,2] + lv.plot_nyquist(u, omega, k_float=k_float, xlim=xlim, ylim=ylim) + + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir, '12_Nyquist.png')) + if __name__ == '__main__': run_example() diff --git a/Examples/example_13.py b/Examples/example_13.py index 9b0b7acd..b326491d 100644 --- a/Examples/example_13.py +++ b/Examples/example_13.py @@ -10,8 +10,7 @@ ''' # Python Modules -import yaml, os, platform -import numpy as np +import os, platform import matplotlib.pyplot as plt # ROSCO toolbox modules @@ -85,7 +84,7 @@ channels['BldPitch3'] = True # Run FAST cases -fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST',dev_branch = True) +fastBatch = runFAST_pywrapper_batch() fastBatch.FAST_directory = os.path.realpath(os.path.join(rosco_dir,'Tune_Cases',path_params['FAST_directory'])) fastBatch.FAST_InputFile = path_params['FAST_InputFile'] diff --git a/Examples/example_14.py b/Examples/example_14.py index f91d4de5..2b14f249 100644 --- a/Examples/example_14.py +++ b/Examples/example_14.py @@ -122,7 +122,7 @@ channels = set_channels() # Run FAST cases -fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST',dev_branch = True) +fastBatch = runFAST_pywrapper_batch() fastBatch.FAST_directory = os.path.realpath(os.path.join(rosco_dir,'Tune_Cases',path_params['FAST_directory'])) fastBatch.FAST_InputFile = path_params['FAST_InputFile'] diff --git a/Examples/example_15.py b/Examples/example_15.py new file mode 100644 index 00000000..baa29c8a --- /dev/null +++ b/Examples/example_15.py @@ -0,0 +1,45 @@ +''' +----------- Example_15 -------------- +Use the runFAST scripts to set up an example, use pass through in yaml +------------------------------------- + +In this example: + - use run_FAST_ROSCO class to set up a test case + +''' + +import os +from ROSCO_toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO +from ROSCO_toolbox.ofTools.case_gen import CaseLibrary as cl + + +#directories +this_dir = os.path.dirname(os.path.abspath(__file__)) +rosco_dir = os.path.dirname(this_dir) +example_out_dir = os.path.join(this_dir,'examples_out') +os.makedirs(example_out_dir,exist_ok=True) + + +def main(): + # Simulation config + r = run_FAST_ROSCO() + + parameter_filename = os.path.join(rosco_dir,'Tune_Cases/NREL5MW_PassThrough.yaml') + run_dir = os.path.join(example_out_dir,'15_PassThrough') + os.makedirs(run_dir,exist_ok=True) + + # Step wind simulation + r.tuning_yaml = parameter_filename + r.wind_case_fcn = cl.simp_step + r.wind_case_opts = { + 'U_start': [10], + 'U_end': [15], + 'wind_dir': run_dir + } + r.save_dir = run_dir + + r.run_FAST() + + +if __name__=="__main__": + main() \ No newline at end of file diff --git a/Examples/example_16.py b/Examples/example_16.py new file mode 100644 index 00000000..96268013 --- /dev/null +++ b/Examples/example_16.py @@ -0,0 +1,67 @@ +''' +----------- Example_16 -------------- +Run openfast with ROSCO and external control interface +------------------------------------- + +IEA-15MW will call NREL-5MW controller and read control inputs + +''' + +import os, platform +from ROSCO_toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO +from ROSCO_toolbox.ofTools.case_gen import CaseLibrary as cl +import shutil + + +#directories +this_dir = os.path.dirname(os.path.abspath(__file__)) +rosco_dir = os.path.dirname(this_dir) +example_out_dir = os.path.join(this_dir,'examples_out') +os.makedirs(example_out_dir,exist_ok=True) + +if platform.system() == 'Windows': + lib_name = os.path.realpath(os.path.join(this_dir, '../ROSCO/build/libdiscon.dll')) +elif platform.system() == 'Darwin': + lib_name = os.path.realpath(os.path.join(this_dir, '../ROSCO/build/libdiscon.dylib')) +else: + lib_name = os.path.realpath(os.path.join(this_dir, '../ROSCO/build/libdiscon.so')) + + +def main(): + + # Make copy of libdiscon + ext = lib_name.split('.')[-1] + copy_lib = os.path.join(os.path.split(lib_name)[0],f"libdiscon_copy.{ext}") + shutil.copyfile(lib_name, copy_lib) + + + # Ensure external control paths are okay + parameter_filename = os.path.join(rosco_dir,'Tune_Cases/IEA15MW_ExtInterface.yaml') + run_dir = os.path.join(example_out_dir,'16_ExtInterface') + os.makedirs(run_dir,exist_ok=True) + + # Set DLL file and DISCON input dynamically (hard-coded in yaml) + controller_params = {} + controller_params['DISCON'] = {} + controller_params['OL_Mode'] = 2 + controller_params['DISCON']['DLL_FileName'] = copy_lib + controller_params['DISCON']['DLL_InFile'] = os.path.join(rosco_dir,'Test_Cases/NREL-5MW/DISCON.IN') + controller_params['DISCON']['DLL_ProcName'] = 'DISCON' + + # RAAW FAD set up + r = run_FAST_ROSCO() + r.tuning_yaml = parameter_filename + r.wind_case_fcn = cl.simp_step + r.wind_case_opts = { + 'U_start': [10], + 'U_end': [15], + 'wind_dir': run_dir + } + r.controller_params = controller_params + r.save_dir = run_dir + + r.run_FAST() + + +if __name__=="__main__": + main() \ No newline at end of file diff --git a/Examples/example_17.py b/Examples/example_17.py new file mode 100644 index 00000000..c77e8cc6 --- /dev/null +++ b/Examples/example_17.py @@ -0,0 +1,134 @@ +''' +----------- Example_17 -------------- +Run ROSCO using the ROSCO toolbox control interface and execute communication with ZeroMQ +------------------------------------- + +A demonstrator for ZeroMQ communication. Instead of using ROSCO with with control interface, +one could call ROSCO from OpenFAST, and communicate with ZeroMQ through that. +''' + + +import platform +import os +import matplotlib.pyplot as plt +from ROSCO_toolbox.inputs.validation import load_rosco_yaml +from ROSCO_toolbox.utilities import write_DISCON +from ROSCO_toolbox import control_interface as ROSCO_ci +from ROSCO_toolbox.control_interface import turbine_zmq_server +from ROSCO_toolbox import sim as ROSCO_sim +from ROSCO_toolbox import turbine as ROSCO_turbine +from ROSCO_toolbox import controller as ROSCO_controller +import numpy as np +import multiprocessing as mp + +def run_zmq(): + connect_zmq = True + s = turbine_zmq_server(network_address="tcp://*:5555", timeout=10.0, verbose=True) + while connect_zmq: + # Get latest measurements from ROSCO + measurements = s.get_measurements() + + # Decide new control input based on measurements + current_time = measurements['Time'] + if current_time <= 10.0: + yaw_setpoint = 0.0 + else: + yaw_setpoint = 20.0 + + # Send new setpoints back to ROSCO + s.send_setpoints(nacelleHeading=yaw_setpoint) + + if measurements['iStatus'] == -1: + connect_zmq = False + s._disconnect() + + +def sim_rosco(): + # Load yaml file + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir, '../Tune_Cases') + parameter_filename = os.path.join(tune_dir, 'NREL5MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Enable ZeroMQ & yaw control + controller_params['Y_ControlMode'] = 1 + controller_params['ZMQ_Mode'] = 1 + + # Specify controller dynamic library path and name + this_dir = os.path.dirname(os.path.abspath(__file__)) + example_out_dir = os.path.join(this_dir, 'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) + + if platform.system() == 'Windows': + lib_name = os.path.join(this_dir, '../ROSCO/build/libdiscon.dll') + elif platform.system() == 'Darwin': + lib_name = os.path.join(this_dir, '../ROSCO/build/libdiscon.dylib') + else: + lib_name = os.path.join(this_dir, '../ROSCO/build/libdiscon.so') + + # # Load turbine model from saved pickle + turbine = ROSCO_turbine.Turbine + turbine = turbine.load(os.path.join(example_out_dir, '01_NREL5MW_saved.p')) + + # Load turbine data from OpenFAST and rotor performance text file + cp_filename = os.path.join( + tune_dir, path_params['FAST_directory'], path_params['rotor_performance_filename']) + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir, path_params['FAST_directory']), + dev_branch=True, + rot_source='txt', txt_filename=cp_filename + ) + + # Tune controller + controller = ROSCO_controller.Controller(controller_params) + controller.tune_controller(turbine) + + # Write parameter input file + param_filename = os.path.join(this_dir, 'DISCON_zmq.IN') + write_DISCON( + turbine, controller, + param_file=param_filename, + txt_filename=cp_filename + ) + + + # Load controller library + controller_int = ROSCO_ci.ControllerInterface(lib_name, param_filename=param_filename, sim_name='sim-zmq') + + # Load the simulator + sim = ROSCO_sim.Sim(turbine, controller_int) + + # Define a wind speed history + dt = 0.025 + tlen = 100 # length of time to simulate (s) + ws0 = 7 # initial wind speed (m/s) + t = np.arange(0, tlen, dt) + ws = np.ones_like(t) * ws0 + # add steps at every 100s + for i in range(len(t)): + ws[i] = ws[i] + t[i]//100 + + # Define wind directions as zeros + wd = np.zeros_like(t) + + # Run simulator and plot results + sim.sim_ws_wd_series(t, ws, wd, rotor_rpm_init=4, make_plots=True) + + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir, '16_NREL5MW_zmqYaw.png')) + + +if __name__ == "__main__": + p1 = mp.Process(target=run_zmq) + p1.start() + p2 = mp.Process(target=sim_rosco) + p2.start() + p1.join() + p2.join() diff --git a/Examples/run_examples.py b/Examples/run_examples.py index 181f4239..56b47208 100644 --- a/Examples/run_examples.py +++ b/Examples/run_examples.py @@ -1,8 +1,7 @@ import os import unittest -import sys from time import time -import importlib +import runpy all_scripts = [ 'example_01', @@ -18,31 +17,27 @@ 'example_11', 'example_12', 'example_13', - 'example_14' + 'example_14', + 'example_15', + 'example_16', + 'example_17', # NJA: only runs on unix in CI ] def execute_script(fscript): - examples_dir = os.path.dirname(os.path.realpath(__file__)) + examples_dir = os.path.dirname(os.path.realpath(__file__)) - # Go to location due to relative path use for airfoil files - print("\n\n") - print("NOW RUNNING:", fscript) - print() - fullpath = os.path.join(examples_dir, fscript + ".py") - basepath = os.path.dirname(os.path.realpath(fullpath)) - os.chdir(basepath) + # Go to location due to relative path use for airfoil files + print("\n\n") + print("NOW RUNNING:", fscript) + print() + fullpath = os.path.join(examples_dir, fscript + ".py") + basepath = os.path.dirname(os.path.realpath(fullpath)) + os.chdir(basepath) - # Get script/module name - froot = fscript.split("/")[-1] - - # Use dynamic import capabilities - # https://www.blog.pythonlibrary.org/2016/05/27/python-201-an-intro-to-importlib/ - print(froot, os.path.realpath(fullpath)) - spec = importlib.util.spec_from_file_location(froot, os.path.realpath(fullpath)) - mod = importlib.util.module_from_spec(spec) - s = time() - spec.loader.exec_module(mod) - print(time() - s, "seconds to run") + # Use runpy to execute examples + s = time() + runpy.run_path(os.path.realpath(fullpath), run_name='__main__') + print(time() - s, "seconds to run") class TestExamples(unittest.TestCase): diff --git a/ROSCO/CMakeLists.txt b/ROSCO/CMakeLists.txt index 0101971c..3a97a216 100644 --- a/ROSCO/CMakeLists.txt +++ b/ROSCO/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.6) -project(ROSCO VERSION 2.5.0 LANGUAGES Fortran) +project(ROSCO VERSION 2.6.0 LANGUAGES Fortran C) set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/ftnmods") @@ -10,20 +10,26 @@ endif() message(STATUS "CMAKE_Fortran_COMPILER_ID = ${CMAKE_Fortran_COMPILER_ID}") if(APPLE OR UNIX) - # Enable .dll export - if (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel") - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -DIMPLICIT_DLLEXPORT -r8 -double-size 64 -cpp -no-wrap-margin") - else() - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -DIMPLICIT_DLLEXPORT -ffree-line-length-0 -fdefault-real-8 -fdefault-double-8 -cpp") - endif() +# Enable .dll export +if (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel") +set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -DIMPLICIT_DLLEXPORT -r8 -double-size 64 -cpp -no-wrap-margin") +else() +set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -DIMPLICIT_DLLEXPORT -ffree-line-length-0 -fdefault-real-8 -fdefault-double-8 -cpp") +endif() elseif (WIN32) - if (CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - # Ensure static linking to avoid requiring Fortran runtime dependencies - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -ffree-line-length-0 -static-libgcc -static-libgfortran -static -fdefault-real-8 -fdefault-double-8 -cpp") - elseif (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel") - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -libs:static -free -static -fpp -real-size:64 -double-size:64") +if (CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") +# Ensure static linking to avoid requiring Fortran runtime dependencies +set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -ffree-line-length-0 -static-libgcc -static-libgfortran -static -fdefault-real-8 -fdefault-double-8 -cpp") +elseif (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel") +set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -libs:static -free -static -fpp -real-size:64 -double-size:64") # set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} /ffree-line-length-0 /static-libgcc /static-libgfortran /static /fdefault-real-8 /fdefault-double-8 /cpp") - endif() +endif() +endif() + +# Enable ZMQ_Client if compiler flag is set +option(ZMQ_CLIENT "Enable use of ZeroMQ client" off) +if(ZMQ_Client) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -lzmq") endif() @@ -36,7 +42,10 @@ set(SOURCES src/Filters.f90 src/Functions.f90 src/ReadSetParameters.f90 + src/ROSCO_Helpers.f90 src/ROSCO_IO.f90 + src/ZeroMQInterface.f90 + src/ExtControl.f90 ) if (${CMAKE_Fortran_COMPILER_ID} STREQUAL "GNU") @@ -61,8 +70,39 @@ else (NWTC_SYS_FILE) endif (NWTC_SYS_FILE) # Library +if(ZMQ_CLIENT) + # Find ZeroMQ installation + find_package(PkgConfig) + pkg_check_modules(PC_ZeroMQ libzmq) + find_path(ZeroMQ_INCLUDE_DIR + NAMES zmq.h + PATHS ${PC_ZeroMQ_INCLUDE_DIRS} + ) + find_library(ZeroMQ_LIBRARY + NAMES zmq + PATHS ${PC_ZeroMQ_LIBRARY_DIRS} + ) + include_directories(${ZeroMQ_INCLUDE_DIR}) + + + # Compile C-based ZeroMQ client as object library + add_compile_options(-I${ZeroMQ_INCLUDE_DIR} -l${ZeroMQ_LIBRARY} -fPIC) + add_library(zmq_client OBJECT ../src/zmq_client.c) + + # Add definition + add_definitions(-DZMQ_CLIENT="TRUE") + set(SOURCES ${SOURCES} + $) +endif() + add_library(discon SHARED ${SOURCES}) +if(ZMQ_CLIENT) + target_include_directories(discon PUBLIC ${ZeroMQ_INCLUDE_DIR}) + target_link_libraries(discon PUBLIC ${ZeroMQ_LIBRARY}) +endif() + + install(TARGETS discon EXPORT "${CMAKE_PROJECT_NAME}Libraries" RUNTIME DESTINATION lib @@ -71,4 +111,4 @@ install(TARGETS discon ) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/install") -endif() \ No newline at end of file +endif() diff --git a/ROSCO/rosco_registry/rosco_types.yaml b/ROSCO/rosco_registry/rosco_types.yaml index 81866dee..ea78a05c 100644 --- a/ROSCO/rosco_registry/rosco_types.yaml +++ b/ROSCO/rosco_registry/rosco_types.yaml @@ -30,6 +30,11 @@ default_types: description: size: 0 equals: + c_float: &c_float + type: c_float + description: + size: 0 + equals: c_pointer: &c_pointer type: c_pointer description: @@ -91,12 +96,18 @@ ControlParameters: F_FlHighPassFreq: <<: *real description: Natural frequency of first-roder high-pass filter for nacelle fore-aft motion [rad/s]. + F_YawErr: + <<: *real + description: Corner low pass filter corner frequency for yaw controller [rad/s]. F_FlpCornerFreq: <<: *real description: Corner frequency (-3dB point) in the second order low pass filter of the blade root bending moment for flap control [rad/s]. allocatable: True # Tower fore-aft damping + TD_Mode: + <<: *integer + description: Tower damper mode (0- no tower damper, 1- feed back translational nacelle accelleration to pitch angle FA_HPFCornerFreq: <<: *real description: Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] @@ -111,6 +122,10 @@ ControlParameters: IPC_ControlMode: <<: *integer description: Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0 - off, 1 - 1P reductions, 2 - 1P+2P reductions} + IPC_Vramp: + <<: *real + description: Wind speeds for IPC cut-in sigma function [m/s] + allocatable: True IPC_IntSat: <<: *real description: Integrator saturation (maximum signal amplitude contrbution to pitch from IPC) @@ -288,43 +303,29 @@ ControlParameters: # Yaw Controller Y_ControlMode: <<: *integer - description: Yaw control mode {0 - no yaw control, 1 - yaw rate control, 2 - yaw-by-IPC} + description: Yaw control mode {0 - no yaw control, 1 - yaw rate control} + Y_uSwitch: + <<: *real + description: Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] Y_ErrThresh: <<: *real - description: Error threshold [rad]. Turbine begins to yaw when it passes this. (104.71975512) -- 1.745329252 + description: Error threshold [rad]. Turbine begins to yaw when it passes this + allocatable: True + Y_Rate: + <<: *real + description: Yaw rate [rad/s] + Y_MErrSet: + <<: *real + description: Yaw alignment error, setpoint (for wake steering) [rad] Y_IPC_IntSat: <<: *real description: Integrator saturation (maximum signal amplitude contrbution to pitch from yaw-by-IPC) - Y_IPC_n: - <<: *integer - description: Number of controller gains (yaw-by-IPC) Y_IPC_KP: <<: *real description: Yaw-by-IPC proportional controller gain Kp - allocatable: True Y_IPC_KI: <<: *real description: Yaw-by-IPC integral controller gain Ki - allocatable: True - Y_IPC_omegaLP: - <<: *real - description: Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. - Y_IPC_zetaLP: - <<: *real - description: Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. - Y_MErrSet: - <<: *real - description: Yaw alignment error, setpoint [rad] - Y_omegaLPFast: - <<: *real - description: Corner frequency fast low pass filter, 1.0 [Hz] - Y_omegaLPSlow: - <<: *real - description: Corner frequency slow low pass filter, 1/60 [Hz] - Y_Rate: - <<: *real - description: Yaw rate [rad/s] - # Pitch Saturation PS_Mode: <<: *integer @@ -418,7 +419,47 @@ ControlParameters: allocatable: True dimension: (:,:) description: Open loop channels in timeseries + + # Pitch actuator + PA_Mode: + <<: *integer + description: Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} + PA_CornerFreq: + <<: *real + description: Pitch actuator bandwidth/cut-off frequency [rad/s] + PA_Damping: + <<: *real + description: Pitch actuator damping ratio [-, unused if PA_Mode = 1] + + # External Control + Ext_Mode: + <<: *integer + description: External control mode (0 - not used, 1 - call external control library) + DLL_FileName: + <<: *character + description: File name of external dynamic library + length: 1024 + DLL_InFile: + <<: *character + description: Name of input file called by dynamic library (DISCON.IN, e.g.) + length: 1024 + DLL_ProcName: + <<: *character + description: Process name of subprocess called in DLL_Filename (Usually DISCON) + length: 1024 + # ZeroMQ + ZMQ_Mode: + <<: *integer + description: Flag for ZeroMQ (0-off, 1-yaw} + ZMQ_CommAddress: + <<: *character + length: 256 + description: Comm Address to zeroMQ client + ZMQ_UpdatePeriod: + <<: *real + description: Integer for zeromq update frequency + # Calculated PC_RtTq99: <<: *real @@ -639,9 +680,12 @@ LocalVariables: RotSpeed: <<: *real description: Rotor speed (LSS) [rad/s] - Y_M: + NacHeading: <<: *real - description: Yaw direction [rad] + description: Nacelle heading of the turbine w.r.t. north [deg] + NacVane: + <<: *real + description: Nacelle vane angle [deg] HorWindV: <<: *real description: Hub height wind speed m/s @@ -750,6 +794,14 @@ LocalVariables: IPC_AxisYaw_2P: <<: *real description: Integral of quadrature, 2P + IPC_KI: + <<: *real + description: Integral gain for IPC, after ramp [-] + size: 2 + IPC_KP: + <<: *real + description: Proportional gain for IPC, after ramp [-] + size: 2 PC_State: <<: *integer description: State of the pitch control system @@ -757,6 +809,10 @@ LocalVariables: <<: *real description: Commanded pitch of each blade the last time the controller was called [rad]. size: 3 + PitComAct: + <<: *real + description: Actuated pitch of each blade the last time the controller was called [rad]. + size: 3 SS_DelOmegaF: <<: *real description: Filtered setpoint shifting term defined in setpoint smoother [rad/s]. @@ -805,21 +861,6 @@ LocalVariables: VS_LastGenTrqF: <<: *real description: Differentiated integrated wind speed quantity for estimation [m/s] - Y_AccErr: - <<: *real - description: Accumulated yaw error [rad]. - Y_ErrLPFFast: - <<: *real - description: Filtered yaw error by fast low pass filter [rad]. - Y_ErrLPFSlow: - <<: *real - description: Filtered yaw error by slow low pass filter [rad]. - Y_MErr: - <<: *real - description: Measured yaw error, measured + setpoint [rad]. - Y_YawEndT: - <<: *real - description: Yaw end time [s]. Indicates the time up until which yaw is active with a fixed rate SD: <<: *logical description: Shutdown, .FALSE. if inactive, .TRUE. if active @@ -966,6 +1007,21 @@ DebugVariables: <<: *real description: Yaw component of coleman transformation, 2P + YawRateCom: + <<: *real + description: Commanded yaw rate [rad/s]. + NacHeadingTarget: + <<: *real + description: Target nacelle heading [rad]. + NacVaneOffset: + <<: *real + description: Nacelle vane angle with offset [rad]. + Yaw_err: + <<: *real + description: Yaw error [rad]. + YawState: + <<: *real + description: State of yaw controller ErrorVariables: size_avcMSG: @@ -973,6 +1029,9 @@ ErrorVariables: aviFAIL: <<: *c_integer description: 'A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message.' + ErrStat: + <<: *c_integer + description: An error status flag used by OpenFAST processes ErrMsg: <<: *character description: a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] @@ -1000,4 +1059,19 @@ ExtDLL_Type: equals: '""' size: 3 length: 1024 - description: The name of the procedure in the DLL that will be called. \ No newline at end of file + description: The name of the procedure in the DLL that will be called. + +ZMQ_Variables: + ZMQ_Flag: + <<: *logical + description: Flag if we're using zeroMQ at all (0-False, 1-True) + Yaw_Offset: + <<: *real + description: Yaw offsety command, [rad] + +ExtControlType: + avrSWAP: + <<: *c_float + description: The swap array- used to pass data to and from the DLL controller [see Bladed DLL documentation] + allocatable: True + dimension: (:) diff --git a/ROSCO/rosco_registry/write_registry.py b/ROSCO/rosco_registry/write_registry.py index d1957748..05baaa8d 100644 --- a/ROSCO/rosco_registry/write_registry.py +++ b/ROSCO/rosco_registry/write_registry.py @@ -74,14 +74,15 @@ def write_roscoio(yfile): # ------------------------------------------------ # ------------ WriteRestartFile ------------------ # ------------------------------------------------ - file.write('SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAME)\n') + file.write('SUBROUTINE WriteRestartFile(LocalVar, CntrPar, ErrVar, objInst, RootName, size_avcOUTNAME)\n') file.write(" TYPE(LocalVariables), INTENT(IN) :: LocalVar\n") file.write(" TYPE(ControlParameters), INTENT(INOUT) :: CntrPar\n") file.write(" TYPE(ObjectInstances), INTENT(INOUT) :: objInst\n") + file.write(" TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar\n") file.write(" INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME\n") file.write(" CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName \n") file.write(" \n") - file.write(" INTEGER(IntKi), PARAMETER :: Un = 87 ! I/O unit for pack/unpack (checkpoint & restart)\n") + file.write(" INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart)\n") file.write(" INTEGER(IntKi) :: I ! Generic index.\n") file.write(" CHARACTER(128) :: InFile ! Input checkpoint file\n") file.write(" INTEGER(IntKi) :: ErrStat\n") @@ -89,7 +90,8 @@ def write_roscoio(yfile): file.write(" CHARACTER(128) :: n_t_global ! timestep number as a string\n") file.write("\n") file.write(" WRITE(n_t_global, '(I0.0)' ) NINT(LocalVar%Time/LocalVar%DT)\n") - file.write(" InFile = RootName(1:size_avcOUTNAME-5)//TRIM( n_t_global )//'.RO.chkp'\n") + file.write(" InFile = TRIM(RootName)//TRIM( n_t_global )//'.RO.chkp'\n") + file.write(" CALL GetNewUnit(Un, ErrVar)\n") file.write(" OPEN(unit=Un, FILE=TRIM(InFile), STATUS='UNKNOWN', FORM='UNFORMATTED' , ACCESS='STREAM', IOSTAT=ErrStat, ACTION='WRITE' )\n") file.write("\n") file.write(" IF ( ErrStat /= 0 ) THEN\n") @@ -116,17 +118,18 @@ def write_roscoio(yfile): # ------------------------------------------------ # ------------ ReadRestartFile ------------------ # ------------------------------------------------ - file.write('SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, ErrVar)\n') + file.write('SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, zmqVar, ErrVar)\n') file.write(" TYPE(LocalVariables), INTENT(INOUT) :: LocalVar\n") file.write(" TYPE(ControlParameters), INTENT(INOUT) :: CntrPar\n") file.write(" TYPE(ObjectInstances), INTENT(INOUT) :: objInst\n") file.write(" TYPE(PerformanceData), INTENT(INOUT) :: PerfData\n") file.write(" TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar\n") + file.write(" TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar\n") file.write(" REAL(C_FLOAT), INTENT(IN) :: avrSWAP(*)\n") file.write(" INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME\n") file.write(" CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName \n") file.write(" \n") - file.write(" INTEGER(IntKi), PARAMETER :: Un = 87 ! I/O unit for pack/unpack (checkpoint & restart)\n") + file.write(" INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart)\n") file.write(" INTEGER(IntKi) :: I ! Generic index.\n") file.write(" CHARACTER(128) :: InFile ! Input checkpoint file\n") file.write(" INTEGER(IntKi) :: ErrStat\n") @@ -134,7 +137,8 @@ def write_roscoio(yfile): file.write(" CHARACTER(128) :: n_t_global ! timestep number as a string\n") file.write("\n") file.write(" WRITE(n_t_global, '(I0.0)' ) NINT(avrSWAP(2)/avrSWAP(3))\n") - file.write(" InFile = RootName(1:size_avcOUTNAME-5)//TRIM( n_t_global )//'.RO.chkp'\n") + file.write(" InFile = TRIM(RootName)//TRIM( n_t_global )//'.RO.chkp'\n") + file.write(" CALL GetNewUnit(Un, ErrVar)\n") file.write(" OPEN(unit=Un, FILE=TRIM(InFile), STATUS='UNKNOWN', FORM='UNFORMATTED' , ACCESS='STREAM', IOSTAT=ErrStat, ACTION='READ' )\n") file.write("\n") file.write(" IF ( ErrStat /= 0 ) THEN\n") @@ -159,7 +163,7 @@ def write_roscoio(yfile): file.write(' Close ( Un )\n') file.write(' ENDIF\n') file.write(' ! Read Parameter files\n') - file.write(' CALL ReadControlParameterFileSub(CntrPar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, ErrVar)\n') + file.write(' CALL ReadControlParameterFileSub(CntrPar, zmqVar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, ErrVar)\n') file.write(' IF (CntrPar%WE_Mode > 0) THEN\n') file.write(' CALL READCpFile(CntrPar, PerfData, ErrVar)\n') file.write(' ENDIF\n') @@ -168,20 +172,21 @@ def write_roscoio(yfile): # ------------------------------------------------ # ------------------ Debug ----------------------- # ------------------------------------------------ - file.write('SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME)\n') + file.write('SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, ErrVar, avrSWAP, RootName, size_avcOUTNAME)\n') file.write('! Debug routine, defines what gets printed to DEBUG.dbg if LoggingLevel = 1\n') file.write('\n') file.write(' TYPE(ControlParameters), INTENT(IN) :: CntrPar\n') file.write(' TYPE(LocalVariables), INTENT(IN) :: LocalVar\n') file.write(' TYPE(DebugVariables), INTENT(IN) :: DebugVar\n') + file.write(' TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar\n') file.write('\n') file.write(' INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME\n') file.write(' INTEGER(IntKi) :: I , nDebugOuts, nLocalVars ! Generic index.\n') file.write(' CHARACTER(1), PARAMETER :: Tab = CHAR(9) ! The tab character.\n') file.write(' CHARACTER(29), PARAMETER :: FmtDat = "(F20.5,TR5,99(ES20.5E2,TR5:))" ! The format of the debugging data\n') - file.write(' INTEGER(IntKi), PARAMETER :: UnDb = 85 ! I/O unit for the debugging information\n') - file.write(' INTEGER(IntKi), PARAMETER :: UnDb2 = 86 ! I/O unit for the debugging information, avrSWAP\n') - file.write(' INTEGER(IntKi), PARAMETER :: UnDb3 = 87 ! I/O unit for the debugging information, avrSWAP\n') + file.write(' INTEGER(IntKi), SAVE :: UnDb ! I/O unit for the debugging information\n') + file.write(' INTEGER(IntKi), SAVE :: UnDb2 ! I/O unit for the debugging information, avrSWAP\n') + file.write(' INTEGER(IntKi), SAVE :: UnDb3 ! I/O unit for the debugging information, avrSWAP\n') file.write(' REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller.\n') file.write(' CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName ! a Fortran version of the input C string (not considered an array here) [subtract 1 for the C null-character]\n') file.write(' CHARACTER(200) :: Version ! git version of ROSCO\n') @@ -257,7 +262,8 @@ def write_roscoio(yfile): file.write(" IF ((LocalVar%iStatus == 0) .OR. (LocalVar%iStatus == -9)) THEN ! .TRUE. if we're on the first call to the DLL\n") # Standar debug file.write(" IF (CntrPar%LoggingLevel > 0) THEN\n") - file.write(" OPEN(unit=UnDb, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg')\n") + file.write(" CALL GetNewUnit(UnDb, ErrVar)\n") + file.write(" OPEN(unit=UnDb, FILE=TRIM(RootName)//'.RO.dbg')\n") file.write(" WRITE(UnDb, *) 'Generated on '//CurDate()//' at '//CurTime()//' using ROSCO-'//TRIM(rosco_version)\n") file.write(" WRITE(UnDb, '(99(a20,TR5:))') 'Time', DebugOutStrings\n") file.write(" WRITE(UnDb, '(99(a20,TR5:))') '(sec)', DebugOutUnits\n") @@ -265,7 +271,8 @@ def write_roscoio(yfile): file.write("\n") # LocalVar debug file.write(" IF (CntrPar%LoggingLevel > 1) THEN\n") - file.write(" OPEN(unit=UnDb2, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg2')\n") + file.write(" CALL GetNewUnit(UnDb2, ErrVar)\n") + file.write(" OPEN(unit=UnDb2, FILE=TRIM(RootName)//'.RO.dbg2')\n") file.write(" WRITE(UnDb2, *) 'Generated on '//CurDate()//' at '//CurTime()//' using ROSCO-'//TRIM(rosco_version)\n") file.write(" WRITE(UnDb2, '(99(a20,TR5:))') 'Time', LocalVarOutStrings\n") file.write(" WRITE(UnDb2, '(99(a20,TR5:))')\n") @@ -273,30 +280,30 @@ def write_roscoio(yfile): file.write("\n") # avrSWAP debug file.write(" IF (CntrPar%LoggingLevel > 2) THEN\n") - file.write(" OPEN(unit=UnDb3, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg3')\n") + file.write(" CALL GetNewUnit(UnDb3, ErrVar)\n") + file.write(" OPEN(unit=UnDb3, FILE=TRIM(RootName)//'.RO.dbg3')\n") file.write(" WRITE(UnDb3,'(/////)')\n") file.write(" WRITE(UnDb3,'"+'(A,85("'+"'//Tab//'"+'AvrSWAP("'+',I2,")"'+"))') 'LocalVar%Time ', (i,i=1, 85)\n") file.write(" WRITE(UnDb3,'"+'(A,85("'+"'//Tab//'"+'(-)"'+"))') '(s)'"+'\n') file.write(" END IF\n") - file.write(" ELSE\n") + file.write(" END IF\n") file.write(" ! Print simulation status, every 10 seconds\n") - file.write(" IF (MODULO(LocalVar%Time, 10.0_DbKi) == 0) THEN\n") - file.write(" WRITE(*, 100) LocalVar%GenSpeedF*RPS2RPM, LocalVar%BlPitch(1)*R2D, avrSWAP(15)/1000.0, LocalVar%WE_Vw\n") - file.write(" 100 FORMAT('Generator speed: ', f6.1, ' RPM, Pitch angle: ', f5.1, ' deg, Power: ', f7.1, ' kW, Est. wind Speed: ', f5.1, ' m/s')\n") - file.write(" END IF\n") + file.write(" IF (MODULO(LocalVar%Time, 10.0_DbKi) == 0) THEN\n") + file.write(" WRITE(*, 100) LocalVar%GenSpeedF*RPS2RPM, LocalVar%BlPitch(1)*R2D, avrSWAP(15)/1000.0, LocalVar%WE_Vw\n") + file.write(" 100 FORMAT('Generator speed: ', f6.1, ' RPM, Pitch angle: ', f5.1, ' deg, Power: ', f7.1, ' kW, Est. wind Speed: ', f5.1, ' m/s')\n") + file.write(" END IF\n") file.write("\n") - file.write(" ! Write debug files\n") - file.write(" IF(CntrPar%LoggingLevel > 0) THEN\n") - file.write(" WRITE (UnDb, FmtDat) LocalVar%Time, DebugOutData\n") - file.write(" END IF\n") + file.write(" ! Write debug files\n") + file.write(" IF(CntrPar%LoggingLevel > 0) THEN\n") + file.write(" WRITE (UnDb, FmtDat) LocalVar%Time, DebugOutData\n") + file.write(" END IF\n") file.write("\n") - file.write(" IF(CntrPar%LoggingLevel > 1) THEN\n") - file.write(" WRITE (UnDb2, FmtDat) LocalVar%Time, LocalVarOutData\n") - file.write(" END IF\n") + file.write(" IF(CntrPar%LoggingLevel > 1) THEN\n") + file.write(" WRITE (UnDb2, FmtDat) LocalVar%Time, LocalVarOutData\n") + file.write(" END IF\n") file.write("\n") - file.write(" IF(CntrPar%LoggingLevel > 2) THEN\n") - file.write(" WRITE (UnDb3, FmtDat) LocalVar%Time, avrSWAP(1: 85)\n") - file.write(" END IF\n") + file.write(" IF(CntrPar%LoggingLevel > 2) THEN\n") + file.write(" WRITE (UnDb3, FmtDat) LocalVar%Time, avrSWAP(1: 85)\n") file.write(" END IF\n") file.write("\n") file.write("END SUBROUTINE Debug\n") @@ -342,6 +349,15 @@ def read_type(param): f90type = 'LOGICAL' elif param['type'] == 'c_integer': f90type = 'INTEGER(C_INT)' + elif param['type'] == 'c_float': + f90type = 'REAL(C_FLOAT)' + if param['allocatable']: + if param['dimension']: + f90type += ', DIMENSION{}, ALLOCATABLE'.format(param['dimension']) + else: + f90type += ', DIMENSION(:), ALLOCATABLE' + elif param['dimension']: + f90type += ', DIMENSION{}'.format(param['dimension']) elif param['type'] == 'c_pointer': f90type = 'TYPE(C_PTR)' elif param['type'] == 'c_intptr_t': diff --git a/ROSCO/src/Constants.f90 b/ROSCO/src/Constants.f90 index bd7d50de..d04745ae 100644 --- a/ROSCO/src/Constants.f90 +++ b/ROSCO/src/Constants.f90 @@ -14,7 +14,7 @@ MODULE Constants USE, INTRINSIC :: ISO_C_Binding - Character(*), PARAMETER :: rosco_version = 'v2.5.0' ! ROSCO version + Character(*), PARAMETER :: rosco_version = 'v2.6.0' ! ROSCO version INTEGER, PARAMETER :: DbKi = C_DOUBLE !< Default kind for double floating-point numbers INTEGER, PARAMETER :: ReKi = C_FLOAT !< Default kind for single floating-point numbers INTEGER, PARAMETER :: IntKi = C_INT !< Default kind for integer numbers diff --git a/ROSCO/src/ControllerBlocks.f90 b/ROSCO/src/ControllerBlocks.f90 index a025f0ca..a62c5366 100644 --- a/ROSCO/src/ControllerBlocks.f90 +++ b/ROSCO/src/ControllerBlocks.f90 @@ -36,9 +36,6 @@ SUBROUTINE ComputeVariablesSetpoints(CntrPar, LocalVar, objInst) REAL(DbKi) :: PC_RefSpd ! Referece speed for pitch controller, [rad/s] REAL(DbKi) :: Omega_op ! Optimal TSR-tracking generator speed, [rad/s] - ! ----- Calculate yaw misalignment error ----- - LocalVar%Y_MErr = LocalVar%Y_M + CntrPar%Y_MErrSet ! Yaw-alignment error - ! ----- Pitch controller speed and power error ----- ! Implement setpoint smoothing IF (LocalVar%SS_DelOmegaF < 0) THEN diff --git a/ROSCO/src/Controllers.f90 b/ROSCO/src/Controllers.f90 index 7b9501e3..7568d4df 100644 --- a/ROSCO/src/Controllers.f90 +++ b/ROSCO/src/Controllers.f90 @@ -61,13 +61,13 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) DebugVar%PC_PICommand = LocalVar%PC_PitComT ! Find individual pitch control contribution IF ((CntrPar%IPC_ControlMode >= 1) .OR. (CntrPar%Y_ControlMode == 2)) THEN - CALL IPC(CntrPar, LocalVar, objInst, DebugVar) + CALL IPC(CntrPar, LocalVar, objInst, DebugVar, ErrVar) ELSE LocalVar%IPC_PitComF = 0.0 ! THIS IS AN ARRAY!! END IF ! Include tower fore-aft tower vibration damping control - IF ((CntrPar%FA_KI > 0.0) .OR. (CntrPar%Y_ControlMode == 2)) THEN + IF ((CntrPar%TD_Mode > 0) .OR. (CntrPar%Y_ControlMode == 2)) THEN CALL ForeAftDamping(CntrPar, LocalVar, objInst) ELSE LocalVar%FA_PitCom = 0.0 ! THIS IS AN ARRAY!! @@ -99,12 +99,11 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) LocalVar%PC_PitComT = ratelimit(LocalVar%PC_PitComT, LocalVar%PC_PitComT_Last, CntrPar%PC_MinRat, CntrPar%PC_MaxRat, LocalVar%DT) ! Saturate the overall command of blade K using the pitch rate limit LocalVar%PC_PitComT_Last = LocalVar%PC_PitComT - ! Combine and saturate all individual pitch commands: - ! Filter to emulate pitch actuator + ! Combine and saturate all individual pitch commands in software DO K = 1,LocalVar%NumBl ! Loop through all blades, add IPC contribution and limit pitch rate LocalVar%PitCom(K) = LocalVar%PC_PitComT + LocalVar%FA_PitCom(K) LocalVar%PitCom(K) = saturate(LocalVar%PitCom(K), LocalVar%PC_MinPit, CntrPar%PC_MaxPit) ! Saturate the command using the pitch satauration limits - LocalVar%PitCom(K) = LocalVar%PC_PitComT + LocalVar%IPC_PitComF(K) ! Add IPC + LocalVar%PitCom(K) = LocalVar%PitCom(K) + LocalVar%IPC_PitComF(K) ! Add IPC LocalVar%PitCom(K) = saturate(LocalVar%PitCom(K), LocalVar%PC_MinPit, CntrPar%PC_MaxPit) ! Saturate the command using the absolute pitch angle limits LocalVar%PitCom(K) = ratelimit(LocalVar%PitCom(K), LocalVar%BlPitch(K), CntrPar%PC_MinRat, CntrPar%PC_MaxRat, LocalVar%DT) ! Saturate the overall command of blade K using the pitch rate limit END DO @@ -119,12 +118,33 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) ENDIF ENDIF + ! Place pitch actuator here, so it can be used with or without open-loop + DO K = 1,LocalVar%NumBl ! Loop through all blades, add IPC contribution and limit pitch rate + IF (CntrPar%PA_Mode > 0) THEN + IF (CntrPar%PA_Mode == 1) THEN + LocalVar%PitComAct(K) = LPFilter(LocalVar%PitCom(K), LocalVar%DT, CntrPar%PA_CornerFreq, LocalVar%FP, LocalVar%iStatus, LocalVar%restart, objInst%instLPF) + ELSE IF (CntrPar%PA_Mode == 2) THEN + LocalVar%PitComAct(K) = SecLPFilter(LocalVar%PitCom(K),LocalVar%DT,CntrPar%PA_CornerFreq,CntrPar%PA_Damping,LocalVar%FP,LocalVar%iStatus,LocalVar%restart,objInst%instSecLPF) + END IF + ELSE + LocalVar%PitComAct(K) = LocalVar%PitCom(K) + ENDIF + END DO + + ! Hardware saturation: using CntrPar%PC_MinPit + DO K = 1,LocalVar%NumBl ! Loop through all blades, add IPC contribution and limit pitch rate + ! Saturate the pitch command using the overall (hardware) limit + LocalVar%PitComAct(K) = saturate(LocalVar%PitComAct(K), LocalVar%PC_MinPit, CntrPar%PC_MaxPit) + ! Saturate the overall command of blade K using the pitch rate limit + LocalVar%PitComAct(K) = ratelimit(LocalVar%PitComAct(K), LocalVar%BlPitch(K), CntrPar%PC_MinRat, CntrPar%PC_MaxRat, LocalVar%DT) ! Saturate the overall command of blade K using the pitch rate limit + END DO + ! Command the pitch demanded from the last ! call to the controller (See Appendix A of Bladed User's Guide): - avrSWAP(42) = LocalVar%PitCom(1) ! Use the command angles of all blades if using individual pitch - avrSWAP(43) = LocalVar%PitCom(2) ! " - avrSWAP(44) = LocalVar%PitCom(3) ! " - avrSWAP(45) = LocalVar%PitCom(1) ! Use the command angle of blade 1 if using collective pitch + avrSWAP(42) = LocalVar%PitComAct(1) ! Use the command angles of all blades if using individual pitch + avrSWAP(43) = LocalVar%PitComAct(2) ! " + avrSWAP(44) = LocalVar%PitComAct(3) ! " + avrSWAP(45) = LocalVar%PitComAct(1) ! Use the command angle of blade 1 if using collective pitch ! Add RoutineName to error message IF (ErrVar%aviFAIL < 0) THEN @@ -225,83 +245,144 @@ SUBROUTINE VariableSpeedControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar) END SUBROUTINE VariableSpeedControl !------------------------------------------------------------------------------------------------------------------------------- - SUBROUTINE YawRateControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar) + SUBROUTINE YawRateControl(avrSWAP, CntrPar, LocalVar, objInst, zmqVar, DebugVar, ErrVar) ! Yaw rate controller ! Y_ControlMode = 0, No yaw control - ! Y_ControlMode = 1, Simple yaw rate control using yaw drive - ! Y_ControlMode = 2, Yaw by IPC (accounted for in IPC subroutine) - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, ErrorVariables - - REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. - TYPE(ControlParameters), INTENT(INOUT) :: CntrPar - TYPE(LocalVariables), INTENT(INOUT) :: LocalVar - TYPE(ObjectInstances), INTENT(INOUT) :: objInst - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + ! Y_ControlMode = 1, Yaw rate control using yaw drive - CHARACTER(*), PARAMETER :: RoutineName = 'YawRateControl' + ! TODO: Lots of R2D->D2R, this should be cleaned up. + ! TODO: The constant offset implementation is sort of circular here as a setpoint is already being defined in SetVariablesSetpoints. This could also use cleanup + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, DebugVariables, ErrorVariables, ZMQ_Variables + + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. + + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(LocalVariables), INTENT(INOUT) :: LocalVar + TYPE(ObjectInstances), INTENT(INOUT) :: objInst + TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar - - !.............................................................................................................................. - ! Yaw control - !.............................................................................................................................. + ! Allocate Variables + REAL(DbKi), SAVE :: NacVaneOffset ! For offset control + INTEGER, SAVE :: YawState ! Yawing left(-1), right(1), or stopped(0) + REAL(DbKi) :: WindDir ! Instantaneous wind dind direction, equal to turbine nacelle heading plus the measured vane angle (deg) + REAL(DbKi) :: WindDirPlusOffset ! Instantaneous wind direction minus the assigned vane offset (deg) + REAL(DbKi) :: WindDirPlusOffsetCosF ! Time-filtered x-component of WindDirPlusOffset (deg) + REAL(DbKi) :: WindDirPlusOffsetSinF ! Time-filtered y-component of WindDirPlusOffset (deg) + REAL(DbKi) :: NacHeadingTarget ! Time-filtered wind direction minus the assigned vane offset (deg) + REAL(DbKi), SAVE :: NacHeadingError ! Yaw error (deg) + REAL(DbKi) :: YawRateCom ! Commanded yaw rate (deg/s) + REAL(DbKi) :: deadband ! Allowable yaw error deadband (deg) + REAL(DbKi) :: Time ! Current time + INTEGER, SAVE :: Tidx ! Index i: commanded yaw error is interpolated between i and i+1 IF (CntrPar%Y_ControlMode == 1) THEN - avrSWAP(29) = 0 ! Yaw control parameter: 0 = yaw rate control - IF (LocalVar%Time >= LocalVar%Y_YawEndT) THEN ! Check if the turbine is currently yawing - avrSWAP(48) = 0.0 ! Set yaw rate to zero - - LocalVar%Y_ErrLPFFast = LPFilter(LocalVar%Y_MErr, LocalVar%DT, CntrPar%Y_omegaLPFast, LocalVar%FP, LocalVar%iStatus, .FALSE., objInst%instLPF) ! Fast low pass filtered yaw error with a frequency of 1 - LocalVar%Y_ErrLPFSlow = LPFilter(LocalVar%Y_MErr, LocalVar%DT, CntrPar%Y_omegaLPSlow, LocalVar%FP, LocalVar%iStatus, .FALSE., objInst%instLPF) ! Slow low pass filtered yaw error with a frequency of 1/60 + + ! Compass wind directions in degrees + WindDir = wrap_360(LocalVar%NacHeading + LocalVar%NacVane) - LocalVar%Y_AccErr = LocalVar%Y_AccErr + LocalVar%DT*SIGN(LocalVar%Y_ErrLPFFast**2, LocalVar%Y_ErrLPFFast) ! Integral of the fast low pass filtered yaw error + ! Initialize + IF (LocalVar%iStatus == 0) THEN + YawState = 0 + Tidx = 1 + ENDIF - IF (ABS(LocalVar%Y_AccErr) >= CntrPar%Y_ErrThresh) THEN ! Check if accumulated error surpasses the threshold - LocalVar%Y_YawEndT = ABS(LocalVar%Y_ErrLPFSlow/CntrPar%Y_Rate) + LocalVar%Time ! Yaw to compensate for the slow low pass filtered error - END IF + ! Compute/apply offset + IF (CntrPar%ZMQ_Mode == 1) THEN + NacVaneOffset = zmqVar%Yaw_Offset ELSE - avrSWAP(48) = SIGN(CntrPar%Y_Rate, LocalVar%Y_MErr) ! Set yaw rate to predefined yaw rate, the sign of the error is copied to the rate - LocalVar%Y_ErrLPFFast = LPFilter(LocalVar%Y_MErr, LocalVar%DT, CntrPar%Y_omegaLPFast, LocalVar%FP, LocalVar%iStatus, .TRUE., objInst%instLPF) ! Fast low pass filtered yaw error with a frequency of 1 - LocalVar%Y_ErrLPFSlow = LPFilter(LocalVar%Y_MErr, LocalVar%DT, CntrPar%Y_omegaLPSlow, LocalVar%FP, LocalVar%iStatus, .TRUE., objInst%instLPF) ! Slow low pass filtered yaw error with a frequency of 1/60 - LocalVar%Y_AccErr = 0.0 ! " - END IF - END IF + NacVaneOffset = CntrPar%Y_MErrSet ! (deg) # Offset from setpoint + ENDIF - ! If using open loop yaw rate control, overwrite controlled output - ! Open loop torque control - IF ((CntrPar%OL_Mode == 1) .AND. (CntrPar%Ind_YawRate > 0)) THEN - IF (LocalVar%Time >= CntrPar%OL_Breakpoints(1)) THEN - avrSWAP(48) = interp1d(CntrPar%OL_Breakpoints,CntrPar%OL_YawRate,LocalVar%Time, ErrVar) + ! Update filtered wind direction + WindDirPlusOffset = wrap_360(WindDir + NacVaneOffset) ! (deg) + WindDirPlusOffsetCosF = LPFilter(cos(WindDirPlusOffset*D2R), LocalVar%DT, CntrPar%F_YawErr, LocalVar%FP, LocalVar%iStatus, .FALSE., objInst%instLPF) ! (-) + WindDirPlusOffsetSinF = LPFilter(sin(WindDirPlusOffset*D2R), LocalVar%DT, CntrPar%F_YawErr, LocalVar%FP, LocalVar%iStatus, .FALSE., objInst%instLPF) ! (-) + NacHeadingTarget = wrap_360(atan2(WindDirPlusOffsetSinF, WindDirPlusOffsetCosF) * R2D) ! (deg) + + ! ---- Now get into the guts of the control ---- + ! Yaw error + NacHeadingError = wrap_180(NacHeadingTarget - LocalVar%NacHeading) + + ! Check for deadband + IF (LocalVar%WE_Vw_F .le. CntrPar%Y_uSwitch) THEN + deadband = CntrPar%Y_ErrThresh(1) + ELSE + deadband = CntrPar%Y_ErrThresh(2) ENDIF - ENDIF - ! Add RoutineName to error message - IF (ErrVar%aviFAIL < 0) THEN - ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) - ENDIF + ! yawing right + IF (YawState == 1) THEN + IF (NacHeadingError .le. 0) THEN + ! stop yawing + YawRateCom = 0.0 + YawState = 0 + ELSE + ! persist + LocalVar%NacHeading = wrap_360(LocalVar%NacHeading + CntrPar%Y_Rate*LocalVar%DT) + YawRateCom = CntrPar%Y_Rate + YawState = 1 + ENDIF + ! yawing left + ELSEIF (YawState == -1) THEN + IF (NacHeadingError .ge. 0) THEN + ! stop yawing + YawRateCom = 0.0 + YawState = 0 + ELSE + ! persist + LocalVar%NacHeading = wrap_360(LocalVar%NacHeading - CntrPar%Y_Rate*LocalVar%DT) + YawRateCom = -CntrPar%Y_Rate + YawState = -1 + ENDIF + ! Initiate yaw if outside yaw error threshold + ELSE + IF (NacHeadingError .gt. deadband) THEN + YawState = 1 ! yaw right + ENDIF + IF (NacHeadingError .lt. -deadband) THEN + YawState = -1 ! yaw left + ENDIF + + YawRateCom = 0.0 ! if YawState is not 0, start yawing on the next time step + ENDIF + + ! Output yaw rate command in rad/s + avrSWAP(48) = YawRateCom * D2R + + ! Save for debug + DebugVar%YawRateCom = YawRateCom + DebugVar%NacHeadingTarget = NacHeadingTarget + DebugVar%NacVaneOffset = NacVaneOffset + DebugVar%YawState = YawState + END IF END SUBROUTINE YawRateControl !------------------------------------------------------------------------------------------------------------------------------- - SUBROUTINE IPC(CntrPar, LocalVar, objInst, DebugVar) + SUBROUTINE IPC(CntrPar, LocalVar, objInst, DebugVar, ErrVar) ! Individual pitch control subroutine ! - Calculates the commanded pitch angles for IPC employed for blade fatigue load reductions at 1P and 2P ! - Includes yaw by IPC - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, DebugVariables + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, DebugVariables, ErrorVariables TYPE(ControlParameters), INTENT(INOUT) :: CntrPar TYPE(LocalVariables), INTENT(INOUT) :: LocalVar TYPE(ObjectInstances), INTENT(INOUT) :: objInst - TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Local variables REAL(DbKi) :: PitComIPC(3), PitComIPCF(3), PitComIPC_1P(3), PitComIPC_2P(3) - INTEGER(IntKi) :: K ! Integer used to loop through turbine blades + INTEGER(IntKi) :: i, K ! Integer used to loop through gains and turbine blades REAL(DbKi) :: axisTilt_1P, axisYaw_1P, axisYawF_1P ! Direct axis and quadrature axis outputted by Coleman transform, 1P REAL(DbKi) :: axisTilt_2P, axisYaw_2P, axisYawF_2P ! Direct axis and quadrature axis outputted by Coleman transform, 1P REAL(DbKi) :: axisYawIPC_1P ! IPC contribution with yaw-by-IPC component - REAL(DbKi) :: Y_MErrF, Y_MErrF_IPC ! Unfiltered and filtered yaw alignment error [rad] - + REAL(DbKi) :: Y_MErr, Y_MErrF, Y_MErrF_IPC ! Unfiltered and filtered yaw alignment error [rad] + CHARACTER(*), PARAMETER :: RoutineName = 'IPC' + ! Body ! Pass rootMOOPs through the Coleman transform to get the tilt and yaw moment axis CALL ColemanTransform(LocalVar%rootMOOPF, LocalVar%Azimuth, NP_1, axisTilt_1P, axisYaw_1P) @@ -309,22 +390,29 @@ SUBROUTINE IPC(CntrPar, LocalVar, objInst, DebugVar) ! High-pass filter the MBC yaw component and filter yaw alignment error, and compute the yaw-by-IPC contribution IF (CntrPar%Y_ControlMode == 2) THEN - Y_MErrF = SecLPFilter(LocalVar%Y_MErr, LocalVar%DT, CntrPar%Y_IPC_omegaLP, CntrPar%Y_IPC_zetaLP, LocalVar%FP, LocalVar%iStatus, LocalVar%restart, objInst%instSecLPF) - Y_MErrF_IPC = PIController(Y_MErrF, CntrPar%Y_IPC_KP(1), CntrPar%Y_IPC_KI(1), -CntrPar%Y_IPC_IntSat, CntrPar%Y_IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) + Y_MErr = wrap_360(LocalVar%NacHeading + LocalVar%NacVane) + Y_MErrF = LPFilter(Y_MErr, LocalVar%DT, CntrPar%F_YawErr, LocalVar%FP, LocalVar%iStatus, LocalVar%restart, objInst%instSecLPF) + Y_MErrF_IPC = PIController(Y_MErrF, CntrPar%Y_IPC_KP, CntrPar%Y_IPC_KI, -CntrPar%Y_IPC_IntSat, CntrPar%Y_IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) ELSE axisYawF_1P = axisYaw_1P Y_MErrF = 0.0 Y_MErrF_IPC = 0.0 END IF + + ! Soft cutin with sigma function + DO i = 1,2 + LocalVar%IPC_KP(i) = sigma(LocalVar%WE_Vw, CntrPar%IPC_Vramp(1), CntrPar%IPC_Vramp(2), 0.0_DbKi, CntrPar%IPC_KP(i), ErrVar) + LocalVar%IPC_KI(i) = sigma(LocalVar%WE_Vw, CntrPar%IPC_Vramp(1), CntrPar%IPC_Vramp(2), 0.0_DbKi, CntrPar%IPC_KI(i), ErrVar) + END DO ! Integrate the signal and multiply with the IPC gain IF ((CntrPar%IPC_ControlMode >= 1) .AND. (CntrPar%Y_ControlMode /= 2)) THEN - LocalVar%IPC_axisTilt_1P = PIController(axisTilt_1P, CntrPar%IPC_KP(1), CntrPar%IPC_KI(1), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) - LocalVar%IPC_axisYaw_1P = PIController(axisYawF_1P, CntrPar%IPC_KP(1), CntrPar%IPC_KI(1), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) + LocalVar%IPC_axisTilt_1P = PIController(axisTilt_1P, LocalVar%IPC_KP(1), LocalVar%IPC_KI(1), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) + LocalVar%IPC_axisYaw_1P = PIController(axisYawF_1P, LocalVar%IPC_KP(1), LocalVar%IPC_KI(1), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) IF (CntrPar%IPC_ControlMode >= 2) THEN - LocalVar%IPC_axisTilt_2P = PIController(axisTilt_2P, CntrPar%IPC_KP(2), CntrPar%IPC_KI(2), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) - LocalVar%IPC_axisYaw_2P = PIController(axisYawF_2P, CntrPar%IPC_KP(2), CntrPar%IPC_KI(2), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) + LocalVar%IPC_axisTilt_2P = PIController(axisTilt_2P, LocalVar%IPC_KP(2), LocalVar%IPC_KI(2), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) + LocalVar%IPC_axisYaw_2P = PIController(axisYawF_2P, LocalVar%IPC_KP(2), LocalVar%IPC_KI(2), -CntrPar%IPC_IntSat, CntrPar%IPC_IntSat, LocalVar%DT, 0.0_DbKi, LocalVar%piP, LocalVar%restart, objInst%instPI) END IF ELSE LocalVar%IPC_axisTilt_1P = 0.0 @@ -361,6 +449,12 @@ SUBROUTINE IPC(CntrPar, LocalVar, objInst, DebugVar) DebugVar%axisYaw_2P = axisYaw_2P + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + END SUBROUTINE IPC !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE ForeAftDamping(CntrPar, LocalVar, objInst) diff --git a/ROSCO/src/DISCON.F90 b/ROSCO/src/DISCON.F90 index ef8e46d6..2b2ec286 100644 --- a/ROSCO/src/DISCON.F90 +++ b/ROSCO/src/DISCON.F90 @@ -25,7 +25,9 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME USE :: Constants USE :: Filters USE :: Functions +USE :: ExtControl USE :: ROSCO_IO +USE :: ZeroMQInterface IMPLICIT NONE ! Enable .dll export @@ -56,19 +58,23 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME TYPE(PerformanceData), SAVE :: PerfData TYPE(DebugVariables), SAVE :: DebugVar TYPE(ErrorVariables), SAVE :: ErrVar +TYPE(ZMQ_Variables), SAVE :: zmqVar +TYPE(ExtControlType), SAVE :: ExtDLL + CHARACTER(*), PARAMETER :: RoutineName = 'ROSCO' RootName = TRANSFER(avcOUTNAME, RootName) +CALL GetRoot(RootName,RootName) !------------------------------------------------------------------------------------------------------------------------------ ! Main control calculations !------------------------------------------------------------------------------------------------------------------------------ ! Check for restart IF ( (NINT(avrSWAP(1)) == -9) .AND. (aviFAIL >= 0)) THEN ! Read restart files - CALL ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, SIZE(avcOUTNAME), ErrVar) + CALL ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, SIZE(avcOUTNAME), zmqVar, ErrVar) IF ( CntrPar%LoggingLevel > 0 ) THEN - CALL Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, SIZE(avcOUTNAME)) + CALL Debug(LocalVar, CntrPar, DebugVar, ErrVar, avrSWAP, RootName, SIZE(avcOUTNAME)) END IF END IF @@ -76,14 +82,24 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME CALL ReadAvrSWAP(avrSWAP, LocalVar) ! Set Control Parameters -CALL SetParameters(avrSWAP, accINFILE, SIZE(avcMSG), CntrPar, LocalVar, objInst, PerfData, ErrVar) +CALL SetParameters(avrSWAP, accINFILE, SIZE(avcMSG), CntrPar, LocalVar, objInst, PerfData, zmqVar, ErrVar) + +! Call external controller, if desired +IF (CntrPar%Ext_Mode > 0) THEN + CALL ExtController(avrSWAP, CntrPar, LocalVar, ExtDLL, ErrVar) + ! Data from external dll is in ExtDLL%avrSWAP, it's unused in the following code +END IF + ! Filter signals CALL PreFilterMeasuredSignals(CntrPar, LocalVar, DebugVar, objInst, ErrVar) IF (((LocalVar%iStatus >= 0) .OR. (LocalVar%iStatus <= -8)) .AND. (ErrVar%aviFAIL >= 0)) THEN ! Only compute control calculations if no error has occurred and we are not on the last time step IF ((LocalVar%iStatus == -8) .AND. (ErrVar%aviFAIL >= 0)) THEN ! Write restart files - CALL WriteRestartFile(LocalVar, CntrPar, objInst, RootName, SIZE(avcOUTNAME)) + CALL WriteRestartFile(LocalVar, CntrPar, ErrVar, objInst, RootName, SIZE(avcOUTNAME)) + ENDIF + IF (zmqVar%ZMQ_Flag) THEN + CALL UpdateZeroMQ(LocalVar, CntrPar, zmqVar, ErrVar) ENDIF CALL WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar, ErrVar) @@ -94,7 +110,7 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME CALL PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) IF (CntrPar%Y_ControlMode > 0) THEN - CALL YawRateControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar) + CALL YawRateControl(avrSWAP, CntrPar, LocalVar, objInst, zmqVar, DebugVar, ErrVar) END IF IF (CntrPar%Flp_Mode > 0) THEN @@ -102,9 +118,10 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME END IF IF ( CntrPar%LoggingLevel > 0 ) THEN - CALL Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, SIZE(avcOUTNAME)) + CALL Debug(LocalVar, CntrPar, DebugVar, ErrVar, avrSWAP, RootName, SIZE(avcOUTNAME)) END IF - +ELSEIF ((LocalVar%iStatus == -1) .AND. (zmqVar%ZMQ_Flag)) THEN + CALL UpdateZeroMQ(LocalVar, CntrPar, zmqVar, ErrVar) END IF diff --git a/ROSCO/src/ExtControl.f90 b/ROSCO/src/ExtControl.f90 new file mode 100644 index 00000000..2a398cda --- /dev/null +++ b/ROSCO/src/ExtControl.f90 @@ -0,0 +1,135 @@ +! Copyright 2019 NREL + +! Licensed under the Apache License, Version 2.0 (the "License"); you may not use +! this file except in compliance with the License. You may obtain a copy of the +! License at http://www.apache.org/licenses/LICENSE-2.0 + +! Unless required by applicable law or agreed to in writing, software distributed +! under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +! CONDITIONS OF ANY KIND, either express or implied. See the License for the +! specific language governing permissions and limitations under the License. +! ------------------------------------------------------------------------------------------- + +! This module contains the primary controller routines + +! Subroutines: +! PitchControl: Blade pitch control high level subroutine +! VariableSpeedControl: Variable speed generator torque control +! YawRateControl: Nacelle yaw control +! IPC: Individual pitch control +! ForeAftDamping: Tower fore-aft damping control +! FloatingFeedback: Tower fore-aft feedback for floating offshore wind turbines + +MODULE ExtControl + + USE, INTRINSIC :: ISO_C_Binding + USE Functions + USE ROSCO_Types + USE SysSubs + + IMPLICIT NONE + + + + ABSTRACT INTERFACE + SUBROUTINE BladedDLL_Legacy_Procedure ( avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG ) BIND(C) + USE, INTRINSIC :: ISO_C_Binding + + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP (*) !< DATA + INTEGER(C_INT), INTENT(INOUT) :: aviFAIL !< FLAG (Status set in DLL and returned to simulation code) + CHARACTER(KIND=C_CHAR), INTENT(IN) :: accINFILE (*) !< INFILE + CHARACTER(KIND=C_CHAR), INTENT(INOUT) :: avcOUTNAME(*) !< OUTNAME (in:Simulation RootName; out:Name:Units; of logging channels) + CHARACTER(KIND=C_CHAR), INTENT(INOUT) :: avcMSG (*) !< MESSAGE (Message from DLL to simulation code [ErrMsg]) + END SUBROUTINE BladedDLL_Legacy_Procedure + + END INTERFACE + +CONTAINS + + SUBROUTINE ExtController(avrSWAP, CntrPar, LocalVar, ExtDLL, ErrVar) + ! Inputs + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(LocalVariables), INTENT(INOUT) :: LocalVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + TYPE(ExtControlType), INTENT(INOUT) :: ExtDLL + + + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from the DLL controller. + + ! Temporary variables + ! CHARACTER(1024), PARAMETER :: ExtDLL_InFile = '/Users/dzalkind/Tools/ROSCO/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN' + CHARACTER(100), PARAMETER :: ExtRootName = 'external_control' + + ! Local Variables + CHARACTER(*), PARAMETER :: RoutineName = 'ExtController' + + TYPE(ExtDLL_Type), SAVE :: DLL_Ext + INTEGER(IntKi), PARAMETER :: max_avr_entries = 2000 + + + PROCEDURE(BladedDLL_Legacy_Procedure), POINTER :: DLL_Legacy_Subroutine ! The address of the (legacy DISCON) procedure in the Bladed DLL + CHARACTER(KIND=C_CHAR) :: accINFILE(LEN_TRIM(CntrPar%DLL_InFile)+1) ! INFILE + CHARACTER(KIND=C_CHAR) :: avcOUTNAME(LEN_TRIM(ExtRootName)+1) ! OUTNAME (Simulation RootName) + CHARACTER(KIND=C_CHAR) :: avcMSG(LEN(ErrVar%ErrMsg)+1) ! MESSA + + + INTEGER(C_INT) :: aviFAIL ! A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message. + + + ! Initialize strings for external controller + aviFAIL = 0 + avcMSG = TRANSFER( C_NULL_CHAR, avcMSG ) + avcOUTNAME = TRANSFER( TRIM(ExtRootName)//C_NULL_CHAR, avcOUTNAME ) + accINFILE = TRANSFER( TRIM(CntrPar%DLL_InFile)//C_NULL_CHAR, accINFILE ) + + IF (LocalVar%iStatus == 0) THEN + + !! Set up DLL, will come from ROSCO input + DLL_Ext%FileName = TRIM(CntrPar%DLL_FileName) + DLL_Ext%ProcName = TRIM(CntrPar%DLL_ProcName) + + PRINT *, "ROSCO is calling an external dynamic library for control input:" + PRINT *, "DLL_FileName:", TRIM(CntrPar%DLL_FileName) + PRINT *, "DLL_InFile:", TRIM(CntrPar%DLL_InFile) + PRINT *, "DLL_ProcName:", TRIM(CntrPar%DLL_ProcName) + + ! Load dynamic library, but first make sure that it's free + ! CALL FreeDynamicLib(DLL_Ext, ErrVar%ErrStat, ErrVar%ErrMsg) + CALL LoadDynamicLib(DLL_Ext, ErrVar%ErrStat, ErrVar%ErrMsg) + ALLOCATE(ExtDLL%avrSWAP(max_avr_entries)) !(1:max_avr_entries) + + PRINT *, "Library loaded successfully" + + END IF + + ! Set avrSWAP of external DLL, inputs to external DLL + ExtDLL%avrSWAP = avrSWAP(1:max_avr_entries) + + ! Set some length parameters + ExtDLL%avrSWAP(49) = LEN(avcMSG) + 1 !> * Record 49: Maximum number of characters in the "MESSAGE" argument (-) [size of ExtErrMsg argument plus 1 (we add one for the C NULL CHARACTER)] + ExtDLL%avrSWAP(50) = LEN_TRIM(CntrPar%DLL_InFile) +1 !> * Record 50: Number of characters in the "INFILE" argument (-) [trimmed length of ExtDLL_InFile parameter plus 1 (we add one for the C NULL CHARACTER)] + ExtDLL%avrSWAP(51) = LEN_TRIM(ExtRootName) +1 !> * Record 51: Number of characters in the "OUTNAME" argument (-) [trimmed length of ExtRootName parameter plus 1 (we add one for the C NULL CHARACTER)] + + ! Call the DLL (first associate the address from the procedure in the DLL with the subroutine): + CALL C_F_PROCPOINTER( DLL_Ext%ProcAddr(1), DLL_Legacy_Subroutine) + CALL DLL_Legacy_Subroutine (ExtDLL%avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG ) + + ! Clean up DLL + ! CALL FreeDynamicLib(DLL_Ext, ErrVar%ErrStat, ErrVar%ErrMsg) + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + print * , TRIM(ErrVar%ErrMsg) + ENDIF + + + END SUBROUTINE ExtController + + +!================================================================================================================= +!================================================================================================================= +!================================================================================================================= + + +END MODULE ExtControl diff --git a/ROSCO/src/Filters.f90 b/ROSCO/src/Filters.f90 index 0204c5bc..5123b74c 100644 --- a/ROSCO/src/Filters.f90 +++ b/ROSCO/src/Filters.f90 @@ -137,30 +137,47 @@ REAL(DbKi) FUNCTION HPFilter( InputSignal, DT, CornerFreq, FP, iStatus, reset, i END FUNCTION HPFilter !------------------------------------------------------------------------------------------------------------------------------- - REAL(DbKi) FUNCTION NotchFilterSlopes(InputSignal, DT, CornerFreq, Damp, FP, iStatus, reset, inst) + REAL(DbKi) FUNCTION NotchFilterSlopes(InputSignal, DT, CornerFreq, Damp, FP, iStatus, reset, inst, Moving) ! Discrete time inverted Notch Filter with descending slopes, G = CornerFreq*s/(Damp*s^2+CornerFreq*s+Damp*CornerFreq^2) USE ROSCO_Types, ONLY : FilterParameters TYPE(FilterParameters), INTENT(INOUT) :: FP - REAL(DbKi), INTENT(IN) :: InputSignal - REAL(DbKi), INTENT(IN) :: DT ! time step [s] - REAL(DbKi), INTENT(IN) :: CornerFreq ! corner frequency [rad/s] - REAL(DbKi), INTENT(IN) :: Damp ! Dampening constant - INTEGER(IntKi), INTENT(IN) :: iStatus ! A status flag set by the simulation as follows: 0 if this is the first call, 1 for all subsequent time steps, -1 if this is the final call at the end of the simulation. - INTEGER(IntKi), INTENT(INOUT) :: inst ! Instance number. Every instance of this function needs to have an unique instance number to ensure instances don't influence each other. - LOGICAL(4), INTENT(IN) :: reset ! Reset the filter to the input signal - + REAL(DbKi), INTENT(IN) :: InputSignal + REAL(DbKi), INTENT(IN) :: DT ! time step [s] + REAL(DbKi), INTENT(IN) :: CornerFreq ! corner frequency [rad/s] + REAL(DbKi), INTENT(IN) :: Damp ! Dampening constant + INTEGER(IntKi), INTENT(IN) :: iStatus ! A status flag set by the simulation as follows: 0 if this is the first call, 1 for all subsequent time steps, -1 if this is the final call at the end of the simulation. + INTEGER(IntKi), INTENT(INOUT) :: inst ! Instance number. Every instance of this function needs to have an unique instance number to ensure instances don't influence each other. + LOGICAL(4), INTENT(IN) :: reset ! Reset the filter to the input signal + LOGICAL, OPTIONAL, INTENT(IN) :: Moving ! Moving CornerFreq flag + + LOGICAL :: Moving_ ! Local version + REAL(DbKi) :: CornerFreq_ ! Local version + + Moving_ = .FALSE. + IF (PRESENT(Moving)) Moving_ = Moving + + ! Saturate Corner Freq at 0 + IF (CornerFreq < 0) THEN + CornerFreq_ = 0 + ELSE + CornerFreq_ = CornerFreq + ENDIF + ! Initialization IF ((iStatus == 0) .OR. reset) THEN FP%nfs_OutputSignalLast1(inst) = InputSignal FP%nfs_OutputSignalLast2(inst) = InputSignal FP%nfs_InputSignalLast1(inst) = InputSignal FP%nfs_InputSignalLast2(inst) = InputSignal - FP%nfs_b2(inst) = 2.0 * DT * CornerFreq + ENDIF + + IF ((iStatus == 0) .OR. reset .OR. Moving_) THEN + FP%nfs_b2(inst) = 2.0 * DT * CornerFreq_ FP%nfs_b0(inst) = -FP%nfs_b2(inst) - FP%nfs_a2(inst) = Damp*DT**2.0*CornerFreq**2.0 + 2.0*DT*CornerFreq + 4.0*Damp - FP%nfs_a1(inst) = 2.0*Damp*DT**2.0*CornerFreq**2.0 - 8.0*Damp - FP%nfs_a0(inst) = Damp*DT**2.0*CornerFreq**2.0 - 2*DT*CornerFreq + 4.0*Damp + FP%nfs_a2(inst) = Damp*DT**2.0*CornerFreq_**2.0 + 2.0*DT*CornerFreq_ + 4.0*Damp + FP%nfs_a1(inst) = 2.0*Damp*DT**2.0*CornerFreq_**2.0 - 8.0*Damp + FP%nfs_a0(inst) = Damp*DT**2.0*CornerFreq_**2.0 - 2*DT*CornerFreq_ + 4.0*Damp ENDIF NotchFilterSlopes = 1.0/FP%nfs_a2(inst) * (FP%nfs_b2(inst)*InputSignal + FP%nfs_b0(inst)*FP%nfs_InputSignalLast1(inst) & @@ -270,7 +287,8 @@ SUBROUTINE PreFilterMeasuredSignals(CntrPar, LocalVar, DebugVar, objInst, ErrVar ! Blade root bending moment for IPC DO K = 1,LocalVar%NumBl IF ((CntrPar%IPC_ControlMode > 0) .OR. (CntrPar%Flp_Mode == 3)) THEN - LocalVar%RootMOOPF(K) = NotchFilterSlopes(LocalVar%rootMOOP(K), LocalVar%DT, LocalVar%RotSpeedF, 0.7_DbKi, LocalVar%FP, LocalVar%iStatus, LocalVar%restart, objInst%instNotchSlopes) + ! Moving inverted notch at rotor speed to isolate 1P + LocalVar%RootMOOPF(K) = NotchFilterSlopes(LocalVar%rootMOOP(K), LocalVar%DT, LocalVar%RotSpeedF, 0.7_DbKi, LocalVar%FP, LocalVar%iStatus, LocalVar%restart, objInst%instNotchSlopes, .TRUE.) ELSEIF ( CntrPar%Flp_Mode == 2 ) THEN ! Filter Blade root bending moments LocalVar%RootMOOPF(K) = SecLPFilter(LocalVar%rootMOOP(K),LocalVar%DT, CntrPar%F_FlpCornerFreq(1), CntrPar%F_FlpCornerFreq(2), LocalVar%FP, LocalVar%iStatus, LocalVar%restart,objInst%instSecLPF) @@ -292,4 +310,4 @@ SUBROUTINE PreFilterMeasuredSignals(CntrPar, LocalVar, DebugVar, objInst, ErrVar DebugVar%NacIMU_FA_AccF = LocalVar%NacIMU_FA_AccF DebugVar%FA_AccF = LocalVar%FA_AccF END SUBROUTINE PreFilterMeasuredSignals - END MODULE Filters \ No newline at end of file + END MODULE Filters diff --git a/ROSCO/src/Functions.f90 b/ROSCO/src/Functions.f90 index f34ba272..af9a0c2c 100644 --- a/ROSCO/src/Functions.f90 +++ b/ROSCO/src/Functions.f90 @@ -9,7 +9,7 @@ ! CONDITIONS OF ANY KIND, either express or implied. See the License for the ! specific language governing permissions and limitations under the License. ! ------------------------------------------------------------------------------------------- -! This module contains basic functions used by the controller +! This module contains basic control-related functions ! Functions: ! AeroDynTorque: Calculate aerodynamic torque @@ -509,6 +509,70 @@ REAL(DbKi) FUNCTION AeroDynTorque(RotSpeed, BldPitch, LocalVar, CntrPar, PerfDat ENDIF END FUNCTION AeroDynTorque +!------------------------------------------------------------------------------------------------------------------------------- + REAL FUNCTION wrap_180(x) + ! Function modifies input angle, x, such that -180<=x<=180, preventing windup + REAL(DbKi), INTENT(IN) :: x ! angle, degrees + + IF (x .le. -180.0) THEN + wrap_180 = x + 360.0 + ELSEIF (x .gt. 180.0) THEN + wrap_180 = x - 360.0 + ELSE + wrap_180 = x + ENDIF + + END FUNCTION wrap_180 +!------------------------------------------------------------------------------------------------------------------------------- + REAL FUNCTION wrap_360(x) + ! Function modifies input angle, x, such that 0<=x<=360, preventing windup + REAL(DbKi), INTENT(IN) :: x ! angle, degrees + + IF (x .lt. 0.0) THEN + wrap_360 = x + 360.0 + ELSEIF (x .ge. 360.0) THEN + wrap_360 = x - 360.0 + ELSE + wrap_360 = x + ENDIF + + END FUNCTION wrap_360 +!------------------------------------------------------------------------------------------------------------------------------- + REAL(DbKi) FUNCTION sigma(x, x0, x1, y0, y1, ErrVar) + ! Generic sigma function + USE ROSCO_Types, ONLY : ErrorVariables + IMPLICIT NONE + + ! Inputs + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + + REAL(DbKi), Intent(IN) :: x, x0, x1 + REAL(DbKi), Intent(IN) :: y0, y1 + + ! Local + REAL(DbKi) :: a3, a2, a1, a0 + + CHARACTER(*), PARAMETER :: RoutineName = 'sigma' + + a3 = 2/(x0-x1)**3 + a2 = -3*(x0+x1)/(x0-x1)**3 + a1 = 6*x1*x0/(x0-x1)**3 + a0 = (x0-3*x1)*x0**2/(x0-x1)**3 + + IF (x < x0) THEN + sigma = y0 + ELSEIF (x > x1) THEN + sigma = y1 + ELSE + sigma = (a3*x**3 + a2*x**2 + a1*x + a0)*(y1-y0) + y0 + ENDIF + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + + END FUNCTION sigma !------------------------------------------------------------------------------------------------------------------------------- @@ -575,98 +639,5 @@ FUNCTION CurDate( ) RETURN END FUNCTION CurDate -!======================================================================= -!> This function returns a character string encoded with the time in the form "hh:mm:ss". - FUNCTION CurTime( ) - - ! Function declaration. - - CHARACTER(8) :: CurTime !< The current time in the form "hh:mm:ss". - - - ! Local declarations. - - CHARACTER(10) :: CTime ! String to hold the returned value from the DATE_AND_TIME subroutine call. - - - - CALL DATE_AND_TIME ( TIME=CTime ) - - CurTime = CTime(1:2)//':'//CTime(3:4)//':'//CTime(5:6) - - - RETURN - END FUNCTION CurTime - -!======================================================================= -! This function checks whether an array is non-decreasing - LOGICAL Function NonDecreasing(Array) - - IMPLICIT NONE - - REAL(DbKi), DIMENSION(:) :: Array - INTEGER(IntKi) :: I_DIFF - - NonDecreasing = .TRUE. - ! Is Array non decreasing - DO I_DIFF = 1, size(Array) - 1 - IF (Array(I_DIFF + 1) - Array(I_DIFF) <= 0) THEN - NonDecreasing = .FALSE. - RETURN - END IF - END DO - - RETURN - END FUNCTION NonDecreasing - -!======================================================================= -!> This routine converts all the text in a string to upper case. - SUBROUTINE Conv2UC ( Str ) - - ! Argument declarations. - - CHARACTER(*), INTENT(INOUT) :: Str !< The string to be converted to UC (upper case). - - - ! Local declarations. - - INTEGER :: IC ! Character index - - - - DO IC=1,LEN_TRIM( Str ) - - IF ( ( Str(IC:IC) >= 'a' ).AND.( Str(IC:IC) <= 'z' ) ) THEN - Str(IC:IC) = CHAR( ICHAR( Str(IC:IC) ) - 32 ) - END IF - - END DO ! IC - - - RETURN - END SUBROUTINE Conv2UC - -!======================================================================= - !> This function returns a left-adjusted string representing the passed numeric value. - !! It eliminates trailing zeroes and even the decimal point if it is not a fraction. \n - !! Use Num2LStr (nwtc_io::num2lstr) instead of directly calling a specific routine in the generic interface. - FUNCTION Int2LStr ( Num ) - - CHARACTER(11) :: Int2LStr !< string representing input number. - - - ! Argument declarations. - - INTEGER, INTENT(IN) :: Num !< The number to convert to a left-justified string. - - - - WRITE (Int2LStr,'(I11)') Num - - Int2Lstr = ADJUSTL( Int2LStr ) - - - RETURN - END FUNCTION Int2LStr END MODULE Functions diff --git a/ROSCO/src/ROSCO_Helpers.f90 b/ROSCO/src/ROSCO_Helpers.f90 new file mode 100644 index 00000000..c030cc84 --- /dev/null +++ b/ROSCO/src/ROSCO_Helpers.f90 @@ -0,0 +1,1041 @@ +! Copyright 2019 NREL + +! Licensed under the Apache License, Version 2.0 (the "License"); you may not use +! this file except in compliance with the License. You may obtain a copy of the +! License at http://www.apache.org/licenses/LICENSE-2.0 + +! Unless required by applicable law or agreed to in writing, software distributed +! under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +! CONDITIONS OF ANY KIND, either express or implied. See the License for the +! specific language governing permissions and limitations under the License. +! ------------------------------------------------------------------------------------------- +! Helper functions, primarily borrowed from NWTC_IO, for reading inputs and carrying out other helpful tasks + +MODULE ROSCO_Helpers + + USE, INTRINSIC :: ISO_C_Binding + USE ROSCO_Types + USE CONSTANTS + USE SysSubs + + + IMPLICIT NONE + + ! Global Variables + LOGICAL, PARAMETER :: DEBUG_PARSING = .FALSE. ! debug flag to output parsing information, set up Echo file later + + INTERFACE ParseInput ! Parses a character variable name and value from a string. + MODULE PROCEDURE ParseInput_Str ! Parses a character string from a string. + MODULE PROCEDURE ParseInput_Dbl ! Parses a double-precision REAL from a string. + MODULE PROCEDURE ParseInput_Int ! Parses an INTEGER from a string. + ! MODULE PROCEDURE ParseInput_Log ! Parses an LOGICAL from a string. + END INTERFACE + + INTERFACE ParseAry ! Parse an array of numbers from a string. + MODULE PROCEDURE ParseDbAry ! Parse an array of double-precision REAL values. + MODULE PROCEDURE ParseInAry ! Parse an array of whole numbers. + END INTERFACE + +CONTAINS + + !======================================================================= + ! Parse integer input: read line, check that variable name is in line, handle errors + subroutine ParseInput_Int(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(20) :: Words (2) ! The two "words" parsed from the line + + INTEGER(IntKi), INTENT(INOUT) :: Variable ! Variable + INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + IF (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),*,IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Int + + !======================================================================= + ! Parse double input, this is a copy of ParseInput_Int and a change in the variable definitions + subroutine ParseInput_Dbl(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(20) :: Words (2) ! The two "words" parsed from the line + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + REAL(DbKi), INTENT(INOUT) :: Variable ! Variable + INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + IF (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),*,IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Dbl + + !======================================================================= + ! Parse string input, this is a copy of ParseInput_Int and a change in the variable definitions + subroutine ParseInput_Str(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(200) :: Words (2) ! The two "words" parsed from the line + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + CHARACTER(*), INTENT(INOUT) :: Variable ! Variable + INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + if (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),'(A)',IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid STRING value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Str + +!======================================================================= +!> This subroutine parses the specified line of text for AryLen REAL values. +!! Generate an error message if the value is the wrong type. +!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. + SUBROUTINE ParseDbAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) + + USE ROSCO_Types, ONLY : ErrorVariables + + ! Arguments declarations. + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. + + REAL(DbKi), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. + + INTEGER(IntKi), INTENT(INOUT) :: LineNum !< The number of the line to parse. + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. + + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + + ! Local declarations. + + CHARACTER(1024) :: Line + INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. + INTEGER(IntKi) :: i + + CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. + CHARACTER(1024) :: Debug_String + CHARACTER(*), PARAMETER :: RoutineName = 'ParseDbAry' + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Allocate array and handle errors + ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + IF ( ALLOCATED(Ary) ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' + END IF + END IF + + ! Allocate words array + ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' + CALL Cleanup() + RETURN + ENDIF + + ! Separate line string into AryLen + 1 words, should include variable name + CALL GetWords ( Line, Words_Ary, AryLen + 1 ) + + ! Debug Output + IF (DEBUG_PARSING) THEN + Debug_String = '' + DO i = 1,AryLen+1 + Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) + IF (i < AryLen + 1) THEN + Debug_String = TRIM(Debug_String)//',' + END IF + END DO + print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum + END IF + + ! Check that Variable Name is at the end of Words, will also check length of array + IF (CheckName_) THEN + CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) + END IF + + ! Read array + READ (Line,*,IOSTAT=ErrStatLcl) Ary + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & + //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & + //' "'//TRIM( Line )//'"' + RETURN + CALL Cleanup() + ENDIF + + ! IF ( PRESENT(UnEc) ) THEN + ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) + ! END IF + + LineNum = LineNum + 1 + CALL Cleanup() + ENDIF + + RETURN + + !======================================================================= + CONTAINS + !======================================================================= + SUBROUTINE Cleanup ( ) + + ! This subroutine cleans up the parent routine before exiting. + + ! Deallocate the Words array if it had been allocated. + + IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) + + + RETURN + + END SUBROUTINE Cleanup + + END SUBROUTINE ParseDbAry + + !======================================================================= +!> This subroutine parses the specified line of text for AryLen INTEGER values. +!! Generate an error message if the value is the wrong type. +!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. + SUBROUTINE ParseInAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) + + USE ROSCO_Types, ONLY : ErrorVariables + + ! Arguments declarations. + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. + + INTEGER(IntKi), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. + + INTEGER(IntKi), INTENT(INOUT) :: LineNum !< The number of the line to parse. + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. + + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + ! Local declarations. + + CHARACTER(1024) :: Line + INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. + INTEGER(IntKi) :: i + + CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. + CHARACTER(1024) :: Debug_String + CHARACTER(*), PARAMETER :: RoutineName = 'ParseInAry' + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Allocate array and handle errors + ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + IF ( ALLOCATED(Ary) ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' + END IF + END IF + + ! Allocate words array + ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' + CALL Cleanup() + RETURN + ENDIF + + ! Separate line string into AryLen + 1 words, should include variable name + CALL GetWords ( Line, Words_Ary, AryLen + 1 ) + + ! Debug Output + IF (DEBUG_PARSING) THEN + Debug_String = '' + DO i = 1,AryLen+1 + Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) + IF (i < AryLen + 1) THEN + Debug_String = TRIM(Debug_String)//',' + END IF + END DO + print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum + END IF + + ! Check that Variable Name is at the end of Words, will also check length of array + IF (CheckName_) THEN + CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) + END IF + + ! Read array + READ (Line,*,IOSTAT=ErrStatLcl) Ary + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & + //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & + //' "'//TRIM( Line )//'"' + RETURN + CALL Cleanup() + ENDIF + + ! IF ( PRESENT(UnEc) ) THEN + ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) + ! END IF + + LineNum = LineNum + 1 + CALL Cleanup() + ENDIF + + RETURN + + !======================================================================= + CONTAINS + !======================================================================= + SUBROUTINE Cleanup ( ) + + ! This subroutine cleans up the parent routine before exiting. + + ! Deallocate the Words array if it had been allocated. + + IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) + + + RETURN + + END SUBROUTINE Cleanup + +END SUBROUTINE ParseInAry + +!======================================================================= + !> This subroutine checks the data to be parsed to make sure it finds + !! the expected variable name and an associated value. +SUBROUTINE ChkParseData ( Words, ExpVarName, FileName, FileLineNum, ErrVar ) + + USE ROSCO_Types, ONLY : ErrorVariables + + + ! Arguments declarations. + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + INTEGER(IntKi), INTENT(IN) :: FileLineNum !< The number of the line in the file being parsed. + INTEGER(IntKi) :: NameIndx !< The index into the Words array that points to the variable name. + + CHARACTER(*), INTENT(IN) :: ExpVarName !< The expected variable name. + CHARACTER(*), INTENT(IN) :: Words (2) !< The two words to be parsed from the line. + + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + ! Local declarations. + + CHARACTER(20) :: ExpUCVarName ! The uppercase version of ExpVarName. + CHARACTER(20) :: FndUCVarName ! The uppercase version of the word being tested. + + + + + ! Convert the found and expected names to uppercase. + + FndUCVarName = Words(1) + ExpUCVarName = ExpVarName + + CALL Conv2UC ( FndUCVarName ) + CALL Conv2UC ( ExpUCVarName ) + + ! See which word is the variable name. Generate an error if it is the first + + IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN + NameIndx = 1 + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & + //'".'//NewLine//' >> The variable "'//TRIM( Words(1) )//'" was not assigned a valid value on line #' & + //TRIM( Int2LStr( FileLineNum ) )//'.' + RETURN + ELSE + FndUCVarName = Words(2) + CALL Conv2UC ( FndUCVarName ) + IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN + NameIndx = 2 + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & + //'".'//NewLine//' >> The variable "'//TRIM( ExpVarName )//'" was not assigned a valid value on line #' & + //TRIM( Int2LStr( FileLineNum ) )//'.' + RETURN + ENDIF + ENDIF + + +END SUBROUTINE ChkParseData + +!======================================================================= +subroutine ReadEmptyLine(Un,CurLine) + INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit + INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input + + CHARACTER(1024) :: Line + + READ(Un, '(A)') Line + CurLine = CurLine + 1 + +END subroutine ReadEmptyLine + +!======================================================================= +!> This subroutine is used to get the NumWords "words" from a line of text. +!! It uses spaces, tabs, commas, semicolons, single quotes, and double quotes ("whitespace") +!! as word separators. If there aren't NumWords in the line, the remaining array elements will remain empty. +!! Use CountWords (nwtc_io::countwords) to count the number of words in a line. +SUBROUTINE GetWords ( Line, Words, NumWords ) + + ! Argument declarations. + + INTEGER, INTENT(IN) :: NumWords !< The number of words to look for. + + CHARACTER(*), INTENT(IN) :: Line !< The string to search. + CHARACTER(*), INTENT(OUT) :: Words(NumWords) !< The array of found words. + + + ! Local declarations. + + INTEGER :: Ch ! Character position within the string. + INTEGER :: IW ! Word index. + INTEGER :: NextWhite ! The location of the next whitespace in the string. + CHARACTER(1), PARAMETER :: Tab = CHAR( 9 ) + + + + ! Let's prefill the array with blanks. + + DO IW=1,NumWords + Words(IW) = ' ' + END DO ! IW + + + ! Let's make sure we have text on this line. + + IF ( LEN_TRIM( Line ) == 0 ) RETURN + + + ! Parse words separated by any combination of spaces, tabs, commas, + ! semicolons, single quotes, and double quotes ("whitespace"). + + Ch = 0 + IW = 0 + + DO + + NextWhite = SCAN( Line(Ch+1:) , ' ,!;''"'//Tab ) + + IF ( NextWhite > 1 ) THEN + + IW = IW + 1 + Words(IW) = Line(Ch+1:Ch+NextWhite-1) + + IF ( IW == NumWords ) EXIT + + Ch = Ch + NextWhite + + ELSE IF ( NextWhite == 1 ) THEN + + Ch = Ch + 1 + + CYCLE + + ELSE + + EXIT + + END IF + + END DO + + + RETURN +END SUBROUTINE GetWords +!======================================================================= +!> Let's parse the path name from the name of the given file. +!! We'll count everything before (and including) the last "\" or "/". +SUBROUTINE GetPath ( GivenFil, PathName ) + + ! Argument declarations. + + CHARACTER(*), INTENT(IN) :: GivenFil !< The name of the given file. + CHARACTER(*), INTENT(OUT) :: PathName !< The path name of the given file (based solely on the GivenFil text string). + + + ! Local declarations. + + INTEGER :: I ! DO index for character position. + + + ! Look for path separators + + I = INDEX( GivenFil, '\', BACK=.TRUE. ) + I = MAX( I, INDEX( GivenFil, '/', BACK=.TRUE. ) ) + + IF ( I == 0 ) THEN + ! we don't have a path specified, return '.' + PathName = '.'//PathSep + ELSE + PathName = GivenFil(:I) + END IF + + + RETURN + END SUBROUTINE GetPath +!======================================================================= +!> Let's parse the root file name from the name of the given file. +!! We'll count everything after the last period as the extension. +!! Borrowed from NWTC_IO...thanks! + + SUBROUTINE GetRoot ( GivenFil, RootName ) + + ! Argument declarations. + + CHARACTER(*), INTENT(IN) :: GivenFil !< The name of the given file. + CHARACTER(*), INTENT(OUT) :: RootName !< The parsed root name of the given file. + + + ! Local declarations. + + INTEGER :: I ! DO index for character position. + + + + ! Deal with a couple of special cases. + + IF ( ( TRIM( GivenFil ) == "." ) .OR. ( TRIM( GivenFil ) == ".." ) ) THEN + RootName = TRIM( GivenFil ) + RETURN + END IF + + + ! More-normal cases. + + DO I=LEN_TRIM( GivenFil ),1,-1 + + + IF ( GivenFil(I:I) == '.' ) THEN + + + IF ( I < LEN_TRIM( GivenFil ) ) THEN ! Make sure the index I is okay + IF ( INDEX( '\/', GivenFil(I+1:I+1)) == 0 ) THEN ! Make sure we don't have the RootName in a different directory + RootName = GivenFil(:I-1) + ELSE + RootName = GivenFil ! This does not have a file extension + END IF + ELSE + IF ( I == 1 ) THEN + RootName = '' + ELSE + RootName = GivenFil(:I-1) + END IF + END IF + + RETURN + + END IF + END DO ! I + + RootName = GivenFil + + + RETURN + END SUBROUTINE GetRoot +!======================================================================= +!> This routine determines if the given file name is absolute or relative. +!! We will consider an absolute path one that satisfies one of the +!! following four criteria: +!! 1. It contains ":/" +!! 2. It contains ":\" +!! 3. It starts with "/" +!! 4. It starts with "\" +!! +!! All others are considered relative. + FUNCTION PathIsRelative ( GivenFil ) + + ! Argument declarations. + + CHARACTER(*), INTENT(IN) :: GivenFil !< The name of the given file. + LOGICAL :: PathIsRelative !< The function return value + + + + ! Determine if file name begins with an absolute path name or if it is relative + ! note that Doxygen has serious issues if you use the single quote instead of + ! double quote characters in the strings below: + + PathIsRelative = .FALSE. + + IF ( ( INDEX( GivenFil, ":/") == 0 ) .AND. ( INDEX( GivenFil, ":\") == 0 ) ) THEN ! No drive is specified (by ":\" or ":/") + + IF ( INDEX( "/\", GivenFil(1:1) ) == 0 ) THEN ! The file name doesn't start with "\" or "/" + + PathIsRelative = .TRUE. + + END IF + + END IF + + RETURN + END FUNCTION PathIsRelative +!======================================================================= +! ------------------------------------------------------ + ! Read Open Loop Control Inputs + ! + ! Timeseries or lookup tables of the form + ! index (time or wind speed) channel_1 \t channel_2 \t channel_3 ... + ! This could be used to read any group of data of unspecified length ... +SUBROUTINE Read_OL_Input(OL_InputFileName, Unit_OL_Input, NumChannels, Channels, ErrVar) + + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024), INTENT(IN) :: OL_InputFileName ! DISCON input filename + INTEGER(IntKi), INTENT(IN) :: Unit_OL_Input + INTEGER(IntKi), INTENT(IN) :: NumChannels ! Number of open loop channels being defined + ! REAL(DbKi), INTENT(OUT), DIMENSION(:), ALLOCATABLE :: Breakpoints ! Breakpoints of open loop Channels + REAL(DbKi), INTENT(OUT), DIMENSION(:,:), ALLOCATABLE :: Channels ! Open loop channels + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + + LOGICAL :: FileExists + INTEGER :: IOS ! I/O status of OPEN. + CHARACTER(1024) :: Line ! Temp variable for reading whole line from file + INTEGER(IntKi) :: NumComments + INTEGER(IntKi) :: NumDataLines + REAL(DbKi) :: TmpData(NumChannels) ! Temp variable for reading all columns from a line + CHARACTER(15) :: NumString + + INTEGER(IntKi) :: I,J + + CHARACTER(*), PARAMETER :: RoutineName = 'Read_OL_Input' + + !------------------------------------------------------------------------------------------------- + ! Read from input file, borrowed (read: copied) from (Open)FAST team...thanks! + !------------------------------------------------------------------------------------------------- + + !------------------------------------------------------------------------------------------------- + ! Open the file for reading + !------------------------------------------------------------------------------------------------- + + INQUIRE (FILE = OL_InputFileName, EXIST = FileExists) + + IF ( .NOT. FileExists) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = TRIM(OL_InputFileName)// ' does not exist' + + ELSE + + OPEN( Unit_OL_Input, FILE=TRIM(OL_InputFileName), STATUS='OLD', FORM='FORMATTED', IOSTAT=IOS, ACTION='READ' ) + + IF (IOS /= 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Cannot open '//TRIM(OL_InputFileName) + + ELSE + ! Do all the stuff! + !------------------------------------------------------------------------------------------------- + ! Find the number of comment lines + !------------------------------------------------------------------------------------------------- + + LINE = '!' ! Initialize the line for the DO WHILE LOOP + NumComments = -1 ! the last line we read is not a comment, so we'll initialize this to -1 instead of 0 + + DO WHILE ( (INDEX( LINE, '!' ) > 0) .OR. (INDEX( LINE, '#' ) > 0) .OR. (INDEX( LINE, '%' ) > 0) ) ! Lines containing "!" are treated as comment lines + NumComments = NumComments + 1 + + READ(Unit_OL_Input,'( A )',IOSTAT=IOS) LINE + + ! NWTC_IO has some error catching here that we'll skip for now + + END DO !WHILE + + !------------------------------------------------------------------------------------------------- + ! Find the number of data lines + !------------------------------------------------------------------------------------------------- + + NumDataLines = 0 + + READ(LINE,*,IOSTAT=IOS) ( TmpData(I), I=1,NumChannels ) ! this line was read when we were figuring out the comment lines; let's make sure it contains + + DO WHILE (IOS == 0) ! read the rest of the file (until an error occurs) + NumDataLines = NumDataLines + 1 + + READ(Unit_OL_Input,*,IOSTAT=IOS) ( TmpData(I), I=1,NumChannels ) + + END DO !WHILE + + + IF (NumDataLines < 1) THEN + WRITE (NumString,'(I11)') NumComments + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Error: '//TRIM(NumString)//' comment lines were found in the uniform wind file, '// & + 'but the first data line does not contain the proper format.' + CLOSE(Unit_OL_Input) + END IF + + !------------------------------------------------------------------------------------------------- + ! Allocate arrays for the uniform wind data + !------------------------------------------------------------------------------------------------- + ALLOCATE(Channels(NumDataLines,NumChannels)) + + !------------------------------------------------------------------------------------------------- + ! Rewind the file (to the beginning) and skip the comment lines + !------------------------------------------------------------------------------------------------- + + REWIND( Unit_OL_Input ) + + DO I=1,NumComments + READ(Unit_OL_Input,'( A )',IOSTAT=IOS) LINE + END DO !I + + !------------------------------------------------------------------------------------------------- + ! Read the data arrays + !------------------------------------------------------------------------------------------------- + + DO I=1,NumDataLines + + READ(Unit_OL_Input,*,IOSTAT=IOS) ( TmpData(J), J=1,NumChannels ) + + IF (IOS > 0) THEN + CLOSE(Unit_OL_Input) + END IF + + Channels(I,:) = TmpData + + END DO !I + END IF + END IF + + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + +END SUBROUTINE Read_OL_Input + +!======================================================================= +!> This routine returns the next unit number greater than 9 that is not currently in use. +!! If it cannot find any unit between 10 and 99 that is available, it either aborts or returns an appropriate error status/message. + SUBROUTINE GetNewUnit ( UnIn, ErrVar ) + + + + ! Argument declarations. + + INTEGER, INTENT(OUT) :: UnIn !< Logical unit for the file. !< The error message, if an error occurred + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + + + ! Local declarations. + + INTEGER :: Un ! Unit number + LOGICAL :: Opened ! Flag indicating whether or not a file is opened. + INTEGER(IntKi), PARAMETER :: StartUnit = 10 ! Starting unit number to check (numbers less than 10 reserved) + INTEGER(IntKi), PARAMETER :: MaxUnit = 99 ! The maximum unit number available (or 10 less than the number of files you want to have open at a time) + + + ! Initialize subroutine outputs + + Un = StartUnit + + ! See if unit is connected to an open file. Check the next largest number until it is not opened. + + DO + + INQUIRE ( UNIT=Un , OPENED=Opened ) + + IF ( .NOT. Opened ) EXIT + Un = Un + 1 + + IF ( Un > MaxUnit ) THEN + + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'GetNewUnit() was unable to find an open file unit specifier between '//TRIM(Int2LStr(StartUnit)) & + //' and '//TRIM(Int2LStr(MaxUnit))//'.' + + EXIT ! stop searching now + + END IF + + + END DO + + UnIn = Un + + RETURN + END SUBROUTINE GetNewUnit + +!======================================================================= +!> This function returns a character string encoded with the time in the form "hh:mm:ss". + FUNCTION CurTime( ) + + ! Function declaration. + + CHARACTER(8) :: CurTime !< The current time in the form "hh:mm:ss". + + + ! Local declarations. + + CHARACTER(10) :: CTime ! String to hold the returned value from the DATE_AND_TIME subroutine call. + + + + CALL DATE_AND_TIME ( TIME=CTime ) + + CurTime = CTime(1:2)//':'//CTime(3:4)//':'//CTime(5:6) + + + RETURN + END FUNCTION CurTime + +!======================================================================= +! This function checks whether an array is non-decreasing + LOGICAL Function NonDecreasing(Array) + + IMPLICIT NONE + + REAL(DbKi), DIMENSION(:) :: Array + INTEGER(IntKi) :: I_DIFF + + NonDecreasing = .TRUE. + ! Is Array non decreasing + DO I_DIFF = 1, size(Array) - 1 + IF (Array(I_DIFF + 1) - Array(I_DIFF) <= 0) THEN + NonDecreasing = .FALSE. + RETURN + END IF + END DO + + RETURN + END FUNCTION NonDecreasing + +!======================================================================= +!> This routine converts all the text in a string to upper case. + SUBROUTINE Conv2UC ( Str ) + + ! Argument declarations. + + CHARACTER(*), INTENT(INOUT) :: Str !< The string to be converted to UC (upper case). + + + ! Local declarations. + + INTEGER :: IC ! Character index + + + + DO IC=1,LEN_TRIM( Str ) + + IF ( ( Str(IC:IC) >= 'a' ).AND.( Str(IC:IC) <= 'z' ) ) THEN + Str(IC:IC) = CHAR( ICHAR( Str(IC:IC) ) - 32 ) + END IF + + END DO ! IC + + + RETURN + END SUBROUTINE Conv2UC + +!======================================================================= + !> This function returns a left-adjusted string representing the passed numeric value. + !! It eliminates trailing zeroes and even the decimal point if it is not a fraction. \n + !! Use Num2LStr (nwtc_io::num2lstr) instead of directly calling a specific routine in the generic interface. + FUNCTION Int2LStr ( Num ) + + CHARACTER(11) :: Int2LStr !< string representing input number. + + + ! Argument declarations. + + INTEGER, INTENT(IN) :: Num !< The number to convert to a left-justified string. + + + + WRITE (Int2LStr,'(I11)') Num + + Int2Lstr = ADJUSTL( Int2LStr ) + + + RETURN + END FUNCTION Int2LStr + + +END MODULE ROSCO_Helpers \ No newline at end of file diff --git a/ROSCO/src/ROSCO_IO.f90 b/ROSCO/src/ROSCO_IO.f90 index ba4d64b0..d495263b 100644 --- a/ROSCO/src/ROSCO_IO.f90 +++ b/ROSCO/src/ROSCO_IO.f90 @@ -1,5 +1,5 @@ ! ROSCO IO -! This file is automatically generated by write_registry.py using ROSCO v2.5.0 +! This file is automatically generated by write_registry.py using ROSCO v2.6.0 ! For any modification to the registry, please edit the rosco_types.yaml accordingly MODULE ROSCO_IO @@ -11,14 +11,15 @@ MODULE ROSCO_IO CONTAINS -SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAME) +SUBROUTINE WriteRestartFile(LocalVar, CntrPar, ErrVar, objInst, RootName, size_avcOUTNAME) TYPE(LocalVariables), INTENT(IN) :: LocalVar TYPE(ControlParameters), INTENT(INOUT) :: CntrPar TYPE(ObjectInstances), INTENT(INOUT) :: objInst + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName - INTEGER(IntKi), PARAMETER :: Un = 87 ! I/O unit for pack/unpack (checkpoint & restart) + INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart) INTEGER(IntKi) :: I ! Generic index. CHARACTER(128) :: InFile ! Input checkpoint file INTEGER(IntKi) :: ErrStat @@ -26,7 +27,8 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM CHARACTER(128) :: n_t_global ! timestep number as a string WRITE(n_t_global, '(I0.0)' ) NINT(LocalVar%Time/LocalVar%DT) - InFile = RootName(1:size_avcOUTNAME-5)//TRIM( n_t_global )//'.RO.chkp' + InFile = TRIM(RootName)//TRIM( n_t_global )//'.RO.chkp' + CALL GetNewUnit(Un, ErrVar) OPEN(unit=Un, FILE=TRIM(InFile), STATUS='UNKNOWN', FORM='UNFORMATTED' , ACCESS='STREAM', IOSTAT=ErrStat, ACTION='WRITE' ) IF ( ErrStat /= 0 ) THEN @@ -39,7 +41,8 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM WRITE( Un, IOSTAT=ErrStat) LocalVar%VS_GenPwr WRITE( Un, IOSTAT=ErrStat) LocalVar%GenSpeed WRITE( Un, IOSTAT=ErrStat) LocalVar%RotSpeed - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_M + WRITE( Un, IOSTAT=ErrStat) LocalVar%NacHeading + WRITE( Un, IOSTAT=ErrStat) LocalVar%NacVane WRITE( Un, IOSTAT=ErrStat) LocalVar%HorWindV WRITE( Un, IOSTAT=ErrStat) LocalVar%rootMOOP(1) WRITE( Un, IOSTAT=ErrStat) LocalVar%rootMOOP(2) @@ -86,10 +89,17 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisYaw_1P WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisTilt_2P WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisYaw_2P + WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_KI(1) + WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_KI(2) + WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_KP(1) + WRITE( Un, IOSTAT=ErrStat) LocalVar%IPC_KP(2) WRITE( Un, IOSTAT=ErrStat) LocalVar%PC_State WRITE( Un, IOSTAT=ErrStat) LocalVar%PitCom(1) WRITE( Un, IOSTAT=ErrStat) LocalVar%PitCom(2) WRITE( Un, IOSTAT=ErrStat) LocalVar%PitCom(3) + WRITE( Un, IOSTAT=ErrStat) LocalVar%PitComAct(1) + WRITE( Un, IOSTAT=ErrStat) LocalVar%PitComAct(2) + WRITE( Un, IOSTAT=ErrStat) LocalVar%PitComAct(3) WRITE( Un, IOSTAT=ErrStat) LocalVar%SS_DelOmegaF WRITE( Un, IOSTAT=ErrStat) LocalVar%TestType WRITE( Un, IOSTAT=ErrStat) LocalVar%VS_MaxTq @@ -106,11 +116,6 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM WRITE( Un, IOSTAT=ErrStat) LocalVar%WE_VwI WRITE( Un, IOSTAT=ErrStat) LocalVar%WE_VwIdot WRITE( Un, IOSTAT=ErrStat) LocalVar%VS_LastGenTrqF - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_AccErr - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_ErrLPFFast - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_ErrLPFSlow - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_MErr - WRITE( Un, IOSTAT=ErrStat) LocalVar%Y_YawEndT WRITE( Un, IOSTAT=ErrStat) LocalVar%SD WRITE( Un, IOSTAT=ErrStat) LocalVar%Fl_PitCom WRITE( Un, IOSTAT=ErrStat) LocalVar%NACIMU_FA_AccF @@ -182,17 +187,18 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM END SUBROUTINE WriteRestartFile -SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, ErrVar) +SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, zmqVar, ErrVar) TYPE(LocalVariables), INTENT(INOUT) :: LocalVar TYPE(ControlParameters), INTENT(INOUT) :: CntrPar TYPE(ObjectInstances), INTENT(INOUT) :: objInst TYPE(PerformanceData), INTENT(INOUT) :: PerfData TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar REAL(C_FLOAT), INTENT(IN) :: avrSWAP(*) INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName - INTEGER(IntKi), PARAMETER :: Un = 87 ! I/O unit for pack/unpack (checkpoint & restart) + INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart) INTEGER(IntKi) :: I ! Generic index. CHARACTER(128) :: InFile ! Input checkpoint file INTEGER(IntKi) :: ErrStat @@ -200,7 +206,8 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa CHARACTER(128) :: n_t_global ! timestep number as a string WRITE(n_t_global, '(I0.0)' ) NINT(avrSWAP(2)/avrSWAP(3)) - InFile = RootName(1:size_avcOUTNAME-5)//TRIM( n_t_global )//'.RO.chkp' + InFile = TRIM(RootName)//TRIM( n_t_global )//'.RO.chkp' + CALL GetNewUnit(Un, ErrVar) OPEN(unit=Un, FILE=TRIM(InFile), STATUS='UNKNOWN', FORM='UNFORMATTED' , ACCESS='STREAM', IOSTAT=ErrStat, ACTION='READ' ) IF ( ErrStat /= 0 ) THEN @@ -213,7 +220,8 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa READ( Un, IOSTAT=ErrStat) LocalVar%VS_GenPwr READ( Un, IOSTAT=ErrStat) LocalVar%GenSpeed READ( Un, IOSTAT=ErrStat) LocalVar%RotSpeed - READ( Un, IOSTAT=ErrStat) LocalVar%Y_M + READ( Un, IOSTAT=ErrStat) LocalVar%NacHeading + READ( Un, IOSTAT=ErrStat) LocalVar%NacVane READ( Un, IOSTAT=ErrStat) LocalVar%HorWindV READ( Un, IOSTAT=ErrStat) LocalVar%rootMOOP(1) READ( Un, IOSTAT=ErrStat) LocalVar%rootMOOP(2) @@ -260,10 +268,17 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa READ( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisYaw_1P READ( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisTilt_2P READ( Un, IOSTAT=ErrStat) LocalVar%IPC_AxisYaw_2P + READ( Un, IOSTAT=ErrStat) LocalVar%IPC_KI(1) + READ( Un, IOSTAT=ErrStat) LocalVar%IPC_KI(2) + READ( Un, IOSTAT=ErrStat) LocalVar%IPC_KP(1) + READ( Un, IOSTAT=ErrStat) LocalVar%IPC_KP(2) READ( Un, IOSTAT=ErrStat) LocalVar%PC_State READ( Un, IOSTAT=ErrStat) LocalVar%PitCom(1) READ( Un, IOSTAT=ErrStat) LocalVar%PitCom(2) READ( Un, IOSTAT=ErrStat) LocalVar%PitCom(3) + READ( Un, IOSTAT=ErrStat) LocalVar%PitComAct(1) + READ( Un, IOSTAT=ErrStat) LocalVar%PitComAct(2) + READ( Un, IOSTAT=ErrStat) LocalVar%PitComAct(3) READ( Un, IOSTAT=ErrStat) LocalVar%SS_DelOmegaF READ( Un, IOSTAT=ErrStat) LocalVar%TestType READ( Un, IOSTAT=ErrStat) LocalVar%VS_MaxTq @@ -280,11 +295,6 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa READ( Un, IOSTAT=ErrStat) LocalVar%WE_VwI READ( Un, IOSTAT=ErrStat) LocalVar%WE_VwIdot READ( Un, IOSTAT=ErrStat) LocalVar%VS_LastGenTrqF - READ( Un, IOSTAT=ErrStat) LocalVar%Y_AccErr - READ( Un, IOSTAT=ErrStat) LocalVar%Y_ErrLPFFast - READ( Un, IOSTAT=ErrStat) LocalVar%Y_ErrLPFSlow - READ( Un, IOSTAT=ErrStat) LocalVar%Y_MErr - READ( Un, IOSTAT=ErrStat) LocalVar%Y_YawEndT READ( Un, IOSTAT=ErrStat) LocalVar%SD READ( Un, IOSTAT=ErrStat) LocalVar%Fl_PitCom READ( Un, IOSTAT=ErrStat) LocalVar%NACIMU_FA_AccF @@ -355,27 +365,28 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa Close ( Un ) ENDIF ! Read Parameter files - CALL ReadControlParameterFileSub(CntrPar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, ErrVar) + CALL ReadControlParameterFileSub(CntrPar, zmqVar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, ErrVar) IF (CntrPar%WE_Mode > 0) THEN CALL READCpFile(CntrPar, PerfData, ErrVar) ENDIF END SUBROUTINE ReadRestartFile -SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME) +SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, ErrVar, avrSWAP, RootName, size_avcOUTNAME) ! Debug routine, defines what gets printed to DEBUG.dbg if LoggingLevel = 1 TYPE(ControlParameters), INTENT(IN) :: CntrPar TYPE(LocalVariables), INTENT(IN) :: LocalVar TYPE(DebugVariables), INTENT(IN) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME INTEGER(IntKi) :: I , nDebugOuts, nLocalVars ! Generic index. CHARACTER(1), PARAMETER :: Tab = CHAR(9) ! The tab character. CHARACTER(29), PARAMETER :: FmtDat = "(F20.5,TR5,99(ES20.5E2,TR5:))" ! The format of the debugging data - INTEGER(IntKi), PARAMETER :: UnDb = 85 ! I/O unit for the debugging information - INTEGER(IntKi), PARAMETER :: UnDb2 = 86 ! I/O unit for the debugging information, avrSWAP - INTEGER(IntKi), PARAMETER :: UnDb3 = 87 ! I/O unit for the debugging information, avrSWAP + INTEGER(IntKi), SAVE :: UnDb ! I/O unit for the debugging information + INTEGER(IntKi), SAVE :: UnDb2 ! I/O unit for the debugging information, avrSWAP + INTEGER(IntKi), SAVE :: UnDb3 ! I/O unit for the debugging information, avrSWAP REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName ! a Fortran version of the input C string (not considered an array here) [subtract 1 for the C null-character] CHARACTER(200) :: Version ! git version of ROSCO @@ -385,7 +396,7 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME CHARACTER(15), ALLOCATABLE :: LocalVarOutStrings(:) REAL(DbKi), ALLOCATABLE :: LocalVarOutData(:) - nDebugOuts = 19 + nDebugOuts = 24 Allocate(DebugOutData(nDebugOuts)) Allocate(DebugOutStrings(nDebugOuts)) Allocate(DebugOutUnits(nDebugOuts)) @@ -408,15 +419,22 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME DebugOutData(17) = DebugVar%axisYaw_1P DebugOutData(18) = DebugVar%axisTilt_2P DebugOutData(19) = DebugVar%axisYaw_2P + DebugOutData(20) = DebugVar%YawRateCom + DebugOutData(21) = DebugVar%NacHeadingTarget + DebugOutData(22) = DebugVar%NacVaneOffset + DebugOutData(23) = DebugVar%Yaw_err + DebugOutData(24) = DebugVar%YawState DebugOutStrings = [CHARACTER(15) :: 'WE_Cp', 'WE_b', 'WE_w', 'WE_t', 'WE_Vm', & 'WE_Vt', 'WE_Vw', 'WE_lambda', 'PC_PICommand', 'GenSpeedF', & 'RotSpeedF', 'NacIMU_FA_AccF', 'FA_AccF', 'Fl_PitCom', 'PC_MinPit', & - 'axisTilt_1P', 'axisYaw_1P', 'axisTilt_2P', 'axisYaw_2P'] + 'axisTilt_1P', 'axisYaw_1P', 'axisTilt_2P', 'axisYaw_2P', 'YawRateCom', & + 'NacHeadingTarget', 'NacVaneOffset', 'Yaw_err', 'YawState'] DebugOutUnits = [CHARACTER(15) :: '[-]', '[-]', '[-]', '[-]', '[m/s]', & '[m/s]', '[m/s]', '[rad]', '[rad]', '[rad/s]', & '[rad/s]', '[rad/s]', '[m/s]', '[rad]', '[rad]', & - '', '', '', ''] - nLocalVars = 70 + '', '', '', '', '[rad/s]', & + '[rad]', '[rad]', '[rad]', ''] + nLocalVars = 69 Allocate(LocalVarOutData(nLocalVars)) Allocate(LocalVarOutStrings(nLocalVars)) LocalVarOutData(1) = LocalVar%iStatus @@ -425,126 +443,126 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME LocalVarOutData(4) = LocalVar%VS_GenPwr LocalVarOutData(5) = LocalVar%GenSpeed LocalVarOutData(6) = LocalVar%RotSpeed - LocalVarOutData(7) = LocalVar%Y_M - LocalVarOutData(8) = LocalVar%HorWindV - LocalVarOutData(9) = LocalVar%rootMOOP(1) - LocalVarOutData(10) = LocalVar%rootMOOPF(1) - LocalVarOutData(11) = LocalVar%BlPitch(1) - LocalVarOutData(12) = LocalVar%Azimuth - LocalVarOutData(13) = LocalVar%NumBl - LocalVarOutData(14) = LocalVar%FA_Acc - LocalVarOutData(15) = LocalVar%NacIMU_FA_Acc - LocalVarOutData(16) = LocalVar%FA_AccHPF - LocalVarOutData(17) = LocalVar%FA_AccHPFI - LocalVarOutData(18) = LocalVar%FA_PitCom(1) - LocalVarOutData(19) = LocalVar%RotSpeedF - LocalVarOutData(20) = LocalVar%GenSpeedF - LocalVarOutData(21) = LocalVar%GenTq - LocalVarOutData(22) = LocalVar%GenTqMeas - LocalVarOutData(23) = LocalVar%GenArTq - LocalVarOutData(24) = LocalVar%GenBrTq - LocalVarOutData(25) = LocalVar%IPC_PitComF(1) - LocalVarOutData(26) = LocalVar%PC_KP - LocalVarOutData(27) = LocalVar%PC_KI - LocalVarOutData(28) = LocalVar%PC_KD - LocalVarOutData(29) = LocalVar%PC_TF - LocalVarOutData(30) = LocalVar%PC_MaxPit - LocalVarOutData(31) = LocalVar%PC_MinPit - LocalVarOutData(32) = LocalVar%PC_PitComT - LocalVarOutData(33) = LocalVar%PC_PitComT_Last - LocalVarOutData(34) = LocalVar%PC_PitComTF - LocalVarOutData(35) = LocalVar%PC_PitComT_IPC(1) - LocalVarOutData(36) = LocalVar%PC_PwrErr - LocalVarOutData(37) = LocalVar%PC_SpdErr - LocalVarOutData(38) = LocalVar%IPC_AxisTilt_1P - LocalVarOutData(39) = LocalVar%IPC_AxisYaw_1P - LocalVarOutData(40) = LocalVar%IPC_AxisTilt_2P - LocalVarOutData(41) = LocalVar%IPC_AxisYaw_2P - LocalVarOutData(42) = LocalVar%PC_State - LocalVarOutData(43) = LocalVar%PitCom(1) - LocalVarOutData(44) = LocalVar%SS_DelOmegaF - LocalVarOutData(45) = LocalVar%TestType - LocalVarOutData(46) = LocalVar%VS_MaxTq - LocalVarOutData(47) = LocalVar%VS_LastGenTrq - LocalVarOutData(48) = LocalVar%VS_LastGenPwr - LocalVarOutData(49) = LocalVar%VS_MechGenPwr - LocalVarOutData(50) = LocalVar%VS_SpdErrAr - LocalVarOutData(51) = LocalVar%VS_SpdErrBr - LocalVarOutData(52) = LocalVar%VS_SpdErr - LocalVarOutData(53) = LocalVar%VS_State - LocalVarOutData(54) = LocalVar%VS_Rgn3Pitch - LocalVarOutData(55) = LocalVar%WE_Vw - LocalVarOutData(56) = LocalVar%WE_Vw_F - LocalVarOutData(57) = LocalVar%WE_VwI - LocalVarOutData(58) = LocalVar%WE_VwIdot - LocalVarOutData(59) = LocalVar%VS_LastGenTrqF - LocalVarOutData(60) = LocalVar%Y_AccErr - LocalVarOutData(61) = LocalVar%Y_ErrLPFFast - LocalVarOutData(62) = LocalVar%Y_ErrLPFSlow - LocalVarOutData(63) = LocalVar%Y_MErr - LocalVarOutData(64) = LocalVar%Y_YawEndT - LocalVarOutData(65) = LocalVar%Fl_PitCom - LocalVarOutData(66) = LocalVar%NACIMU_FA_AccF - LocalVarOutData(67) = LocalVar%FA_AccF - LocalVarOutData(68) = LocalVar%Flp_Angle(1) - LocalVarOutData(69) = LocalVar%RootMyb_Last(1) - LocalVarOutData(70) = LocalVar%ACC_INFILE_SIZE + LocalVarOutData(7) = LocalVar%NacHeading + LocalVarOutData(8) = LocalVar%NacVane + LocalVarOutData(9) = LocalVar%HorWindV + LocalVarOutData(10) = LocalVar%rootMOOP(1) + LocalVarOutData(11) = LocalVar%rootMOOPF(1) + LocalVarOutData(12) = LocalVar%BlPitch(1) + LocalVarOutData(13) = LocalVar%Azimuth + LocalVarOutData(14) = LocalVar%NumBl + LocalVarOutData(15) = LocalVar%FA_Acc + LocalVarOutData(16) = LocalVar%NacIMU_FA_Acc + LocalVarOutData(17) = LocalVar%FA_AccHPF + LocalVarOutData(18) = LocalVar%FA_AccHPFI + LocalVarOutData(19) = LocalVar%FA_PitCom(1) + LocalVarOutData(20) = LocalVar%RotSpeedF + LocalVarOutData(21) = LocalVar%GenSpeedF + LocalVarOutData(22) = LocalVar%GenTq + LocalVarOutData(23) = LocalVar%GenTqMeas + LocalVarOutData(24) = LocalVar%GenArTq + LocalVarOutData(25) = LocalVar%GenBrTq + LocalVarOutData(26) = LocalVar%IPC_PitComF(1) + LocalVarOutData(27) = LocalVar%PC_KP + LocalVarOutData(28) = LocalVar%PC_KI + LocalVarOutData(29) = LocalVar%PC_KD + LocalVarOutData(30) = LocalVar%PC_TF + LocalVarOutData(31) = LocalVar%PC_MaxPit + LocalVarOutData(32) = LocalVar%PC_MinPit + LocalVarOutData(33) = LocalVar%PC_PitComT + LocalVarOutData(34) = LocalVar%PC_PitComT_Last + LocalVarOutData(35) = LocalVar%PC_PitComTF + LocalVarOutData(36) = LocalVar%PC_PitComT_IPC(1) + LocalVarOutData(37) = LocalVar%PC_PwrErr + LocalVarOutData(38) = LocalVar%PC_SpdErr + LocalVarOutData(39) = LocalVar%IPC_AxisTilt_1P + LocalVarOutData(40) = LocalVar%IPC_AxisYaw_1P + LocalVarOutData(41) = LocalVar%IPC_AxisTilt_2P + LocalVarOutData(42) = LocalVar%IPC_AxisYaw_2P + LocalVarOutData(43) = LocalVar%IPC_KI(1) + LocalVarOutData(44) = LocalVar%IPC_KP(1) + LocalVarOutData(45) = LocalVar%PC_State + LocalVarOutData(46) = LocalVar%PitCom(1) + LocalVarOutData(47) = LocalVar%PitComAct(1) + LocalVarOutData(48) = LocalVar%SS_DelOmegaF + LocalVarOutData(49) = LocalVar%TestType + LocalVarOutData(50) = LocalVar%VS_MaxTq + LocalVarOutData(51) = LocalVar%VS_LastGenTrq + LocalVarOutData(52) = LocalVar%VS_LastGenPwr + LocalVarOutData(53) = LocalVar%VS_MechGenPwr + LocalVarOutData(54) = LocalVar%VS_SpdErrAr + LocalVarOutData(55) = LocalVar%VS_SpdErrBr + LocalVarOutData(56) = LocalVar%VS_SpdErr + LocalVarOutData(57) = LocalVar%VS_State + LocalVarOutData(58) = LocalVar%VS_Rgn3Pitch + LocalVarOutData(59) = LocalVar%WE_Vw + LocalVarOutData(60) = LocalVar%WE_Vw_F + LocalVarOutData(61) = LocalVar%WE_VwI + LocalVarOutData(62) = LocalVar%WE_VwIdot + LocalVarOutData(63) = LocalVar%VS_LastGenTrqF + LocalVarOutData(64) = LocalVar%Fl_PitCom + LocalVarOutData(65) = LocalVar%NACIMU_FA_AccF + LocalVarOutData(66) = LocalVar%FA_AccF + LocalVarOutData(67) = LocalVar%Flp_Angle(1) + LocalVarOutData(68) = LocalVar%RootMyb_Last(1) + LocalVarOutData(69) = LocalVar%ACC_INFILE_SIZE LocalVarOutStrings = [CHARACTER(15) :: 'iStatus', 'Time', 'DT', 'VS_GenPwr', 'GenSpeed', & - 'RotSpeed', 'Y_M', 'HorWindV', 'rootMOOP', 'rootMOOPF', & - 'BlPitch', 'Azimuth', 'NumBl', 'FA_Acc', 'NacIMU_FA_Acc', & - 'FA_AccHPF', 'FA_AccHPFI', 'FA_PitCom', 'RotSpeedF', 'GenSpeedF', & - 'GenTq', 'GenTqMeas', 'GenArTq', 'GenBrTq', 'IPC_PitComF', & - 'PC_KP', 'PC_KI', 'PC_KD', 'PC_TF', 'PC_MaxPit', & - 'PC_MinPit', 'PC_PitComT', 'PC_PitComT_Last', 'PC_PitComTF', 'PC_PitComT_IPC', & - 'PC_PwrErr', 'PC_SpdErr', 'IPC_AxisTilt_1P', 'IPC_AxisYaw_1P', 'IPC_AxisTilt_2P', & - 'IPC_AxisYaw_2P', 'PC_State', 'PitCom', 'SS_DelOmegaF', 'TestType', & - 'VS_MaxTq', 'VS_LastGenTrq', 'VS_LastGenPwr', 'VS_MechGenPwr', 'VS_SpdErrAr', & - 'VS_SpdErrBr', 'VS_SpdErr', 'VS_State', 'VS_Rgn3Pitch', 'WE_Vw', & - 'WE_Vw_F', 'WE_VwI', 'WE_VwIdot', 'VS_LastGenTrqF', 'Y_AccErr', & - 'Y_ErrLPFFast', 'Y_ErrLPFSlow', 'Y_MErr', 'Y_YawEndT', 'Fl_PitCom', & - 'NACIMU_FA_AccF', 'FA_AccF', 'Flp_Angle', 'RootMyb_Last', 'ACC_INFILE_SIZE' & - ] + 'RotSpeed', 'NacHeading', 'NacVane', 'HorWindV', 'rootMOOP', & + 'rootMOOPF', 'BlPitch', 'Azimuth', 'NumBl', 'FA_Acc', & + 'NacIMU_FA_Acc', 'FA_AccHPF', 'FA_AccHPFI', 'FA_PitCom', 'RotSpeedF', & + 'GenSpeedF', 'GenTq', 'GenTqMeas', 'GenArTq', 'GenBrTq', & + 'IPC_PitComF', 'PC_KP', 'PC_KI', 'PC_KD', 'PC_TF', & + 'PC_MaxPit', 'PC_MinPit', 'PC_PitComT', 'PC_PitComT_Last', 'PC_PitComTF', & + 'PC_PitComT_IPC', 'PC_PwrErr', 'PC_SpdErr', 'IPC_AxisTilt_1P', 'IPC_AxisYaw_1P', & + 'IPC_AxisTilt_2P', 'IPC_AxisYaw_2P', 'IPC_KI', 'IPC_KP', 'PC_State', & + 'PitCom', 'PitComAct', 'SS_DelOmegaF', 'TestType', 'VS_MaxTq', & + 'VS_LastGenTrq', 'VS_LastGenPwr', 'VS_MechGenPwr', 'VS_SpdErrAr', 'VS_SpdErrBr', & + 'VS_SpdErr', 'VS_State', 'VS_Rgn3Pitch', 'WE_Vw', 'WE_Vw_F', & + 'WE_VwI', 'WE_VwIdot', 'VS_LastGenTrqF', 'Fl_PitCom', 'NACIMU_FA_AccF', & + 'FA_AccF', 'Flp_Angle', 'RootMyb_Last', 'ACC_INFILE_SIZE'] ! Initialize debug file IF ((LocalVar%iStatus == 0) .OR. (LocalVar%iStatus == -9)) THEN ! .TRUE. if we're on the first call to the DLL IF (CntrPar%LoggingLevel > 0) THEN - OPEN(unit=UnDb, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg') + CALL GetNewUnit(UnDb, ErrVar) + OPEN(unit=UnDb, FILE=TRIM(RootName)//'.RO.dbg') WRITE(UnDb, *) 'Generated on '//CurDate()//' at '//CurTime()//' using ROSCO-'//TRIM(rosco_version) WRITE(UnDb, '(99(a20,TR5:))') 'Time', DebugOutStrings WRITE(UnDb, '(99(a20,TR5:))') '(sec)', DebugOutUnits END IF IF (CntrPar%LoggingLevel > 1) THEN - OPEN(unit=UnDb2, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg2') + CALL GetNewUnit(UnDb2, ErrVar) + OPEN(unit=UnDb2, FILE=TRIM(RootName)//'.RO.dbg2') WRITE(UnDb2, *) 'Generated on '//CurDate()//' at '//CurTime()//' using ROSCO-'//TRIM(rosco_version) WRITE(UnDb2, '(99(a20,TR5:))') 'Time', LocalVarOutStrings WRITE(UnDb2, '(99(a20,TR5:))') END IF IF (CntrPar%LoggingLevel > 2) THEN - OPEN(unit=UnDb3, FILE=RootName(1: size_avcOUTNAME-5)//'RO.dbg3') + CALL GetNewUnit(UnDb3, ErrVar) + OPEN(unit=UnDb3, FILE=TRIM(RootName)//'.RO.dbg3') WRITE(UnDb3,'(/////)') WRITE(UnDb3,'(A,85("'//Tab//'AvrSWAP(",I2,")"))') 'LocalVar%Time ', (i,i=1, 85) WRITE(UnDb3,'(A,85("'//Tab//'(-)"))') '(s)' END IF - ELSE + END IF ! Print simulation status, every 10 seconds - IF (MODULO(LocalVar%Time, 10.0_DbKi) == 0) THEN - WRITE(*, 100) LocalVar%GenSpeedF*RPS2RPM, LocalVar%BlPitch(1)*R2D, avrSWAP(15)/1000.0, LocalVar%WE_Vw - 100 FORMAT('Generator speed: ', f6.1, ' RPM, Pitch angle: ', f5.1, ' deg, Power: ', f7.1, ' kW, Est. wind Speed: ', f5.1, ' m/s') - END IF + IF (MODULO(LocalVar%Time, 10.0_DbKi) == 0) THEN + WRITE(*, 100) LocalVar%GenSpeedF*RPS2RPM, LocalVar%BlPitch(1)*R2D, avrSWAP(15)/1000.0, LocalVar%WE_Vw + 100 FORMAT('Generator speed: ', f6.1, ' RPM, Pitch angle: ', f5.1, ' deg, Power: ', f7.1, ' kW, Est. wind Speed: ', f5.1, ' m/s') + END IF - ! Write debug files - IF(CntrPar%LoggingLevel > 0) THEN - WRITE (UnDb, FmtDat) LocalVar%Time, DebugOutData - END IF + ! Write debug files + IF(CntrPar%LoggingLevel > 0) THEN + WRITE (UnDb, FmtDat) LocalVar%Time, DebugOutData + END IF - IF(CntrPar%LoggingLevel > 1) THEN - WRITE (UnDb2, FmtDat) LocalVar%Time, LocalVarOutData - END IF + IF(CntrPar%LoggingLevel > 1) THEN + WRITE (UnDb2, FmtDat) LocalVar%Time, LocalVarOutData + END IF - IF(CntrPar%LoggingLevel > 2) THEN - WRITE (UnDb3, FmtDat) LocalVar%Time, avrSWAP(1: 85) - END IF + IF(CntrPar%LoggingLevel > 2) THEN + WRITE (UnDb3, FmtDat) LocalVar%Time, avrSWAP(1: 85) END IF END SUBROUTINE Debug diff --git a/ROSCO/src/ROSCO_Types.f90 b/ROSCO/src/ROSCO_Types.f90 index 56841b63..46c26a09 100644 --- a/ROSCO/src/ROSCO_Types.f90 +++ b/ROSCO/src/ROSCO_Types.f90 @@ -1,5 +1,5 @@ ! ROSCO Registry -! This file is automatically generated by write_registry.py using ROSCO v2.5.0 +! This file is automatically generated by write_registry.py using ROSCO v2.6.0 ! For any modification to the registry, please edit the rosco_types.yaml accordingly MODULE ROSCO_Types @@ -19,11 +19,14 @@ MODULE ROSCO_Types REAL(DbKi) :: F_WECornerFreq ! Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s] REAL(DbKi), DIMENSION(:), ALLOCATABLE :: F_FlCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s]. REAL(DbKi) :: F_FlHighPassFreq ! Natural frequency of first-roder high-pass filter for nacelle fore-aft motion [rad/s]. + REAL(DbKi) :: F_YawErr ! Corner low pass filter corner frequency for yaw controller [rad/s]. REAL(DbKi), DIMENSION(:), ALLOCATABLE :: F_FlpCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the blade root bending moment for flap control [rad/s]. + INTEGER(IntKi) :: TD_Mode ! Tower damper mode (0- no tower damper, 1- feed back translational nacelle accelleration to pitch angle REAL(DbKi) :: FA_HPFCornerFreq ! Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] REAL(DbKi) :: FA_IntSat ! Integrator saturation (maximum signal amplitude contrbution to pitch from FA damper), [rad] REAL(DbKi) :: FA_KI ! Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on [rad s/m] INTEGER(IntKi) :: IPC_ControlMode ! Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0 - off, 1 - 1P reductions, 2 - 1P+2P reductions} + REAL(DbKi), DIMENSION(:), ALLOCATABLE :: IPC_Vramp ! Wind speeds for IPC cut-in sigma function [m/s] REAL(DbKi) :: IPC_IntSat ! Integrator saturation (maximum signal amplitude contrbution to pitch from IPC) REAL(DbKi), DIMENSION(:), ALLOCATABLE :: IPC_KP ! Integral gain for the individual pitch controller, [-]. REAL(DbKi), DIMENSION(:), ALLOCATABLE :: IPC_KI ! Integral gain for the individual pitch controller, [-]. @@ -74,18 +77,14 @@ MODULE ROSCO_Types INTEGER(IntKi) :: WE_FOPoles_N ! Number of first-order system poles used in EKF REAL(DbKi), DIMENSION(:), ALLOCATABLE :: WE_FOPoles_v ! Wind speeds corresponding to first-order system poles [m/s] REAL(DbKi), DIMENSION(:), ALLOCATABLE :: WE_FOPoles ! First order system poles - INTEGER(IntKi) :: Y_ControlMode ! Yaw control mode {0 - no yaw control, 1 - yaw rate control, 2 - yaw-by-IPC} - REAL(DbKi) :: Y_ErrThresh ! Error threshold [rad]. Turbine begins to yaw when it passes this. (104.71975512) -- 1.745329252 - REAL(DbKi) :: Y_IPC_IntSat ! Integrator saturation (maximum signal amplitude contrbution to pitch from yaw-by-IPC) - INTEGER(IntKi) :: Y_IPC_n ! Number of controller gains (yaw-by-IPC) - REAL(DbKi), DIMENSION(:), ALLOCATABLE :: Y_IPC_KP ! Yaw-by-IPC proportional controller gain Kp - REAL(DbKi), DIMENSION(:), ALLOCATABLE :: Y_IPC_KI ! Yaw-by-IPC integral controller gain Ki - REAL(DbKi) :: Y_IPC_omegaLP ! Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. - REAL(DbKi) :: Y_IPC_zetaLP ! Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. - REAL(DbKi) :: Y_MErrSet ! Yaw alignment error, setpoint [rad] - REAL(DbKi) :: Y_omegaLPFast ! Corner frequency fast low pass filter, 1.0 [Hz] - REAL(DbKi) :: Y_omegaLPSlow ! Corner frequency slow low pass filter, 1/60 [Hz] + INTEGER(IntKi) :: Y_ControlMode ! Yaw control mode {0 - no yaw control, 1 - yaw rate control} + REAL(DbKi) :: Y_uSwitch ! Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] + REAL(DbKi), DIMENSION(:), ALLOCATABLE :: Y_ErrThresh ! Error threshold [rad]. Turbine begins to yaw when it passes this REAL(DbKi) :: Y_Rate ! Yaw rate [rad/s] + REAL(DbKi) :: Y_MErrSet ! Yaw alignment error, setpoint (for wake steering) [rad] + REAL(DbKi) :: Y_IPC_IntSat ! Integrator saturation (maximum signal amplitude contrbution to pitch from yaw-by-IPC) + REAL(DbKi) :: Y_IPC_KP ! Yaw-by-IPC proportional controller gain Kp + REAL(DbKi) :: Y_IPC_KI ! Yaw-by-IPC integral controller gain Ki INTEGER(IntKi) :: PS_Mode ! Pitch saturation mode {0 - no peak shaving, 1 - implement pitch saturation} INTEGER(IntKi) :: PS_BldPitchMin_N ! Number of values in minimum blade pitch lookup table (should equal number of values in PS_WindSpeeds and PS_BldPitchMin) REAL(DbKi), DIMENSION(:), ALLOCATABLE :: PS_WindSpeeds ! Wind speeds corresponding to minimum blade pitch angles [m/s] @@ -111,6 +110,16 @@ MODULE ROSCO_Types REAL(DbKi), DIMENSION(:), ALLOCATABLE :: OL_GenTq ! Open generator torque timeseries REAL(DbKi), DIMENSION(:), ALLOCATABLE :: OL_YawRate ! Open yaw rate timeseries REAL(DbKi), DIMENSION(:,:), ALLOCATABLE :: OL_Channels ! Open loop channels in timeseries + INTEGER(IntKi) :: PA_Mode ! Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} + REAL(DbKi) :: PA_CornerFreq ! Pitch actuator bandwidth/cut-off frequency [rad/s] + REAL(DbKi) :: PA_Damping ! Pitch actuator damping ratio [-, unused if PA_Mode = 1] + INTEGER(IntKi) :: Ext_Mode ! External control mode (0 - not used, 1 - call external control library) + CHARACTER(1024) :: DLL_FileName ! File name of external dynamic library + CHARACTER(1024) :: DLL_InFile ! Name of input file called by dynamic library (DISCON.IN, e.g.) + CHARACTER(1024) :: DLL_ProcName ! Process name of subprocess called in DLL_Filename (Usually DISCON) + INTEGER(IntKi) :: ZMQ_Mode ! Flag for ZeroMQ (0-off, 1-yaw} + CHARACTER(256) :: ZMQ_CommAddress ! Comm Address to zeroMQ client + REAL(DbKi) :: ZMQ_UpdatePeriod ! Integer for zeromq update frequency REAL(DbKi) :: PC_RtTq99 ! 99% of the rated torque value, using for switching between pitch and torque control, [Nm]. REAL(DbKi) :: VS_MaxOMTq ! Maximum torque at the end of the below-rated region 2, [Nm] REAL(DbKi) :: VS_MinOMTq ! Minimum torque at the beginning of the below-rated region 2, [Nm] @@ -179,7 +188,8 @@ MODULE ROSCO_Types REAL(DbKi) :: VS_GenPwr ! Generator power [W] REAL(DbKi) :: GenSpeed ! Generator speed (HSS) [rad/s] REAL(DbKi) :: RotSpeed ! Rotor speed (LSS) [rad/s] - REAL(DbKi) :: Y_M ! Yaw direction [rad] + REAL(DbKi) :: NacHeading ! Nacelle heading of the turbine w.r.t. north [deg] + REAL(DbKi) :: NacVane ! Nacelle vane angle [deg] REAL(DbKi) :: HorWindV ! Hub height wind speed m/s REAL(DbKi) :: rootMOOP(3) ! Blade root bending moment [Nm] REAL(DbKi) :: rootMOOPF(3) ! Filtered Blade root bending moment [Nm] @@ -214,8 +224,11 @@ MODULE ROSCO_Types REAL(DbKi) :: IPC_AxisYaw_1P ! Integral of quadrature, 1P REAL(DbKi) :: IPC_AxisTilt_2P ! Integral of the direct axis, 2P REAL(DbKi) :: IPC_AxisYaw_2P ! Integral of quadrature, 2P + REAL(DbKi) :: IPC_KI(2) ! Integral gain for IPC, after ramp [-] + REAL(DbKi) :: IPC_KP(2) ! Proportional gain for IPC, after ramp [-] INTEGER(IntKi) :: PC_State ! State of the pitch control system REAL(DbKi) :: PitCom(3) ! Commanded pitch of each blade the last time the controller was called [rad]. + REAL(DbKi) :: PitComAct(3) ! Actuated pitch of each blade the last time the controller was called [rad]. REAL(DbKi) :: SS_DelOmegaF ! Filtered setpoint shifting term defined in setpoint smoother [rad/s]. REAL(DbKi) :: TestType ! Test variable, no use REAL(DbKi) :: VS_MaxTq ! Maximum allowable generator torque [Nm]. @@ -232,11 +245,6 @@ MODULE ROSCO_Types REAL(DbKi) :: WE_VwI ! Integrated wind speed quantity for estimation [m/s] REAL(DbKi) :: WE_VwIdot ! Differentiated integrated wind speed quantity for estimation [m/s] REAL(DbKi) :: VS_LastGenTrqF ! Differentiated integrated wind speed quantity for estimation [m/s] - REAL(DbKi) :: Y_AccErr ! Accumulated yaw error [rad]. - REAL(DbKi) :: Y_ErrLPFFast ! Filtered yaw error by fast low pass filter [rad]. - REAL(DbKi) :: Y_ErrLPFSlow ! Filtered yaw error by slow low pass filter [rad]. - REAL(DbKi) :: Y_MErr ! Measured yaw error, measured + setpoint [rad]. - REAL(DbKi) :: Y_YawEndT ! Yaw end time [s]. Indicates the time up until which yaw is active with a fixed rate LOGICAL :: SD ! Shutdown, .FALSE. if inactive, .TRUE. if active REAL(DbKi) :: Fl_PitCom ! Shutdown, .FALSE. if inactive, .TRUE. if active REAL(DbKi) :: NACIMU_FA_AccF ! None @@ -288,11 +296,17 @@ MODULE ROSCO_Types REAL(DbKi) :: axisYaw_1P ! Yaw component of coleman transformation, 1P REAL(DbKi) :: axisTilt_2P ! Tilt component of coleman transformation, 2P REAL(DbKi) :: axisYaw_2P ! Yaw component of coleman transformation, 2P + REAL(DbKi) :: YawRateCom ! Commanded yaw rate [rad/s]. + REAL(DbKi) :: NacHeadingTarget ! Target nacelle heading [rad]. + REAL(DbKi) :: NacVaneOffset ! Nacelle vane angle with offset [rad]. + REAL(DbKi) :: Yaw_err ! Yaw error [rad]. + REAL(DbKi) :: YawState ! State of yaw controller END TYPE DebugVariables TYPE, PUBLIC :: ErrorVariables INTEGER(IntKi) :: size_avcMSG ! None INTEGER(C_INT) :: aviFAIL ! A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message. + INTEGER(C_INT) :: ErrStat ! An error status flag used by OpenFAST processes CHARACTER(:), ALLOCATABLE :: ErrMsg ! a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] END TYPE ErrorVariables @@ -304,4 +318,13 @@ MODULE ROSCO_Types CHARACTER(1024) :: ProcName(3) = "" ! The name of the procedure in the DLL that will be called. END TYPE ExtDLL_Type +TYPE, PUBLIC :: ZMQ_Variables + LOGICAL :: ZMQ_Flag ! Flag if we're using zeroMQ at all (0-False, 1-True) + REAL(DbKi) :: Yaw_Offset ! Yaw offsety command, [rad] +END TYPE ZMQ_Variables + +TYPE, PUBLIC :: ExtControlType + REAL(C_FLOAT), DIMENSION(:), ALLOCATABLE :: avrSWAP ! The swap array- used to pass data to and from the DLL controller [see Bladed DLL documentation] +END TYPE ExtControlType + END MODULE ROSCO_Types \ No newline at end of file diff --git a/ROSCO/src/ReadSetParameters.f90 b/ROSCO/src/ReadSetParameters.f90 index 74ab40f6..a0ac9e38 100644 --- a/ROSCO/src/ReadSetParameters.f90 +++ b/ROSCO/src/ReadSetParameters.f90 @@ -18,35 +18,20 @@ MODULE ReadSetParameters USE Constants USE Functions USE SysSubs + USE ROSCO_Helpers IMPLICIT NONE - ! Global Variables - LOGICAL, PARAMETER :: DEBUG_PARSING = .FALSE. ! debug flag to output parsing information, set up Echo file later - - INTERFACE ParseInput ! Parses a character variable name and value from a string. - MODULE PROCEDURE ParseInput_Str ! Parses a character string from a string. - MODULE PROCEDURE ParseInput_Dbl ! Parses a double-precision REAL from a string. - MODULE PROCEDURE ParseInput_Int ! Parses an INTEGER from a string. - ! MODULE PROCEDURE ParseInput_Log ! Parses an LOGICAL from a string. - END INTERFACE - - INTERFACE ParseAry ! Parse an array of numbers from a string. - MODULE PROCEDURE ParseDbAry ! Parse an array of double-precision REAL values. - MODULE PROCEDURE ParseInAry ! Parse an array of whole numbers. - END INTERFACE - - CONTAINS ! ----------------------------------------------------------------------------------- ! Read avrSWAP array passed from ServoDyn SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) - USE ROSCO_Types, ONLY : LocalVariables + USE ROSCO_Types, ONLY : LocalVariables, ZMQ_Variables REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. TYPE(LocalVariables), INTENT(INOUT) :: LocalVar - + ! Load variables from calling program (See Appendix A of Bladed User's Guide): LocalVar%iStatus = NINT(avrSWAP(1)) LocalVar%Time = avrSWAP(2) @@ -56,7 +41,7 @@ SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) LocalVar%GenSpeed = avrSWAP(20) LocalVar%RotSpeed = avrSWAP(21) LocalVar%GenTqMeas = avrSWAP(23) - LocalVar%Y_M = avrSWAP(24) + LocalVar%NacVane = avrSWAP(24) * R2D LocalVar%HorWindV = avrSWAP(27) LocalVar%rootMOOP(1) = avrSWAP(30) LocalVar%rootMOOP(2) = avrSWAP(31) @@ -86,18 +71,19 @@ SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) END SUBROUTINE ReadAvrSWAP ! ----------------------------------------------------------------------------------- ! Define parameters for control actions - SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, objInst, PerfData, ErrVar) + SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, objInst, PerfData, zmqVar, ErrVar) - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, PerformanceData, ErrorVariables + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, PerformanceData, ErrorVariables, ZMQ_Variables REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. CHARACTER(C_CHAR), INTENT(IN ) :: accINFILE(NINT(avrSWAP(50))) ! The name of the parameter input file - INTEGER(IntKi), INTENT(IN ) :: size_avcMSG + INTEGER(IntKi), INTENT(IN ) :: size_avcMSG TYPE(ControlParameters), INTENT(INOUT) :: CntrPar TYPE(LocalVariables), INTENT(INOUT) :: LocalVar TYPE(ObjectInstances), INTENT(INOUT) :: objInst TYPE(PerformanceData), INTENT(INOUT) :: PerfData + TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar @@ -153,7 +139,7 @@ SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, obj LocalVar%ACC_INFILE = accINFILE ! Read Control Parameter File - CALL ReadControlParameterFileSub(CntrPar, accINFILE, NINT(avrSWAP(50)),ErrVar) + CALL ReadControlParameterFileSub(CntrPar, zmqVar, accINFILE, NINT(avrSWAP(50)),ErrVar) ! If there's been an file reading error, don't continue ! Add RoutineName to error message IF (ErrVar%aviFAIL < 0) THEN @@ -167,9 +153,7 @@ SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, obj ! Initialize the SAVED variables: LocalVar%PitCom = LocalVar%BlPitch ! This will ensure that the variable speed controller picks the correct control region and the pitch controller picks the correct gain on the first call - LocalVar%Y_AccErr = 0.0 ! This will ensure that the accumulated yaw error starts at zero - LocalVar%Y_YawEndT = -1.0 ! This will ensure that the initial yaw end time is lower than the actual time to prevent initial yawing - + ! Wind speed estimator initialization LocalVar%WE_Vw = LocalVar%HorWindV LocalVar%WE_VwI = LocalVar%WE_Vw - CntrPar%WE_Gamma*LocalVar%RotSpeed @@ -194,22 +178,28 @@ SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, obj ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) ENDIF + ! Check if we're using zeromq + IF (CntrPar%ZMQ_Mode == 1) THEN ! add .OR. statements as more functionality is built in + zmqVar%ZMQ_Flag = .TRUE. + ENDIF ENDIF END SUBROUTINE SetParameters ! ----------------------------------------------------------------------------------- ! Read all constant control parameters from DISCON.IN parameter file - SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar)!, accINFILE_size) + SUBROUTINE ReadControlParameterFileSub(CntrPar, zmqVar, accINFILE, accINFILE_size,ErrVar)!, accINFILE_size) USE, INTRINSIC :: ISO_C_Binding - USE ROSCO_Types, ONLY : ControlParameters, ErrorVariables + USE ROSCO_Types, ONLY : ControlParameters, ErrorVariables, ZMQ_Variables INTEGER(IntKi) :: accINFILE_size ! size of DISCON input filename CHARACTER(accINFILE_size), INTENT(IN ) :: accINFILE(accINFILE_size) ! DISCON input filename - INTEGER(IntKi), PARAMETER :: UnControllerParameters = 89 ! Unit number to open file TYPE(ControlParameters), INTENT(INOUT) :: CntrPar ! Control parameter type - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Control parameter type - + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Control parameter type + TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar ! Control parameter type + + INTEGER(IntKi) :: UnControllerParameters ! Unit number to open file INTEGER(IntKi) :: CurLine + ! INTEGER(IntKi), PARAMETER :: UnControllerParameters = 89 ! Unit number to open file CHARACTER(1024) :: OL_String ! Open description loop string INTEGER(IntKi) :: OL_Count ! Number of open loop channels @@ -223,7 +213,7 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar ! Get primary path of DISCON.IN file (accINFILE(1) here) CALL GetPath( accINFILE(1), PriPath ) ! Input files will be relative to the path where the primary input file is located. - + CALL GetNewUnit(UnControllerParameters, ErrVar) OPEN(unit=UnControllerParameters, file=accINFILE(1), status='old', action='read') !----------------------- HEADER ------------------------ @@ -251,8 +241,12 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar CALL ParseInput(UnControllerParameters,CurLine,'PS_Mode',accINFILE(1),CntrPar%PS_Mode,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'SD_Mode',accINFILE(1),CntrPar%SD_Mode,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'FL_Mode',accINFILE(1),CntrPar%FL_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'TD_Mode',accINFILE(1),CntrPar%TD_Mode,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'Flp_Mode',accINFILE(1),CntrPar%Flp_Mode,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'OL_Mode',accINFILE(1),CntrPar%OL_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PA_Mode',accINFILE(1),CntrPar%PA_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Ext_Mode',accINFILE(1),CntrPar%Ext_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'ZMQ_Mode',accINFILE(1), CntrPar%ZMQ_Mode,ErrVar) CALL ReadEmptyLine(UnControllerParameters,CurLine) @@ -264,6 +258,7 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar CALL ParseAry(UnControllerParameters, CurLine, 'F_NotchBetaNumDen', CntrPar%F_NotchBetaNumDen, 2, accINFILE(1), ErrVar ) CALL ParseInput(UnControllerParameters,CurLine,'F_SSCornerFreq',accINFILE(1),CntrPar%F_SSCornerFreq,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'F_WECornerFreq',accINFILE(1),CntrPar%F_WECornerFreq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'F_YawErr',accINFILE(1),CntrPar%F_YawErr, ErrVar) CALL ParseAry(UnControllerParameters, CurLine, 'F_FlCornerFreq', CntrPar%F_FlCornerFreq, 2, accINFILE(1), ErrVar ) CALL ParseInput(UnControllerParameters,CurLine,'F_FlHighPassFreq',accINFILE(1),CntrPar%F_FlHighPassFreq,ErrVar) CALL ParseAry(UnControllerParameters, CurLine, 'F_FlpCornerFreq', CntrPar%F_FlpCornerFreq, 2, accINFILE(1), ErrVar ) @@ -288,6 +283,7 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar !------------------- IPC CONSTANTS ----------------------- CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseAry(UnControllerParameters, CurLine, 'IPC_Vramp', CntrPar%IPC_Vramp, 2, accINFILE(1), ErrVar ) CALL ParseInput(UnControllerParameters,CurLine,'IPC_IntSat',accINFILE(1),CntrPar%IPC_IntSat,ErrVar) CALL ParseAry(UnControllerParameters, CurLine, 'IPC_KP', CntrPar%IPC_KP, 2, accINFILE(1), ErrVar ) CALL ParseAry(UnControllerParameters, CurLine, 'IPC_KI', CntrPar%IPC_KI, 2, accINFILE(1), ErrVar ) @@ -337,17 +333,13 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar !-------------- YAW CONTROLLER CONSTANTS ----------------- CALL ReadEmptyLine(UnControllerParameters,CurLine) - CALL ParseInput(UnControllerParameters,CurLine,'Y_ErrThresh',accINFILE(1),CntrPar%Y_ErrThresh,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_IntSat',accINFILE(1),CntrPar%Y_IPC_IntSat,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_n',accINFILE(1),CntrPar%Y_IPC_n,ErrVar) - CALL ParseAry(UnControllerParameters, CurLine, 'Y_IPC_KP', CntrPar%Y_IPC_KP, CntrPar%Y_IPC_n, accINFILE(1), ErrVar ) - CALL ParseAry(UnControllerParameters, CurLine, 'Y_IPC_KI', CntrPar%Y_IPC_KI, CntrPar%Y_IPC_n, accINFILE(1), ErrVar ) - CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_omegaLP',accINFILE(1),CntrPar%Y_IPC_omegaLP,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_zetaLP',accINFILE(1),CntrPar%Y_IPC_zetaLP,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_MErrSet',accINFILE(1),CntrPar%Y_MErrSet,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_omegaLPFast',accINFILE(1),CntrPar%Y_omegaLPFast,ErrVar) - CALL ParseInput(UnControllerParameters,CurLine,'Y_omegaLPSlow',accINFILE(1),CntrPar%Y_omegaLPSlow,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_uSwitch',accINFILE(1),CntrPar%Y_uSwitch,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'Y_ErrThresh', CntrPar%Y_ErrThresh, 2, accINFILE(1), ErrVar ) CALL ParseInput(UnControllerParameters,CurLine,'Y_Rate',accINFILE(1),CntrPar%Y_Rate,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_MErrSet',accINFILE(1),CntrPar%Y_MErrSet,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_IntSat',accINFILE(1),CntrPar%Y_IPC_IntSat,ErrVar) + CALL ParseInput(UnControllerParameters, CurLine,'Y_IPC_KP', accINFILE(1), CntrPar%Y_IPC_KP, ErrVar ) + CALL ParseInput(UnControllerParameters, CurLine,'Y_IPC_KI', accINFILE(1), CntrPar%Y_IPC_KI, ErrVar ) CALL ReadEmptyLine(UnControllerParameters,CurLine) !------------ FORE-AFT TOWER DAMPER CONSTANTS ------------ @@ -391,6 +383,25 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar CALL ParseInput(UnControllerParameters,CurLine,'Ind_BldPitch',accINFILE(1),CntrPar%Ind_BldPitch,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'Ind_GenTq',accINFILE(1),CntrPar%Ind_GenTq,ErrVar) CALL ParseInput(UnControllerParameters,CurLine,'Ind_YawRate',accINFILE(1),CntrPar%Ind_YawRate,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ Pitch Actuator Inputs ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'PA_CornerFreq',accINFILE(1),CntrPar%PA_CornerFreq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PA_Damping',accINFILE(1),CntrPar%PA_Damping,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ External control interface ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'DLL_FileName',accINFILE(1),CntrPar%DLL_FileName,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'DLL_InFile',accINFILE(1),CntrPar%DLL_InFile,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'DLL_ProcName',accINFILE(1),CntrPar%DLL_ProcName,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ ZeroMQ ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'ZMQ_CommAddress',accINFILE(1), CntrPar%ZMQ_CommAddress,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'ZMQ_UpdatePeriod',accINFILE(1), CntrPar%ZMQ_UpdatePeriod,ErrVar) ! Fix Paths (add relative paths if called from another dir) IF (PathIsRelative(CntrPar%PerfFileName)) CntrPar%PerfFileName = TRIM(PriPath)//TRIM(CntrPar%PerfFileName) @@ -433,6 +444,9 @@ SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar ENDIF END IF + ! Convert yaw rate to deg/s + CntrPar%Y_Rate = CntrPar%Y_Rate * R2D + ! Debugging outputs (echo someday) ! write(400,*) CntrPar%OL_YawRate @@ -467,7 +481,7 @@ SUBROUTINE ReadCpFile(CntrPar,PerfData, ErrVar) TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Local variables - INTEGER(IntKi), PARAMETER :: UnPerfParameters = 89 + INTEGER(IntKi) :: UnPerfParameters INTEGER(IntKi) :: i ! iteration index INTEGER(IntKi) :: CurLine @@ -475,7 +489,7 @@ SUBROUTINE ReadCpFile(CntrPar,PerfData, ErrVar) REAL(DbKi), DIMENSION(:), ALLOCATABLE :: TmpPerf CurLine = 1 - + CALL GetNewUnit(UnPerfParameters, ErrVar) OPEN(unit=UnPerfParameters, file=TRIM(CntrPar%PerfFileName), status='old', action='read') ! Should put input file into DISCON.IN ! ----------------------- Axis Definitions ------------------------ @@ -514,6 +528,9 @@ SUBROUTINE ReadCpFile(CntrPar,PerfData, ErrVar) READ(UnPerfParameters, *) PerfData%Cq_mat(i,:) ! Read Cq table END DO + ! Close file + CLOSE(UnPerfParameters) + ! Add RoutineName to error message IF (ErrVar%aviFAIL < 0) THEN ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) @@ -902,38 +919,18 @@ SUBROUTINE CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG) ErrVar%ErrMsg = 'WE_FOPoles_v must be non-decreasing.' ENDIF - - ! ---- Yaw Control ---- IF (CntrPar%Y_ControlMode > 0) THEN - IF (CntrPar%Y_IPC_omegaLP <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Y_IPC_omegaLP must be greater than zero.' - ENDIF - - IF (CntrPar%Y_IPC_zetaLP <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Y_IPC_zetaLP must be greater than zero.' - ENDIF - - IF (CntrPar%Y_ErrThresh <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Y_ErrThresh must be greater than zero.' - ENDIF - - IF (CntrPar%Y_Rate <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'CntrPar%Y_Rate must be greater than zero.' - ENDIF - - IF (CntrPar%Y_omegaLPFast <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Y_omegaLPFast must be greater than zero.' - ENDIF - - IF (CntrPar%Y_omegaLPSlow <= 0.0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Y_omegaLPSlow must be greater than zero.' + IF (CntrPar%Y_ControlMode == 1) THEN + IF (CntrPar%Y_ErrThresh(1) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_ErrThresh must be greater than zero.' + ENDIF + + IF (CntrPar%Y_Rate <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'CntrPar%Y_Rate must be greater than zero.' + ENDIF ENDIF ENDIF @@ -971,6 +968,22 @@ SUBROUTINE CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG) ErrVar%aviFAIL = -1 ErrVar%ErrMsg = 'All open loop control indices must be greater than zero' ENDIF + + ! --- Pitch Actuator --- + IF (CntrPar%PA_Mode > 0) THEN + IF ((CntrPar%PA_Mode < 0) .OR. (CntrPar%PA_Mode < 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PA_Mode must be 0, 1, or 2' + END IF + IF (CntrPar%PA_CornerFreq < 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PA_CornerFreq must be greater than 0' + END IF + IF (CntrPar%PA_Damping < 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PA_Damping must be greater than 0' + END IF + END IF @@ -998,798 +1011,4 @@ SUBROUTINE CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG) END SUBROUTINE CheckInputs - !======================================================================= - ! Parse integer input: read line, check that variable name is in line, handle errors - subroutine ParseInput_Int(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) - USE ROSCO_Types, ONLY : ErrorVariables - - CHARACTER(1024) :: Line - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit - CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit - INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - CHARACTER(20) :: Words (2) ! The two "words" parsed from the line - - INTEGER(IntKi), INTENT(INOUT) :: Variable ! Variable - INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. - LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName - - LOGICAL :: CheckName_ - - ! Figure out if we're checking the name, default to .TRUE. - CheckName_ = .TRUE. - if (PRESENT(CheckName)) CheckName_ = CheckName - - ! If we've already failed, don't read anything - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the whole line as a string - READ(Un, '(A)') Line - - ! Separate line string into 2 words - CALL GetWords ( Line, Words, 2 ) - - ! Debugging: show what's being read, turn into Echo later - IF (DEBUG_PARSING) THEN - print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine - END IF - - ! Check that Variable Name is in Words - IF (CheckName_) THEN - CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) - END IF - - ! IF We haven't failed already - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the variable - READ (Words(1),*,IOSTAT=ErrStatLcl) Variable - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & - //TRIM( FileName )//'".'//NewLine// & - ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & - //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& - ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' - ENDIF - - ENDIF - - ! Increment line counter - CurLine = CurLine + 1 - END IF - - END subroutine ParseInput_Int - - !======================================================================= - ! Parse double input, this is a copy of ParseInput_Int and a change in the variable definitions - subroutine ParseInput_Dbl(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) - USE ROSCO_Types, ONLY : ErrorVariables - - CHARACTER(1024) :: Line - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit - CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit - INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - CHARACTER(20) :: Words (2) ! The two "words" parsed from the line - LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName - - REAL(DbKi), INTENT(INOUT) :: Variable ! Variable - INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. - - LOGICAL :: CheckName_ - - ! Figure out if we're checking the name, default to .TRUE. - CheckName_ = .TRUE. - if (PRESENT(CheckName)) CheckName_ = CheckName - - ! If we've already failed, don't read anything - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the whole line as a string - READ(Un, '(A)') Line - - ! Separate line string into 2 words - CALL GetWords ( Line, Words, 2 ) - - ! Debugging: show what's being read, turn into Echo later - IF (DEBUG_PARSING) THEN - print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine - END IF - - ! Check that Variable Name is in Words - IF (CheckName_) THEN - CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) - END IF - - ! IF We haven't failed already - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the variable - READ (Words(1),*,IOSTAT=ErrStatLcl) Variable - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & - //TRIM( FileName )//'".'//NewLine// & - ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & - //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& - ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' - ENDIF - - ENDIF - - ! Increment line counter - CurLine = CurLine + 1 - END IF - - END subroutine ParseInput_Dbl - - !======================================================================= - ! Parse string input, this is a copy of ParseInput_Int and a change in the variable definitions - subroutine ParseInput_Str(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) - USE ROSCO_Types, ONLY : ErrorVariables - - CHARACTER(1024) :: Line - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit - CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit - INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - CHARACTER(200) :: Words (2) ! The two "words" parsed from the line - LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName - - CHARACTER(*), INTENT(INOUT) :: Variable ! Variable - INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. - - LOGICAL :: CheckName_ - - ! Figure out if we're checking the name, default to .TRUE. - CheckName_ = .TRUE. - if (PRESENT(CheckName)) CheckName_ = CheckName - - ! If we've already failed, don't read anything - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the whole line as a string - READ(Un, '(A)') Line - - ! Separate line string into 2 words - CALL GetWords ( Line, Words, 2 ) - - ! Debugging: show what's being read, turn into Echo later - if (DEBUG_PARSING) THEN - print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine - END IF - - ! Check that Variable Name is in Words - IF (CheckName_) THEN - CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) - END IF - - ! IF We haven't failed already - IF (ErrVar%aviFAIL >= 0) THEN - - ! Read the variable - READ (Words(1),'(A)',IOSTAT=ErrStatLcl) Variable - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & - //TRIM( FileName )//'".'//NewLine// & - ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid STRING value on line #' & - //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& - ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' - ENDIF - - ENDIF - - ! Increment line counter - CurLine = CurLine + 1 - END IF - - END subroutine ParseInput_Str - -!======================================================================= -!> This subroutine parses the specified line of text for AryLen REAL values. -!! Generate an error message if the value is the wrong type. -!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. - SUBROUTINE ParseDbAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) - - USE ROSCO_Types, ONLY : ErrorVariables - - ! Arguments declarations. - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. - - REAL(DbKi), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. - - INTEGER(IntKi), INTENT(INOUT) :: LineNum !< The number of the line to parse. - CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. - - - CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. - - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - - LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName - - - ! Local declarations. - - CHARACTER(1024) :: Line - INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. - INTEGER(IntKi) :: i - - CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. - CHARACTER(1024) :: Debug_String - CHARACTER(*), PARAMETER :: RoutineName = 'ParseDbAry' - LOGICAL :: CheckName_ - - ! Figure out if we're checking the name, default to .TRUE. - CheckName_ = .TRUE. - if (PRESENT(CheckName)) CheckName_ = CheckName - - ! If we've already failed, don't read anything - IF (ErrVar%aviFAIL >= 0) THEN - ! Read the whole line as a string - READ(Un, '(A)') Line - - ! Allocate array and handle errors - ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) - IF ( ErrStatLcl /= 0 ) THEN - IF ( ALLOCATED(Ary) ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' - ELSE - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' - END IF - END IF - - ! Allocate words array - ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' - CALL Cleanup() - RETURN - ENDIF - - ! Separate line string into AryLen + 1 words, should include variable name - CALL GetWords ( Line, Words_Ary, AryLen + 1 ) - - ! Debug Output - IF (DEBUG_PARSING) THEN - Debug_String = '' - DO i = 1,AryLen+1 - Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) - IF (i < AryLen + 1) THEN - Debug_String = TRIM(Debug_String)//',' - END IF - END DO - print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum - END IF - - ! Check that Variable Name is at the end of Words, will also check length of array - IF (CheckName_) THEN - CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) - END IF - - ! Read array - READ (Line,*,IOSTAT=ErrStatLcl) Ary - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & - //TRIM( FileName )//'".'//NewLine// & - ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & - //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & - //' "'//TRIM( Line )//'"' - RETURN - CALL Cleanup() - ENDIF - - ! IF ( PRESENT(UnEc) ) THEN - ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) - ! END IF - - LineNum = LineNum + 1 - CALL Cleanup() - ENDIF - - RETURN - - !======================================================================= - CONTAINS - !======================================================================= - SUBROUTINE Cleanup ( ) - - ! This subroutine cleans up the parent routine before exiting. - - ! Deallocate the Words array if it had been allocated. - - IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) - - - RETURN - - END SUBROUTINE Cleanup - - END SUBROUTINE ParseDbAry - - !======================================================================= -!> This subroutine parses the specified line of text for AryLen INTEGER values. -!! Generate an error message if the value is the wrong type. -!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. - SUBROUTINE ParseInAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) - - USE ROSCO_Types, ONLY : ErrorVariables - - ! Arguments declarations. - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. - - INTEGER(IntKi), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. - - INTEGER(IntKi), INTENT(INOUT) :: LineNum !< The number of the line to parse. - CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. - - - CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. - - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - - LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName - - ! Local declarations. - - CHARACTER(1024) :: Line - INTEGER(IntKi) :: ErrStatLcl ! Error status local to this routine. - INTEGER(IntKi) :: i - - CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. - CHARACTER(1024) :: Debug_String - CHARACTER(*), PARAMETER :: RoutineName = 'ParseInAry' - - LOGICAL :: CheckName_ - - ! Figure out if we're checking the name, default to .TRUE. - CheckName_ = .TRUE. - if (PRESENT(CheckName)) CheckName_ = CheckName - - ! If we've already failed, don't read anything - IF (ErrVar%aviFAIL >= 0) THEN - ! Read the whole line as a string - READ(Un, '(A)') Line - - ! Allocate array and handle errors - ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) - IF ( ErrStatLcl /= 0 ) THEN - IF ( ALLOCATED(Ary) ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' - ELSE - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' - END IF - END IF - - ! Allocate words array - ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' - CALL Cleanup() - RETURN - ENDIF - - ! Separate line string into AryLen + 1 words, should include variable name - CALL GetWords ( Line, Words_Ary, AryLen + 1 ) - - ! Debug Output - IF (DEBUG_PARSING) THEN - Debug_String = '' - DO i = 1,AryLen+1 - Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) - IF (i < AryLen + 1) THEN - Debug_String = TRIM(Debug_String)//',' - END IF - END DO - print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum - END IF - - ! Check that Variable Name is at the end of Words, will also check length of array - IF (CheckName_) THEN - CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) - END IF - - ! Read array - READ (Line,*,IOSTAT=ErrStatLcl) Ary - IF ( ErrStatLcl /= 0 ) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & - //TRIM( FileName )//'".'//NewLine// & - ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & - //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & - //' "'//TRIM( Line )//'"' - RETURN - CALL Cleanup() - ENDIF - - ! IF ( PRESENT(UnEc) ) THEN - ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) - ! END IF - - LineNum = LineNum + 1 - CALL Cleanup() - ENDIF - - RETURN - - !======================================================================= - CONTAINS - !======================================================================= - SUBROUTINE Cleanup ( ) - - ! This subroutine cleans up the parent routine before exiting. - - ! Deallocate the Words array if it had been allocated. - - IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) - - - RETURN - - END SUBROUTINE Cleanup - -END SUBROUTINE ParseInAry - -!======================================================================= - !> This subroutine checks the data to be parsed to make sure it finds - !! the expected variable name and an associated value. -SUBROUTINE ChkParseData ( Words, ExpVarName, FileName, FileLineNum, ErrVar ) - - USE ROSCO_Types, ONLY : ErrorVariables - - - ! Arguments declarations. - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - - INTEGER(IntKi), INTENT(IN) :: FileLineNum !< The number of the line in the file being parsed. - INTEGER(IntKi) :: NameIndx !< The index into the Words array that points to the variable name. - - CHARACTER(*), INTENT(IN) :: ExpVarName !< The expected variable name. - CHARACTER(*), INTENT(IN) :: Words (2) !< The two words to be parsed from the line. - - CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. - - - ! Local declarations. - - CHARACTER(20) :: ExpUCVarName ! The uppercase version of ExpVarName. - CHARACTER(20) :: FndUCVarName ! The uppercase version of the word being tested. - - - - - ! Convert the found and expected names to uppercase. - - FndUCVarName = Words(1) - ExpUCVarName = ExpVarName - - CALL Conv2UC ( FndUCVarName ) - CALL Conv2UC ( ExpUCVarName ) - - ! See which word is the variable name. Generate an error if it is the first - - IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN - NameIndx = 1 - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & - //'".'//NewLine//' >> The variable "'//TRIM( Words(1) )//'" was not assigned a valid value on line #' & - //TRIM( Int2LStr( FileLineNum ) )//'.' - RETURN - ELSE - FndUCVarName = Words(2) - CALL Conv2UC ( FndUCVarName ) - IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN - NameIndx = 2 - ELSE - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & - //'".'//NewLine//' >> The variable "'//TRIM( ExpVarName )//'" was not assigned a valid value on line #' & - //TRIM( Int2LStr( FileLineNum ) )//'.' - RETURN - ENDIF - ENDIF - - -END SUBROUTINE ChkParseData - -!======================================================================= -subroutine ReadEmptyLine(Un,CurLine) - INTEGER(IntKi), INTENT(IN ) :: Un ! Input file unit - INTEGER(IntKi), INTENT(INOUT) :: CurLine ! Current line of input - - CHARACTER(1024) :: Line - - READ(Un, '(A)') Line - CurLine = CurLine + 1 - -END subroutine ReadEmptyLine - -!======================================================================= -!> This subroutine is used to get the NumWords "words" from a line of text. -!! It uses spaces, tabs, commas, semicolons, single quotes, and double quotes ("whitespace") -!! as word separators. If there aren't NumWords in the line, the remaining array elements will remain empty. -!! Use CountWords (nwtc_io::countwords) to count the number of words in a line. -SUBROUTINE GetWords ( Line, Words, NumWords ) - - ! Argument declarations. - - INTEGER, INTENT(IN) :: NumWords !< The number of words to look for. - - CHARACTER(*), INTENT(IN) :: Line !< The string to search. - CHARACTER(*), INTENT(OUT) :: Words(NumWords) !< The array of found words. - - - ! Local declarations. - - INTEGER :: Ch ! Character position within the string. - INTEGER :: IW ! Word index. - INTEGER :: NextWhite ! The location of the next whitespace in the string. - CHARACTER(1), PARAMETER :: Tab = CHAR( 9 ) - - - - ! Let's prefill the array with blanks. - - DO IW=1,NumWords - Words(IW) = ' ' - END DO ! IW - - - ! Let's make sure we have text on this line. - - IF ( LEN_TRIM( Line ) == 0 ) RETURN - - - ! Parse words separated by any combination of spaces, tabs, commas, - ! semicolons, single quotes, and double quotes ("whitespace"). - - Ch = 0 - IW = 0 - - DO - - NextWhite = SCAN( Line(Ch+1:) , ' ,!;''"'//Tab ) - - IF ( NextWhite > 1 ) THEN - - IW = IW + 1 - Words(IW) = Line(Ch+1:Ch+NextWhite-1) - - IF ( IW == NumWords ) EXIT - - Ch = Ch + NextWhite - - ELSE IF ( NextWhite == 1 ) THEN - - Ch = Ch + 1 - - CYCLE - - ELSE - - EXIT - - END IF - - END DO - - - RETURN -END SUBROUTINE GetWords -!======================================================================= -!> Let's parse the path name from the name of the given file. -!! We'll count everything before (and including) the last "\" or "/". -SUBROUTINE GetPath ( GivenFil, PathName ) - - ! Argument declarations. - - CHARACTER(*), INTENT(IN) :: GivenFil !< The name of the given file. - CHARACTER(*), INTENT(OUT) :: PathName !< The path name of the given file (based solely on the GivenFil text string). - - - ! Local declarations. - - INTEGER :: I ! DO index for character position. - - - ! Look for path separators - - I = INDEX( GivenFil, '\', BACK=.TRUE. ) - I = MAX( I, INDEX( GivenFil, '/', BACK=.TRUE. ) ) - - IF ( I == 0 ) THEN - ! we don't have a path specified, return '.' - PathName = '.'//PathSep - ELSE - PathName = GivenFil(:I) - END IF - - - RETURN - END SUBROUTINE GetPath -!======================================================================= -!> This routine determines if the given file name is absolute or relative. -!! We will consider an absolute path one that satisfies one of the -!! following four criteria: -!! 1. It contains ":/" -!! 2. It contains ":\" -!! 3. It starts with "/" -!! 4. It starts with "\" -!! -!! All others are considered relative. - FUNCTION PathIsRelative ( GivenFil ) - - ! Argument declarations. - - CHARACTER(*), INTENT(IN) :: GivenFil !< The name of the given file. - LOGICAL :: PathIsRelative !< The function return value - - - - ! Determine if file name begins with an absolute path name or if it is relative - ! note that Doxygen has serious issues if you use the single quote instead of - ! double quote characters in the strings below: - - PathIsRelative = .FALSE. - - IF ( ( INDEX( GivenFil, ":/") == 0 ) .AND. ( INDEX( GivenFil, ":\") == 0 ) ) THEN ! No drive is specified (by ":\" or ":/") - - IF ( INDEX( "/\", GivenFil(1:1) ) == 0 ) THEN ! The file name doesn't start with "\" or "/" - - PathIsRelative = .TRUE. - - END IF - - END IF - - RETURN - END FUNCTION PathIsRelative -!======================================================================= -! ------------------------------------------------------ - ! Read Open Loop Control Inputs - ! - ! Timeseries or lookup tables of the form - ! index (time or wind speed) channel_1 \t channel_2 \t channel_3 ... - ! This could be used to read any group of data of unspecified length ... -SUBROUTINE Read_OL_Input(OL_InputFileName, Unit_OL_Input, NumChannels, Channels, ErrVar) - - USE ROSCO_Types, ONLY : ErrorVariables - - CHARACTER(1024), INTENT(IN) :: OL_InputFileName ! DISCON input filename - INTEGER(IntKi), INTENT(IN) :: Unit_OL_Input - INTEGER(IntKi), INTENT(IN) :: NumChannels ! Number of open loop channels being defined - ! REAL(DbKi), INTENT(OUT), DIMENSION(:), ALLOCATABLE :: Breakpoints ! Breakpoints of open loop Channels - REAL(DbKi), INTENT(OUT), DIMENSION(:,:), ALLOCATABLE :: Channels ! Open loop channels - TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input - - - LOGICAL :: FileExists - INTEGER :: IOS ! I/O status of OPEN. - CHARACTER(1024) :: Line ! Temp variable for reading whole line from file - INTEGER(IntKi) :: NumComments - INTEGER(IntKi) :: NumDataLines - REAL(DbKi) :: TmpData(NumChannels) ! Temp variable for reading all columns from a line - CHARACTER(15) :: NumString - - INTEGER(IntKi) :: I,J - - CHARACTER(*), PARAMETER :: RoutineName = 'Read_OL_Input' - - !------------------------------------------------------------------------------------------------- - ! Read from input file, borrowed (read: copied) from (Open)FAST team...thanks! - !------------------------------------------------------------------------------------------------- - - !------------------------------------------------------------------------------------------------- - ! Open the file for reading - !------------------------------------------------------------------------------------------------- - - INQUIRE (FILE = OL_InputFileName, EXIST = FileExists) - - IF ( .NOT. FileExists) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = TRIM(OL_InputFileName)// ' does not exist' - - ELSE - - OPEN( Unit_OL_Input, FILE=TRIM(OL_InputFileName), STATUS='OLD', FORM='FORMATTED', IOSTAT=IOS, ACTION='READ' ) - - IF (IOS /= 0) THEN - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Cannot open '//TRIM(OL_InputFileName) - - ELSE - ! Do all the stuff! - !------------------------------------------------------------------------------------------------- - ! Find the number of comment lines - !------------------------------------------------------------------------------------------------- - - LINE = '!' ! Initialize the line for the DO WHILE LOOP - NumComments = -1 ! the last line we read is not a comment, so we'll initialize this to -1 instead of 0 - - DO WHILE ( (INDEX( LINE, '!' ) > 0) .OR. (INDEX( LINE, '#' ) > 0) .OR. (INDEX( LINE, '%' ) > 0) ) ! Lines containing "!" are treated as comment lines - NumComments = NumComments + 1 - - READ(Unit_OL_Input,'( A )',IOSTAT=IOS) LINE - - ! NWTC_IO has some error catching here that we'll skip for now - - END DO !WHILE - - !------------------------------------------------------------------------------------------------- - ! Find the number of data lines - !------------------------------------------------------------------------------------------------- - - NumDataLines = 0 - - READ(LINE,*,IOSTAT=IOS) ( TmpData(I), I=1,NumChannels ) ! this line was read when we were figuring out the comment lines; let's make sure it contains - - DO WHILE (IOS == 0) ! read the rest of the file (until an error occurs) - NumDataLines = NumDataLines + 1 - - READ(Unit_OL_Input,*,IOSTAT=IOS) ( TmpData(I), I=1,NumChannels ) - - END DO !WHILE - - - IF (NumDataLines < 1) THEN - WRITE (NumString,'(I11)') NumComments - ErrVar%aviFAIL = -1 - ErrVar%ErrMsg = 'Error: '//TRIM(NumString)//' comment lines were found in the uniform wind file, '// & - 'but the first data line does not contain the proper format.' - CLOSE(Unit_OL_Input) - END IF - - !------------------------------------------------------------------------------------------------- - ! Allocate arrays for the uniform wind data - !------------------------------------------------------------------------------------------------- - ALLOCATE(Channels(NumDataLines,NumChannels)) - - !------------------------------------------------------------------------------------------------- - ! Rewind the file (to the beginning) and skip the comment lines - !------------------------------------------------------------------------------------------------- - - REWIND( Unit_OL_Input ) - - DO I=1,NumComments - READ(Unit_OL_Input,'( A )',IOSTAT=IOS) LINE - END DO !I - - !------------------------------------------------------------------------------------------------- - ! Read the data arrays - !------------------------------------------------------------------------------------------------- - - DO I=1,NumDataLines - - READ(Unit_OL_Input,*,IOSTAT=IOS) ( TmpData(J), J=1,NumChannels ) - - IF (IOS > 0) THEN - CLOSE(Unit_OL_Input) - END IF - - Channels(I,:) = TmpData - - END DO !I - END IF - END IF - - IF (ErrVar%aviFAIL < 0) THEN - ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) - ENDIF - -END SUBROUTINE Read_OL_Input - END MODULE ReadSetParameters diff --git a/ROSCO/src/SysFiles/SysGnuLinux.f90 b/ROSCO/src/SysFiles/SysGnuLinux.f90 index f83606bd..925b57b6 100644 --- a/ROSCO/src/SysFiles/SysGnuLinux.f90 +++ b/ROSCO/src/SysFiles/SysGnuLinux.f90 @@ -82,7 +82,7 @@ FUNCTION dlOpen(filename,mode) BIND(C,NAME="dlopen") IF( .NOT. C_ASSOCIATED(DLL%FileAddrX) ) THEN ErrStat = -1 - WRITE(ErrMsg,'(I2)') BITS_IN_ADDR + ! WRITE(ErrMsg,'(I2)') BITS_IN_ADDR ErrMsg = 'The dynamic library '//TRIM(DLL%FileName)//' could not be loaded. Check that the file '// & 'exists in the specified location and that it is compiled for '//TRIM(ErrMsg)//'-bit applications.' RETURN diff --git a/ROSCO/src/SysFiles/SysGnuWin.f90 b/ROSCO/src/SysFiles/SysGnuWin.f90 index bd3ef396..3d40f0e2 100644 --- a/ROSCO/src/SysFiles/SysGnuWin.f90 +++ b/ROSCO/src/SysFiles/SysGnuWin.f90 @@ -79,7 +79,7 @@ END FUNCTION LoadLibrary DLL%FileAddr = LoadLibrary( TRIM(DLL%FileName)//C_NULL_CHAR ) !the "C_NULL_CHAR" converts the Fortran string to a C-type string (i.e., adds //CHAR(0) to the end) IF ( DLL%FileAddr == INT(0,C_INTPTR_T) ) THEN ErrStat = ErrID_Fatal - WRITE(ErrMsg,'(I2)') BITS_IN_ADDR + ! WRITE(ErrMsg,'(I2)') BITS_IN_ADDR ErrMsg = 'The dynamic library '//TRIM(DLL%FileName)//' could not be loaded. Check that the file '// & 'exists in the specified location and that it is compiled for '//TRIM(ErrMsg)//'-bit applications.' RETURN diff --git a/ROSCO/src/SysFiles/SysIFL.f90 b/ROSCO/src/SysFiles/SysIFL.f90 index 46a591a9..4ff4b19d 100644 --- a/ROSCO/src/SysFiles/SysIFL.f90 +++ b/ROSCO/src/SysFiles/SysIFL.f90 @@ -84,7 +84,7 @@ FUNCTION dlOpen(filename,mode) BIND(C,NAME="dlopen") IF( .NOT. C_ASSOCIATED(DLL%FileAddrX) ) THEN ErrStat = ErrID_Fatal - WRITE(ErrMsg,'(I2)') BITS_IN_ADDR + ! WRITE(ErrMsg,'(I2)') BITS_IN_ADDR ErrMsg = 'The dynamic library '//TRIM(DLL%FileName)//' could not be loaded. Check that the file '// & 'exists in the specified location and that it is compiled for '//TRIM(ErrMsg)//'-bit applications.' RETURN diff --git a/ROSCO/src/SysFiles/SysIVF.f90 b/ROSCO/src/SysFiles/SysIVF.f90 index dc29db54..c0d81cb0 100644 --- a/ROSCO/src/SysFiles/SysIVF.f90 +++ b/ROSCO/src/SysFiles/SysIVF.f90 @@ -60,7 +60,7 @@ SUBROUTINE LoadDynamicLib ( DLL, ErrStat, ErrMsg ) IF ( DLL%FileAddr == INT(0,C_INTPTR_T) ) THEN ErrStat = ErrID_Fatal - WRITE(ErrMsg,'(I2)') BITS_IN_ADDR + ! WRITE(ErrMsg,'(I2)') BITS_IN_ADDR ErrMsg = 'The dynamic library '//TRIM(DLL%FileName)//' could not be loaded. Check that the file '// & 'exists in the specified location and that it is compiled for '//TRIM(ErrMsg)//'-bit applications.' RETURN diff --git a/ROSCO/src/ZeroMQInterface.f90 b/ROSCO/src/ZeroMQInterface.f90 new file mode 100644 index 00000000..88ea2ecf --- /dev/null +++ b/ROSCO/src/ZeroMQInterface.f90 @@ -0,0 +1,77 @@ +module ZeroMQInterface + USE, INTRINSIC :: ISO_C_BINDING, only: C_CHAR, C_DOUBLE, C_NULL_CHAR + IMPLICIT NONE + ! + +CONTAINS + SUBROUTINE UpdateZeroMQ(LocalVar, CntrPar, zmqVar, ErrVar) + USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ZMQ_Variables, ErrorVariables + IMPLICIT NONE + TYPE(LocalVariables), INTENT(INOUT) :: LocalVar + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + + character(256) :: zmq_address + real(C_DOUBLE) :: setpoints(5) + real(C_DOUBLE) :: turbine_measurements(16) + CHARACTER(*), PARAMETER :: RoutineName = 'UpdateZeroMQ' + + ! C interface with ZeroMQ client +#ifdef ZMQ_CLIENT + interface + subroutine zmq_client(zmq_address, measurements, setpoints) bind(C, name="zmq_client") + import :: C_CHAR, C_DOUBLE + implicit none + character(C_CHAR), intent(out) :: zmq_address(*) + real(C_DOUBLE) :: measurements(16) + real(C_DOUBLE) :: setpoints(5) + end subroutine zmq_client + end interface +#endif + + ! Communicate if threshold has been reached + IF ((MODULO(LocalVar%Time, CntrPar%ZMQ_UpdatePeriod) == 0) .OR. (LocalVar%iStatus == -1)) THEN + ! Collect measurements to be sent to ZeroMQ server + turbine_measurements(1) = LocalVar%iStatus + turbine_measurements(2) = LocalVar%Time + turbine_measurements(3) = LocalVar%VS_MechGenPwr + turbine_measurements(4) = LocalVar%VS_GenPwr + turbine_measurements(5) = LocalVar%GenSpeed + turbine_measurements(6) = LocalVar%RotSpeed + turbine_measurements(7) = LocalVar%GenTqMeas + turbine_measurements(8) = LocalVar%NacHeading + turbine_measurements(9) = LocalVar%NacVane + turbine_measurements(10) = LocalVar%HorWindV + turbine_measurements(11) = LocalVar%rootMOOP(1) + turbine_measurements(12) = LocalVar%rootMOOP(2) + turbine_measurements(13) = LocalVar%rootMOOP(3) + turbine_measurements(14) = LocalVar%FA_Acc + turbine_measurements(15) = LocalVar%NacIMU_FA_Acc + turbine_measurements(16) = LocalVar%Azimuth + + write (zmq_address, "(A,A)") TRIM(CntrPar%ZMQ_CommAddress), C_NULL_CHAR +#ifdef ZMQ_CLIENT + call zmq_client(zmq_address, turbine_measurements, setpoints) +#else + ! Add RoutineName to error message + ErrVar%aviFAIL = -1 + IF (CntrPar%ZMQ_Mode > 0) THEN + ErrVar%ErrMsg = " >> The ZeroMQ client has not been properly installed, " & + //"please install it to use ZMQ_Mode > 0." + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF +#endif + + ! write (*,*) "ZeroMQInterface: torque setpoint from ssc: ", setpoints(1) + ! write (*,*) "ZeroMQInterface: yaw setpoint from ssc: ", setpoints(2) + ! write (*,*) "ZeroMQInterface: pitch 1 setpoint from ssc: ", setpoints(3) + ! write (*,*) "ZeroMQInterface: pitch 2 setpoint from ssc: ", setpoints(4) + ! write (*,*) "ZeroMQInterface: pitch 3 setpoint from ssc: ", setpoints(5) + zmqVar%Yaw_Offset = setpoints(2) + + ENDIF + + + END SUBROUTINE UpdateZeroMQ +end module ZeroMQInterface \ No newline at end of file diff --git a/ROSCO/src/zmq_client.c b/ROSCO/src/zmq_client.c new file mode 100644 index 00000000..0e3974a2 --- /dev/null +++ b/ROSCO/src/zmq_client.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include + + +void delete_blank_spaces_in_string(char *s) +{ + int i,k=0; + for(i=0;s[i];i++) + { + s[i]=s[i+k]; + if(s[i]==' '|| s[i]=='\t') + { + k++; + i--; + } + } +} + + +int zmq_client ( + char *zmq_address, + double measurements[16], + double setpoints[5] +) +{ + int num_measurements = 16; // Number of setpoints and measurements, respectively, and float precision (character length) + int char_buffer_size_single = 20; // Char buffer for a single measurement + int char_buffer_size_array = (num_measurements * (char_buffer_size_single + 1)); // Char buffer for full messages to and from ROSCO + char string_to_ssc[char_buffer_size_array]; + char string_from_ssc[char_buffer_size_array]; + + int verbose = 0; // Variable to define verbose + + if (verbose == 1) { + printf ("Connecting to ZeroMQ server at %s...\n", zmq_address); + } + + // Open connection with ZeroMQ server + void *context = zmq_ctx_new (); + void *requester = zmq_socket (context, ZMQ_REQ); + zmq_connect (requester, zmq_address); // string_to_zmq is something like "tcp://localhost:5555" + + // Create a string with measurements to be sent to ZeroMQ server (e.g., Python) + char a[char_buffer_size_array], b[char_buffer_size_single]; + sprintf(b, "%.6e", measurements[0]); + strncpy(a, b, char_buffer_size_single); + //printf ("zmq_client.c: a[char_buffer_size_single]: measurements[0]: %s\n", a); + int i = 1; + while (i < num_measurements) { + strcat(a, ","); // Add a comma + sprintf(b, "%.6e", measurements[i]); // Add value + strcat(a, b); // Concatenate b to a + //printf ("zmq_client.c: b[char_buffer_size_single]: measurements[i]: %s\n", b); + //printf (" --> zmq_client.c: a[char_buffer_size_single]: measurements[i]: %s\n", a); + i = i + 1; + } + strncpy(string_to_ssc, a, char_buffer_size_array); + + // Print the string + if (verbose == 1) { + printf ("zmq_client.c: string_to_ssc: %s…\n", string_to_ssc); + } + + // Core ZeroMQ communication: receive data and send back signals + zmq_send (requester, string_to_ssc, char_buffer_size_array, 0); + zmq_recv (requester, string_from_ssc, char_buffer_size_array, 0); + + if (verbose == 1) { + printf ("zmq_client.c: Received a response: %s\n", string_from_ssc); + } + + // Convert string_from_ssc string to separate floats + delete_blank_spaces_in_string(string_from_ssc); + char *pt; + pt = strtok (string_from_ssc,","); + i = 0; + while (pt != NULL) { + double dtmp = atof(pt); + if (verbose == 1) { + printf("pt subloop: %s (var), %f (double) \n", pt, dtmp); + printf("zmq_client.c: setpoint[%d]: %f \n", i, dtmp); + } + pt = strtok (NULL, ","); + setpoints[i] = dtmp; // Save values to setpoints + i = i + 1; + } + + // Close connection + zmq_close (requester); + zmq_ctx_destroy (context); + return 0; +} \ No newline at end of file diff --git a/ROSCO_testing/regtest.py b/ROSCO_testing/test_checkpoint.py similarity index 100% rename from ROSCO_testing/regtest.py rename to ROSCO_testing/test_checkpoint.py diff --git a/ROSCO_toolbox/__init__.py b/ROSCO_toolbox/__init__.py index 87556368..afbd37e5 100644 --- a/ROSCO_toolbox/__init__.py +++ b/ROSCO_toolbox/__init__.py @@ -3,4 +3,4 @@ __author__ = """Nikhar J. Abbas and Daniel S. Zalkind""" __email__ = 'nikhar.abbas@nrel.gov' -__version__ = '2.5.0' +__version__ = '2.6.0' diff --git a/ROSCO_toolbox/control_interface.py b/ROSCO_toolbox/control_interface.py index f273ae17..5514bf6a 100644 --- a/ROSCO_toolbox/control_interface.py +++ b/ROSCO_toolbox/control_interface.py @@ -9,10 +9,10 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -from ctypes import byref, cdll, c_int, POINTER, c_float, c_char_p, c_double, create_string_buffer, c_int32, c_void_p +from ctypes import byref, cdll, POINTER, c_float, c_char_p, c_double, create_string_buffer, c_int32, c_void_p import numpy as np -from numpy.ctypeslib import ndpointer import platform, ctypes +import zmq # Some useful constants deg2rad = np.deg2rad(1) @@ -85,8 +85,7 @@ def init_discon(self): # Torque initial condition self.avrSWAP[22] = 0 - - # Code this as first casll + # Code this as first call self.avrSWAP[0] = 0 # Put some values in @@ -98,7 +97,6 @@ def init_discon(self): # Initialize DISCON and related self.aviFAIL = c_int32() # 1 self.accINFILE = self.param_name.encode('utf-8') - # self.avcOUTNAME = create_string_buffer(1000) # 'DEMO'.encode('utf-8') self.avcOUTNAME = (self.sim_name + '.RO.dbg').encode('utf-8') self.avcMSG = create_string_buffer(1000) self.discon.DISCON.argtypes = [POINTER(c_float), POINTER(c_int32), c_char_p, c_char_p, c_char_p] # (all defined by ctypes) @@ -106,7 +104,7 @@ def init_discon(self): # Run DISCON self.call_discon() - # Code as not first run + # Code as not first run now that DISCON has been initialized self.avrSWAP[0] = 1 @@ -126,43 +124,52 @@ def call_discon(self): self.avrSWAP = data - def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws,NacIMU_FA_Acc=0): + def call_controller(self, turbine_state, end=False): ''' Runs the controller. Passes current turbine state to the controller, and returns control inputs back Parameters: ----------- - t: float - time, (s) - dt: float - timestep, (s) - pitch: float - blade pitch, (rad) - genspeed: float - generator speed, (rad/s) - geneff: float - generator efficiency, (rad/s) - rotspeed: float - rotor speed, (rad/s) - ws: float - wind speed, (m/s) - NacIMU_FA_Acc : float - nacelle IMU accel. in the nodding dir. , (m/s**2) - default to 0 (fixed-bottom, simple 1-DOF sim does not include it, but OpenFAST linearizations do) + turbine_state: dict + t: float + time, (s) + dt: float + timestep, (s) + pitch: float + blade pitch, (rad) + genspeed: float + generator speed, (rad/s) + geneff: float + generator efficiency, (rad/s) + rotspeed: float + rotor speed, (rad/s) + ws: float + wind speed, (m/s) + yaw: float, optional + nacelle yaw position (from north) (deg) + yawerr: float, optional + yaw misalignment, defined as the wind direction minus the yaw + position (deg) ''' # Add states to avr - self.avrSWAP[1] = t - self.avrSWAP[2] = dt - self.avrSWAP[3] = pitch - self.avrSWAP[32] = pitch - self.avrSWAP[33] = pitch - self.avrSWAP[14] = genspeed*torque*geneff - self.avrSWAP[22] = torque - self.avrSWAP[19] = genspeed - self.avrSWAP[20] = rotspeed - self.avrSWAP[26] = ws - self.avrSWAP[82] = NacIMU_FA_Acc + self.avrSWAP[0] = turbine_state['iStatus'] + self.avrSWAP[1] = turbine_state['t'] + self.avrSWAP[2] = turbine_state['dt'] + self.avrSWAP[3] = turbine_state['bld_pitch'] + self.avrSWAP[32] = turbine_state['bld_pitch'] + self.avrSWAP[33] = turbine_state['bld_pitch'] + self.avrSWAP[14] = turbine_state['gen_speed'] * turbine_state['gen_torque'] * turbine_state['gen_eff'] + self.avrSWAP[22] = turbine_state['gen_torque'] + self.avrSWAP[19] = turbine_state['gen_speed'] + self.avrSWAP[20] = turbine_state['rot_speed'] + self.avrSWAP[23] = turbine_state['Y_MeasErr'] + self.avrSWAP[26] = turbine_state['ws'] + self.avrSWAP[36] = turbine_state['Yaw_fromNorth'] + try: + self.avrSWAP[82] = turbine_state['NacIMU_FA_Acc'] + except KeyError: + self.avrSWAP[82] = 0 # call controller self.call_discon() @@ -170,8 +177,9 @@ def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws,NacIMU_FA # return controller states self.pitch = self.avrSWAP[41] self.torque = self.avrSWAP[46] + self.nac_yawrate = self.avrSWAP[47] - return(self.torque,self.pitch) + return(self.torque,self.pitch,self.nac_yawrate) def show_control_values(self): ''' @@ -254,3 +262,204 @@ def null_free_dll(*spam): # pragma: no cover dlclose(handle) del self.discon + + +class farm_zmq_server(): + def __init__(self, network_addresses=["tcp://*:5555", "tcp://*:5556"], + identifiers=None, timeout=600.0, verbose=False): + """Python implementation for communicating with multiple instances + of the ROSCO ZeroMQ interface. This is useful for SOWFA and FAST.Farm + simulations in which multiple turbines are running in real time. + Args: + network_addresses (str, optional): List with the network addresses + used to communicate with the desired instances of ROSCO. + identifiers (iteratible, optional): List of strings denoting the + turbine identification string, e.g., ["WTG-01", "WTG-02"]. + If left unspecified, will simple name the turbines "0" to + nturbs - 1. + timeout (float, optional): Seconds to wait for a message from + the ZeroMQ server before timing out. Defaults to 600.0. + verbose (bool, optional): Print to console. Defaults to False. + """ + self.network_addresses = network_addresses + self.verbose = verbose + self.nturbs = len(self.network_addresses) + + if identifiers is None: + identifiers = ["%d" % i for i in range(self.nturbs)] + + # Initialize ZeroMQ servers + self.zmq_servers = [None for _ in range(self.nturbs)] + for ti, address in enumerate(self.network_addresses): + self.zmq_servers[ti] = turbine_zmq_server( + network_address=address, + identifier=identifiers[ti], + timeout=timeout, + verbose=verbose) + + def get_measurements(self): + ''' + Get measurements from zmq servers + ''' + measurements = [None for _ in range(self.nturbs)] + for ti in range(self.nturbs): + measurements[ti] = self.zmq_servers[ti].get_measurements() + return measurements + + def send_setpoints(self, genTorques=None, nacelleHeadings=None, + bladePitchAngles=None): + ''' + Send setpoints to DLL via zmq server for farm level controls + + Parameters: + ----------- + genTorques: List + List of generator torques of length self.nturbs + nacelleHeadings: List + List of nacelle headings of length self.nturbs + bladePitchAngles: List + List of blade pitch angles of length self.nturbs + ''' + # Default choices if unspecified + if genTorques is None: + genTorques = [0.0] * self.nturbs + if nacelleHeadings is None: + nacelleHeadings = [0.0] * self.nturbs + if bladePitchAngles is None: + bladePitchAngles = [[0.0, 0.0, 0.0]] * self.nturbs + + # Send setpoints + for ti in range(self.nturbs): + self.zmq_servers[ti].send_setpoints( + genTorque=genTorques[ti], + nacelleHeading=nacelleHeadings[ti], + bladePitch=bladePitchAngles[ti] + ) + + +class turbine_zmq_server(): + def __init__(self, network_address="tcp://*:5555", identifier="0", + timeout=600.0, verbose=False): + """Python implementation of the ZeroMQ server side for the ROSCO + ZeroMQ wind farm control interface. This class makes it easy for + users to receive measurements from ROSCO and then send back control + setpoints (generator torque, nacelle heading and/or blade pitch + angles). + Args: + network_address (str, optional): The network address to + communicate over with the desired instance of ROSCO. Note that, + if running a wind farm simulation in SOWFA or FAST.Farm, there + are multiple instances of ROSCO and each of these instances + needs to communicate over a unique port. Also, for each of those + instances, you will need an instance of zmq_server. This variable + Defaults to "tcp://*:5555". + identifier (str, optional): Turbine identifier. Defaults to "0". + timeout (float, optional): Seconds to wait for a message from + the ZeroMQ server before timing out. Defaults to 600.0. + verbose (bool, optional): Print to console. Defaults to False. + """ + self.network_address = network_address + self.identifier = identifier + self.timeout = timeout + self.verbose = verbose + self._connect() + + def _connect(self): + ''' + Connect to zmq server + ''' + address = self.network_address + + # Connect socket + context = zmq.Context() + self.socket = context.socket(zmq.REP) + self.socket.setsockopt(zmq.LINGER, 0) + self.socket.bind(address) + + if self.verbose: + print("[%s] Successfully established connection with %s" % (self.identifier, address)) + + def _disconnect(self): + ''' + Disconnect from zmq server + ''' + self.socket.close() + context = zmq.Context() + context.term() + + def get_measurements(self): + ''' + Receive measurements from ROSCO .dll + ''' + if self.verbose: + print("[%s] Waiting to receive measurements from ROSCO..." % (self.identifier)) + + # Initialize a poller for timeouts + poller = zmq.Poller() + poller.register(self.socket, zmq.POLLIN) + timeout_ms = int(self.timeout * 1000) + if poller.poll(timeout_ms): + # Receive measurements over network protocol + message_in = self.socket.recv_string() + else: + raise IOError("[%s] Connection to '%s' timed out." + % (self.identifier, self.network_address)) + + # Convert to individual strings and then to floats + measurements = message_in + measurements = measurements.replace('\x00', '').split(',') + measurements = [float(m) for m in measurements] + + # Convert to a measurement dict + measurements = dict({ + 'iStatus': measurements[0], + 'Time': measurements[1], + 'VS_MechGenPwr': measurements[2], + 'VS_GenPwr': measurements[3], + 'GenSpeed': measurements[4], + 'RotSpeed': measurements[5], + 'GenTqMeas': measurements[6], + 'NacelleHeading': measurements[7], + 'NacelleVane': measurements[8], + 'HorWindV': measurements[9], + 'rootMOOP1': measurements[10], + 'rootMOOP2': measurements[11], + 'rootMOOP3': measurements[12], + 'FA_Acc': measurements[13], + 'NacIMU_FA_Acc': measurements[14], + 'Azimuth': measurements[15], + }) + + if self.verbose: + print('[%s] Measurements received:' % self.identifier, measurements) + + return measurements + + def send_setpoints(self, genTorque=0.0, nacelleHeading=0.0, + bladePitch=[0.0, 0.0, 0.0]): + ''' + Send setpoints to ROSCO .dll ffor individual turbine control + + Parameters: + ----------- + genTorques: float + Generator torque setpoint + nacelleHeadings: float + Nacelle heading setpoint + bladePitchAngles: List (len=3) + Blade pitch angle setpoint + ''' + # Create a message with setpoints to send to ROSCO + message_out = b"%016.5f, %016.5f, %016.5f, %016.5f, %016.5f" % ( + genTorque, nacelleHeading, bladePitch[0], bladePitch[1], + bladePitch[2]) + + # Send reply back to client + if self.verbose: + print("[%s] Sending setpoint string to ROSCO: %s." % (self.identifier, message_out)) + + # Send control setpoints over network protocol + self.socket.send(message_out) + + if self.verbose: + print("[%s] Setpoints sent successfully." % self.identifier) diff --git a/ROSCO_toolbox/controller.py b/ROSCO_toolbox/controller.py index 5b49961f..b4e3c353 100644 --- a/ROSCO_toolbox/controller.py +++ b/ROSCO_toolbox/controller.py @@ -10,10 +10,11 @@ # specific language governing permissions and limitations under the License. import numpy as np -import sys, os +import os import datetime -from scipy import interpolate, gradient, integrate +from scipy import interpolate, integrate from ROSCO_toolbox.utilities import list_check +from scipy import optimize # Some useful constants now = datetime.datetime.now() @@ -60,7 +61,11 @@ def __init__(self, controller_params): self.PS_Mode = controller_params['PS_Mode'] self.SD_Mode = controller_params['SD_Mode'] self.Fl_Mode = controller_params['Fl_Mode'] + self.TD_Mode = controller_params['TD_Mode'] self.Flp_Mode = controller_params['Flp_Mode'] + self.PA_Mode = controller_params['PA_Mode'] + self.Ext_Mode = controller_params['Ext_Mode'] + self.ZMQ_Mode = controller_params['ZMQ_Mode'] # Necessary parameters self.U_pc = list_check(controller_params['U_pc'], return_bool=False) @@ -85,6 +90,7 @@ def __init__(self, controller_params): self.Ki_ipc1p = controller_params['IPC_Ki1p'] self.Kp_ipc2p = controller_params['IPC_Kp2p'] self.Ki_ipc2p = controller_params['IPC_Kp2p'] + self.IPC_Vramp = controller_params['IPC_Vramp'] # Optional parameters without defaults if self.Flp_Mode > 0: @@ -125,6 +131,7 @@ def __init__(self, controller_params): self.f_we_cornerfreq = controller_params['filter_params']['f_we_cornerfreq'] self.f_fl_highpassfreq = controller_params['filter_params']['f_fl_highpassfreq'] self.f_ss_cornerfreq = controller_params['filter_params']['f_ss_cornerfreq'] + self.f_yawerr = controller_params['filter_params']['f_yawerr'] self.f_sd_cornerfreq = controller_params['filter_params']['f_sd_cornerfreq'] # Open loop parameters: set up and error catching @@ -134,8 +141,6 @@ def __init__(self, controller_params): if self.OL_Mode: ol_params = controller_params['open_loop'] - - self.OL_Ind_Breakpoint = ol_params['OL_Ind_Breakpoint'] self.OL_Ind_BldPitch = ol_params['OL_Ind_BldPitch'] self.OL_Ind_GenTq = ol_params['OL_Ind_GenTq'] @@ -146,6 +151,11 @@ def __init__(self, controller_params): raise Exception(f'Open-loop control set up, but the open loop file {self.OL_Filename} does not exist') + # Pitch actuator parameters + self.PA_Mode = controller_params['PA_Mode'] + self.PA_CornerFreq = controller_params['PA_CornerFreq'] + self.PA_Damping = controller_params['PA_Damping'] + # Save controller_params for later (direct passthrough) self.controller_params = controller_params @@ -179,6 +189,8 @@ def tune_controller(self, turbine): Ng = turbine.Ng # Gearbox ratio (-) rated_rotor_speed = turbine.rated_rotor_speed # Rated rotor speed (rad/s) + # ------------- Saturation Limits --------------- # + turbine.max_torque = turbine.rated_torque * self.controller_params['max_torque_factor'] # -------------Define Operation Points ------------- # TSR_rated = rated_rotor_speed*R/turbine.v_rated # TSR at rated @@ -191,15 +203,14 @@ def tune_controller(self, turbine): # separate TSRs by operations regions TSR_below_rated = [min(turbine.TSR_operational, rated_rotor_speed*R/v) for v in v_below_rated] # below rated - TSR_above_rated = rated_rotor_speed*R/v_above_rated # above rated - # TSR_below_rated = np.minimum(np.max(TSR_above_rated), TSR_below_rated) - TSR_op = np.concatenate((TSR_below_rated, TSR_above_rated)) # operational TSRs + TSR_above_rated = rated_rotor_speed*R/v_above_rated # above rated + TSR_op = np.concatenate((TSR_below_rated, TSR_above_rated)) # operational TSRs # Find expected operational Cp values - Cp_above_rated = turbine.Cp.interp_surface(0,TSR_above_rated[0]) # Cp during rated operation (not optimal). Assumes cut-in bld pitch to be 0 + Cp_above_rated = turbine.Cp.interp_surface(0,TSR_above_rated[0]) # Cp during rated operation (not optimal). Assumes cut-in bld pitch to be 0 Cp_op_br = np.ones(len(v_below_rated)) * turbine.Cp.max # below rated Cp_op_ar = Cp_above_rated * (TSR_above_rated/TSR_rated)**3 # above rated - Cp_op = np.concatenate((Cp_op_br, Cp_op_ar)) # operational CPs to linearize around + Cp_op = np.concatenate((Cp_op_br, Cp_op_ar)) # operational CPs to linearize around pitch_initial_rad = turbine.pitch_initial_rad TSR_initial = turbine.TSR_initial @@ -212,22 +223,25 @@ def tune_controller(self, turbine): Ct_op = np.empty(len(TSR_op)) # ------------- Find Linearized State "Matrices" ------------- # + # At each operating point for i in range(len(TSR_op)): - # Find pitch angle as a function of expected operating CP for each TSR + # Find pitch angle as a function of expected operating CP for each TSR operating point Cp_TSR = np.ndarray.flatten(turbine.Cp.interp_surface(turbine.pitch_initial_rad, TSR_op[i])) # all Cp values for a given tsr Cp_maxidx = Cp_TSR.argmax() - Cp_op[i] = np.clip(Cp_op[i], np.min(Cp_TSR[Cp_maxidx:]), np.max(Cp_TSR[Cp_maxidx:])) # saturate Cp values to be on Cp surface # Find maximum Cp value for this TSR - f_cp_pitch = interpolate.interp1d(Cp_TSR[Cp_maxidx:],pitch_initial_rad[Cp_maxidx:]) # interpolate function for Cp(tsr) values - # expected operation blade pitch values + Cp_op[i] = np.clip(Cp_op[i], np.min(Cp_TSR[Cp_maxidx:]), np.max(Cp_TSR[Cp_maxidx:])) # saturate Cp values to be on Cp surface # Find maximum Cp value for this TSR + f_cp_pitch = interpolate.interp1d(Cp_TSR[Cp_maxidx:],pitch_initial_rad[Cp_maxidx:]) # interpolate function for Cp(tsr) values + + # expected operational blade pitch values. Saturates by min_pitch if it exists if v[i] <= turbine.v_rated and isinstance(self.min_pitch, float): # Below rated & defined min_pitch pitch_op[i] = min(self.min_pitch, f_cp_pitch(Cp_op[i])) - elif isinstance(self.min_pitch, float): + elif isinstance(self.min_pitch, float): # above rated & defined min_pitch pitch_op[i] = max(self.min_pitch, f_cp_pitch(Cp_op[i])) - else: + else: # no defined minimum pitch schedule pitch_op[i] = f_cp_pitch(Cp_op[i]) - dCp_beta[i], dCp_TSR[i] = turbine.Cp.interp_gradient(pitch_op[i],TSR_op[i]) # gradients of Cp surface in Beta and TSR directions - dCt_beta[i], dCt_TSR[i] = turbine.Ct.interp_gradient(pitch_op[i],TSR_op[i]) # gradients of Cp surface in Beta and TSR directions + # Calculate Cp Surface gradients + dCp_beta[i], dCp_TSR[i] = turbine.Cp.interp_gradient(pitch_op[i],TSR_op[i]) + dCt_beta[i], dCt_TSR[i] = turbine.Ct.interp_gradient(pitch_op[i],TSR_op[i]) # Thrust Ct_TSR = np.ndarray.flatten(turbine.Ct.interp_surface(turbine.pitch_initial_rad, TSR_op[i])) # all Cp values for a given tsr @@ -246,12 +260,11 @@ def tune_controller(self, turbine): dCt_dbeta = dCt_beta/np.diff(pitch_initial_rad)[0] dCt_dTSR = dCt_TSR/np.diff(TSR_initial)[0] - # Linearized system derivatives - dtau_dbeta = Ng/2*rho*Ar*R*(1/TSR_op)*dCp_dbeta*v**2 - dtau_dlambda = Ng/2*rho*Ar*R*v**2*(1/(TSR_op**2))*(dCp_dTSR*TSR_op - Cp_op) + # Linearized system derivatives, equations from https://wes.copernicus.org/articles/7/53/2022/wes-7-53-2022.pdf + dtau_dbeta = Ng/2*rho*Ar*R*(1/TSR_op)*dCp_dbeta*v**2 # (26) + dtau_dlambda = Ng/2*rho*Ar*R*v**2*(1/(TSR_op**2))*(dCp_dTSR*TSR_op - Cp_op) # (7) dlambda_domega = R/v/Ng dtau_domega = dtau_dlambda*dlambda_domega - dlambda_dv = -(TSR_op/v) Pi_beta = 1/2 * rho * Ar * v**2 * dCt_dbeta @@ -315,8 +328,13 @@ def tune_controller(self, turbine): else: self.sd_maxpit = pitch_op[-1] + # Set IPC ramp inputs if not already defined + if max(self.IPC_Vramp) == 0.0: + self.IPC_Vramp = [turbine.v_rated*0.8, turbine.v_rated] + # Store some variables self.v = v # Wind speed (m/s) + self.v_above_rated = v_above_rated self.v_below_rated = v_below_rated self.pitch_op = pitch_op self.pitch_op_pc = pitch_op[-len(v_above_rated)+1:] @@ -400,23 +418,19 @@ def tune_flap_controller(self,turbine): # Find blade aerodynamic coefficients v_rel = [] phi_vec = [] - alpha=[] for i, _ in enumerate(self.v): turbine.cc_rotor.induction_inflow=True # Axial and tangential inductions try: - a, ap, alpha0, cl, cd = turbine.cc_rotor.distributedAeroLoads( + a, ap, _, _, _ = turbine.cc_rotor.distributedAeroLoads( self.v[i], self.omega_op[i], self.pitch_op[i], 0.0) except ValueError: - loads, derivs = turbine.cc_rotor.distributedAeroLoads( + loads, _ = turbine.cc_rotor.distributedAeroLoads( self.v[i], self.omega_op[i], self.pitch_op[i], 0.0) - a = loads['a'] - ap = loads['ap'] - alpha0 = loads['alpha'] - cl = loads['Cl'] - cd = loads['Cd'] + a = loads['a'] # Axial induction factor + ap = loads['ap'] # Tangential induction factor - # Relative windspeed + # Relative windspeed along blade span v_rel.append([np.sqrt(self.v[i]**2*(1-a)**2 + self.omega_op[i]**2*turbine.span**2*(1-ap)**2)]) # Inflow wind direction phi_vec.append(self.pitch_op[i] + turbine.twist*deg2rad) @@ -431,9 +445,11 @@ def tune_flap_controller(self,turbine): Cdm = np.zeros(num_af) for i,section in enumerate(turbine.af_data): - # assume airfoil section as AOA of zero for slope calculations - for now + # assume airfoil section as AOA of zero for slope calculations a0_ind = section[0]['Alpha'].index(np.min(np.abs(section[0]['Alpha']))) # Coefficients + # - If the flap exists in this blade section, define Cx-plus,-minus,-neutral(0) + # - IF teh flap does not exist in this blade section, Cx matrix is all the same value if section[0]['NumTabs'] == 3: # sections with 3 flaps Clm[i,] = section[0]['Cl'][a0_ind] Cdm[i,] = section[0]['Cd'][a0_ind] @@ -447,12 +463,12 @@ def tune_flap_controller(self,turbine): Cd0[i,] = Cdp[i,] = Cdm[i,] = section[0]['Cd'][a0_ind] Ctrl = float(section[0]['Ctrl']) - # Find slopes + # Find lift and drag coefficient slopes w.r.t. flap angle Kcl = (Clp - Cl0)/( (Ctrl_flp-Ctrl)*deg2rad ) Kcd = (Cdp - Cd0)/( (Ctrl_flp-Ctrl)*deg2rad ) # Find integrated constants - self.kappa = np.zeros(len(v_rel)) + self.kappa = np.zeros(len(v_rel)) # "flap efficacy term" C1 = np.zeros(len(v_rel)) C2 = np.zeros(len(v_rel)) for i, (v_sec,phi) in enumerate(zip(v_rel, phi_vec)): @@ -463,7 +479,6 @@ def tune_flap_controller(self,turbine): # PI Gains if (self.flp_kp_norm == 0 or self.flp_tau == 0) or (not self.flp_kp_norm or not self.flp_tau): raise ValueError('flp_kp_norm and flp_tau must be nonzero for Flp_Mode >= 1') - self.Kp_flap = self.flp_kp_norm / self.kappa self.Ki_flap = self.flp_kp_norm / self.kappa / self.flp_tau @@ -492,22 +507,20 @@ def peak_shaving(self,controller, turbine): ''' # Re-define Turbine Parameters for shorthand - J = turbine.J # Total rotor inertial (kg-m^2) - rho = turbine.rho # Air density (kg/m^3) - R = turbine.rotor_radius # Rotor radius (m) - A = np.pi*R**2 # Rotor area (m^2) - Ng = turbine.Ng # Gearbox ratio (-) - rated_rotor_speed = turbine.rated_rotor_speed # Rated rotor speed (rad/s) + rho = turbine.rho # Air density (kg/m^3) + R = turbine.rotor_radius # Rotor radius (m) + A = np.pi*R**2 # Rotor area (m^2) # Initialize some arrays Ct_op = np.empty(len(controller.TSR_op),dtype='float64') Ct_max = np.empty(len(controller.TSR_op),dtype='float64') - beta_min = np.empty(len(controller.TSR_op),dtype='float64') - # Find unshaved rotor thurst coefficients and associated rotor thrusts - # for i in len(controller.TSR_op): + + # Find unshaved rotor thrust coefficients at each TSR for i in range(len(controller.TSR_op)): Ct_op[i] = turbine.Ct.interp_surface(controller.pitch_op[i],controller.TSR_op[i]) - T = 0.5 * rho * A * controller.v**2 * Ct_op + + # Thrust vs. wind speed + T = 0.5 * rho * A * controller.v**2 * Ct_op # Define minimum max thrust and initialize pitch_min Tmax = controller.ps_percent * np.max(T) @@ -516,18 +529,21 @@ def peak_shaving(self,controller, turbine): # Modify pitch_min if max thrust exceeds limits for i in range(len(controller.TSR_op)): # Find Ct values for operational TSR - # Ct_tsr = turbine.Ct.interp_surface(turbine.pitch_initial_rad, controller.TSR_op[i]) Ct_tsr = turbine.Ct.interp_surface(turbine.pitch_initial_rad,controller.TSR_op[i]) # Define max Ct values Ct_max[i] = Tmax/(0.5 * rho * A * controller.v[i]**2) if T[i] > Tmax: Ct_op[i] = Ct_max[i] else: + # TSR_below_rated = np.minimum(np.max(TSR_above_rated), TSR_below_rated) Ct_max[i] = np.minimum( np.max(Ct_tsr), Ct_max[i]) + # Define minimum pitch angle + # - find min(\beta) so that Ct <= Ct_max and \beta > \beta_fine at each operational TSR f_pitch_min = interpolate.interp1d(Ct_tsr, turbine.pitch_initial_rad, kind='linear', bounds_error=False, fill_value=(turbine.pitch_initial_rad[0],turbine.pitch_initial_rad[-1])) pitch_min[i] = max(controller.min_pitch, f_pitch_min(Ct_max[i])) + # Save to controller object controller.ps_min_bld_pitch = pitch_min # save some outputs for analysis or future work @@ -539,9 +555,20 @@ def peak_shaving(self,controller, turbine): self.T = T def min_pitch_saturation(self, controller, turbine): - + ''' + Minimum pitch saturation in low wind speeds to maximize power capture + + Parameters: + ----------- + controller: class + Controller class containing controller operational information + turbine: class + Turbine class containing necessary wind turbine information for controller tuning + ''' # Find TSR associated with minimum rotor speed TSR_at_minspeed = (controller.pc_minspd) * turbine.rotor_radius / controller.v_below_rated + + # For each below rated wind speed operating point for i in range(len(TSR_at_minspeed)): if TSR_at_minspeed[i] > controller.TSR_op[i]: controller.TSR_op[i] = TSR_at_minspeed[i] @@ -549,18 +576,16 @@ def min_pitch_saturation(self, controller, turbine): # Initialize some arrays Cp_op = np.empty(len(turbine.pitch_initial_rad),dtype='float64') min_pitch = np.empty(len(TSR_at_minspeed),dtype='float64') - - # Find Cp-maximizing minimum pitch schedule - # Find Cp coefficients at below-rated tip speed ratios + # ------- Find Cp-maximizing minimum pitch schedule --------- + # Cp coefficients at below-rated tip speed ratios Cp_op = turbine.Cp.interp_surface(turbine.pitch_initial_rad,TSR_at_minspeed[i]) - Cp_max = max(Cp_op) - # f_pitch_min = interpolate.interp1d(Cp_op, -turbine.pitch_initial_rad, kind='quadratic', bounds_error=False, fill_value=(turbine.pitch_initial_rad[0],turbine.pitch_initial_rad[-1])) + + # Setup and run small optimization problem to find blade pitch angle that maximizes Cp at a given TSR + # - Finds \beta to satisfy max( Cp(\beta,TSR_op) ) f_pitch_min = interpolate.interp1d(turbine.pitch_initial_rad, -Cp_op, kind='quadratic', bounds_error=False, fill_value=(turbine.pitch_initial_rad[0],turbine.pitch_initial_rad[-1])) - from scipy import optimize res = optimize.minimize(f_pitch_min, 0.0) min_pitch[i] = res.x[0] - # min_pitch[i] = f_pitch_min(Cp_max) # modify existing minimum pitch schedule controller.ps_min_bld_pitch[i] = np.maximum(controller.ps_min_bld_pitch[i], min_pitch[i]) @@ -799,19 +824,28 @@ def write_input(self,ol_filename): return open_loop -# helper functions - +# ----------- Helper Functions ----------- def sigma(tt,t0,t1,y0=0,y1=1): ''' generates timeseries for a smooth transition from y0 to y1 from x0 to x1 - inputs: tt - time indices - t0 - start time - t1 - end time - y0 - start output - y1 - end output - - outputs: yy - output timeseries corresponding to tt + Parameters: + ----------- + tt: List-like + time indices + t0: float + start time + t1: float + end time + y0: float + start output + y1: + end output + + Returns: + -------- + yy: List-like + output timeseries corresponding to tt ''' a3 = 2/(t0-t1)**3 @@ -836,14 +870,19 @@ def multi_sigma(xx,x_bp,y_bp): Parameters: ----------- - xx : list of floats (-) + xx: list of floats (-) new sample points - x_bp : list of floats (-) + x_bp: list of floats (-) breakpoints y_bp : list of floats (-) function value at breakpoints + Returns: + -------- + yy: List-like + output timeseries corresponding to tt ''' + # initialize yy = np.zeros_like(xx) # interpolate sigma functions between all breakpoints diff --git a/ROSCO_toolbox/inputs/toolbox_schema.yaml b/ROSCO_toolbox/inputs/toolbox_schema.yaml index 48d51ed7..c3e0c80b 100644 --- a/ROSCO_toolbox/inputs/toolbox_schema.yaml +++ b/ROSCO_toolbox/inputs/toolbox_schema.yaml @@ -161,6 +161,12 @@ properties: maximum: 1 default: 0 description: Shutdown mode (0- no shutdown procedure, 1- pitch to max pitch at shutdown) + TD_Mode: + type: number + minimum: 0 + maximum: 1 + default: 0 + description: Tower damper mode (0- no tower damper, 1- feed back translational nacelle accelleration to pitch angle Fl_Mode: type: number minimum: 0 @@ -179,6 +185,24 @@ properties: maximum: 2 default: 0 description: Active Power Control Mode (0- no active power control 1- constant active power control, 2- open loop power vs time, 3- open loop power vs. wind speed) + ZMQ_Mode: + type: number + minimum: 0 + maximum: 1 + default: 0 + description: ZMQ Mode (0 - ZMQ Inteface, 1 - ZMQ for yaw control) + PA_Mode: + type: number + minimum: 0 + maximum: 2 + default: 0 + description: Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} + Ext_Mode: + type: number + minimum: 0 + maximum: 1 + default: 0 + description: External control mode {{0 - not used, 1 - call external dynamic library}} U_pc: type: array description: List of wind speeds to schedule pitch control zeta and omega @@ -309,6 +333,11 @@ properties: minimum: 0 description: Flap controller time constant for integral gain unit: s + max_torque_factor: + type: number + minimum: 0 + default: 1.1 + description: Maximum torque = rated torque * max_torque_factor IPC_Kp1p: type: number minimum: 0 @@ -331,6 +360,15 @@ properties: minimum: 0 description: integral gain for IPC, 2P [-] default: 0.0 + IPC_Vramp: + type: array + description: wind speeds for IPC cut-in sigma function [m/s] + items: + type: number + minimum: 0.0 + default: [0.0, 0.0] + unit: m/s + filter_params: type: object default: {} @@ -365,6 +403,12 @@ properties: minimum: 0 unit: rad/s default: 0.6283 + f_yawerr: + type: number + description: Low pass filter corner frequency for yaw controller [rad/ + minimum: 0 + unit: rad/s + default: 0.17952 f_sd_cornerfreq: description: Cutoff Frequency for first order low-pass filter for blade pitch angle [rad/s], {default = 0.41888 ~ time constant of 15s} type: number @@ -398,8 +442,413 @@ properties: description: Index (column, 1-indexed) of breakpoint (time) in open loop index type: number default: 0 + PA_CornerFreq: + type: number + description: Pitch actuator natural frequency [rad/s] + unit: rad/s + default: 3.14 + minimum: 0 + PA_Damping: + type: number + description: Pitch actuator damping ratio [-] + default: 0.707 + minimum: 0 - + DISCON: + type: object + description: These are pass-through parameters for the DISCON.IN file. Use with caution. + default: {} + properties: + LoggingLevel: + type: number + description: (0- write no debug files, 1- write standard output .dbg-file, 2- write standard output .dbg-file and complete avrSWAP-array .dbg2-file) + F_LPFType: + type: number + description: 1- first-order low-pass filter, 2- second-order low-pass filter (currently filters generator speed and pitch control signals + F_NotchType: + type: number + description: Notch on the measured generator speed and/or tower fore-aft motion (for floating) (0- disable, 1- generator speed, 2- tower-top fore-aft motion, 3- generator speed and tower-top fore-aft motion) + IPC_ControlMode: + type: number + description: Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) (0- off, 1- 1P reductions, 2- 1P+2P reductions) + VS_ControlMode: + type: number + description: Generator torque control mode in above rated conditions (0- constant torque, 1- constant power, 2- TSR tracking PI control with constant torque, 3- TSR tracking PI control with constant power) + PC_ControlMode: + type: number + description: Blade pitch control mode (0- No pitch, fix to fine pitch, 1- active PI blade pitch control) + Y_ControlMode: + type: number + description: Yaw control mode (0- no yaw control, 1- yaw rate control, 2- yaw-by-IPC) + SS_Mode: + type: number + description: Setpoint Smoother mode (0- no setpoint smoothing, 1- introduce setpoint smoothing) + WE_Mode: + type: number + description: Wind speed estimator mode (0- One-second low pass filtered hub height wind speed, 1- Immersion and Invariance Estimator, 2- Extended Kalman Filter) + PS_Mode: + type: number + description: Pitch saturation mode (0- no pitch saturation, 1- implement pitch saturation) + SD_Mode: + type: number + description: Shutdown mode (0- no shutdown procedure, 1- pitch to max pitch at shutdown) + Fl_Mode: + type: number + description: Floating specific feedback mode (0- no nacelle velocity feedback, 1- feed back translational velocity, 2- feed back rotational veloicty) + Flp_Mode: + type: number + description: Flap control mode (0- no flap control, 1- steady state flap angle, 2- Proportional flap control) + F_LPFCornerFreq: + type: number + description: Corner frequency (-3dB point) in the low-pass filters, + units: rad/s + F_LPFDamping: + type: number + description: Damping coefficient (used only when F_FilterType = 2 [-] + F_NotchCornerFreq: + type: number + description: Natural frequency of the notch filter, + units: rad/s + F_NotchBetaNumDen: + type: array + items: + type: number + description: Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] + F_SSCornerFreq: + type: number + description: Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, + units: rad/s. + F_WECornerFreq: + type: number + description: Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate + units: rad/s. + F_FlCornerFreq: + type: array + items: + type: number + description: Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control + units: rad/s + F_FlHighPassFreq: + type: number + description: Natural frequency of first-order high-pass filter for nacelle fore-aft motion + units: rad/s + F_FlpCornerFreq: + type: array + items: + type: number + description: Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control + units: rad/s + PC_GS_n: + type: number + description: Amount of gain-scheduling table entries + PC_GS_angles: + type: array + items: + type: number + description: Gain-schedule table- pitch angles + units: rad + PC_GS_KP: + type: array + items: + type: number + description: Gain-schedule table- pitch controller kp gains + units: s + PC_GS_KI: + type: array + items: + type: number + description: Gain-schedule table- pitch controller ki gains + PC_GS_KD: + type: array + items: + type: number + description: Gain-schedule table- pitch controller kd gains + PC_GS_TF: + type: array + items: + type: number + description: Gain-schedule table- pitch controller tf gains (derivative filter) + PC_MaxPit: + type: number + description: Maximum physical pitch limit, + units: rad + PC_MinPit: + type: number + description: Minimum physical pitch limit, + units: rad + PC_MaxRat: + type: number + description: Maximum pitch rate (in absolute value) in pitch controller + units: rad/s. + PC_MinRat: + type: number + description: Minimum pitch rate (in absolute value) in pitch controller + units: rad/s. + PC_RefSpd: + type: number + description: Desired (reference) HSS speed for pitch controller + units: rad/s. + PC_FinePit: + type: number + description: Record 5- Below-rated pitch angle set-point + units: rad + PC_Switch: + type: number + description: Angle above lowest minimum pitch angle for switch + units: rad + IPC_IntSat: + type: number + description: Integrator saturation (maximum signal amplitude contribution to pitch from IPC) + units: rad + IPC_KP: + type: array + items: + type: number + description: Proportional gain for the individual pitch controller- first parameter for 1P reductions, second for 2P reductions, [-] + IPC_KI: + type: array + items: + type: number + description: Integral gain for the individual pitch controller- first parameter for 1P reductions, second for 2P reductions, [-] + IPC_aziOffset: + type: array + items: + type: number + description: Phase offset added to the azimuth angle for the individual pitch controller + units: rad + IPC_CornerFreqAct: + type: number + description: Corner frequency of the first-order actuators model, to induce a phase lag in the IPC signal (0- Disable) + units: rad/s + VS_GenEff: + type: number + description: Generator efficiency mechanical power -> electrical power, should match the efficiency defined in the generator properties + units: percent + VS_ArSatTq: + type: number + description: Above rated generator torque PI control saturation + units: Nm + VS_MaxRat: + type: number + description: Maximum torque rate (in absolute value) in torque controller + units: Nm/s + VS_MaxTq: + type: number + description: Maximum generator torque in Region 3 (HSS side) + units: Nm + VS_MinTq: + type: number + description: Minimum generator torque (HSS side) + units: Nm + VS_MinOMSpd: + type: number + description: Minimum generator speed + units: rad/s + VS_Rgn2K: + type: number + description: Generator torque constant in Region 2 (HSS side) + units: Nm/(rad/s)^2 + VS_RtPwr: + type: number + description: Wind turbine rated power + units: W + VS_RtTq: + type: number + description: Rated torque + units: Nm + VS_RefSpd: + type: number + description: Rated generator speed + units: rad/s + VS_n: + type: number + description: Number of generator PI torque controller gains + VS_KP: + type: number + description: Proportional gain for generator PI torque controller. (Only used in the transitional 2.5 region if VS_ControlMode =/ 2) + VS_KI: + type: number + description: Integral gain for generator PI torque controller (Only used in the transitional 2.5 region if VS_ControlMode =/ 2) + units: s + VS_TSRopt: + type: number + description: Power-maximizing region 2 tip-speed-ratio + units: rad + SS_VSGain: + type: number + description: Variable speed torque controller setpoint smoother gain + SS_PCGain: + type: number + description: Collective pitch controller setpoint smoother gain + WE_BladeRadius: + type: number + description: Blade length (distance from hub center to blade tip) + units: m + WE_CP_n: + type: number + description: Amount of parameters in the Cp array + WE_CP: + type: array + items: + type: number + description: Parameters that define the parameterized CP(lambda) function + WE_Gamma: + type: number + description: Adaption gain of the wind speed estimator algorithm + units: m/rad + WE_GearboxRatio: + type: number + description: Gearbox ratio, >=1 + WE_Jtot: + type: number + description: Total drivetrain inertia, including blades, hub and casted generator inertia to LSS + units: kg m^2 + WE_RhoAir: + type: number + description: Air density + units: kg m^-3 + PerfFileName: + type: string + description: File containing rotor performance tables (Cp,Ct,Cq) (absolute path or relative to this file) + PerfTableSize: + type: number + description: Size of rotor performance tables, first number refers to number of blade pitch angles, second number referse to number of tip-speed ratios + WE_FOPoles_N: + type: number + description: Number of first-order system poles used in EKF + WE_FOPoles_v: + type: array + items: + type: number + description: Wind speeds corresponding to first-order system poles + units: m/s + WE_FOPoles: + type: array + items: + type: number + description: First order system poles + units: 1/s + Y_ErrThresh: + type: number + description: Yaw error threshold. Turbine begins to yaw when it passes this + units: rad^2 s + Y_IPC_IntSat: + type: number + description: Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC) + units: rad + Y_IPC_n: + type: number + description: Number of controller gains (yaw-by-IPC) + Y_IPC_KP: + type: number + description: Yaw-by-IPC proportional controller gain Kp + Y_IPC_KI: + type: number + description: Yaw-by-IPC integral controller gain Ki + Y_IPC_omegaLP: + type: number + description: Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error + units: rad/s. + Y_IPC_zetaLP: + type: number + description: Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error. + Y_MErrSet: + type: number + description: Yaw alignment error, set point + units: rad + Y_omegaLPFast: + type: number + description: Corner frequency fast low pass filter, 1.0 + units: rad/s + Y_omegaLPSlow: + type: number + description: Corner frequency slow low pass filter, 1/60 + units: rad/s + Y_Rate: + type: number + description: Yaw rate + units: rad/s + FA_KI: + type: number + description: Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on + units: rad s/m + FA_HPFCornerFreq: + type: number + description: Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal + units: rad/s + FA_IntSat: + type: number + description: Integrator saturation (maximum signal amplitude contribution to pitch from FA damper) + units: rad + PS_BldPitchMin_N: + type: number + description: Number of values in minimum blade pitch lookup table (should equal number of values in PS_WindSpeeds and PS_BldPitchMin) + PS_WindSpeeds: + type: array + items: + type: number + description: Wind speeds corresponding to minimum blade pitch angles + units: m/s + PS_BldPitchMin: + type: array + items: + type: number + description: Minimum blade pitch angles + units: rad + SD_MaxPit: + type: number + description: Maximum blade pitch angle to initiate shutdown + units: rad + SD_CornerFreq: + type: number + description: Cutoff Frequency for first order low-pass filter for blade pitch angle + units: rad/s + Fl_Kp: + type: number + description: Nacelle velocity proportional feedback gain + units: s + Flp_Angle: + type: number + description: Initial or steady state flap angle + units: rad + Flp_Kp: + type: number + description: Blade root bending moment proportional gain for flap control + units: s + Flp_Ki: + type: number + description: Flap displacement integral gain for flap control + Flp_MaxPit: + type: number + description: Maximum (and minimum) flap pitch angle + units: rad + OL_Filename: + type: string + description: Input file with open loop timeseries (absolute path or relative to this file) + Ind_Breakpoint: + type: number + description: The column in OL_Filename that contains the breakpoint (time if OL_Mode = 1) + Ind_BldPitch: + type: number + description: The column in OL_Filename that contains the blade pitch input in rad + Ind_GenTq: + type: number + description: The column in OL_Filename that contains the generator torque in Nm + Ind_YawRate: + type: number + description: The column in OL_Filename that contains the generator torque in Nm + DLL_FileName: + type: string + description: Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format + default: unused + DLL_InFile: + type: string + description: Name of input file sent to the DLL + default: unused + DLL_ProcName: + type: string + description: Name of procedure in DLL to be called + default: DISCON linmodel_tuning: type: object diff --git a/ROSCO_toolbox/linear/lin_util.py b/ROSCO_toolbox/linear/lin_util.py index 29203da0..969ea3fa 100644 --- a/ROSCO_toolbox/linear/lin_util.py +++ b/ROSCO_toolbox/linear/lin_util.py @@ -66,7 +66,7 @@ def pc_closedloop(linturb, controller, u): Cs = interp_pitch_controller(controller, u) # Combine controller and plant - sys_cl = feedback(1, Cs*P) + sys_cl = feedback(Cs*P, 1) return sys_cl @@ -102,21 +102,33 @@ def pc_sensitivity(linturb, controller, u): return sens def smargin(linturb, controller, u_eval): - # try: - # k_float = inputs[1] - # except: + ''' + Calculates the stability margin for an open-loop system + + linturb: object + LinearTurbineModel object + controller: object + ROSCO toolbox controller object + u_eval: float + wind speed to evaluate system at + ''' + + # Find standard transfer functions sens_sys = pc_sensitivity(linturb, controller, u_eval) ol_sys = pc_openloop(linturb, controller, u_eval) - + # Convert to state space sp_plant = sp.signal.StateSpace(ol_sys.A, ol_sys.B, ol_sys.C, ol_sys.D) sp_sens = sp.signal.StateSpace(sens_sys.A, sens_sys.B, sens_sys.C, sens_sys.D) + # Minimum distance to the critical point on the nyquist diagram def nyquist_min(om): return np.abs(sp.signal.freqresp(sp_plant, w=om)[1] + 1.) + # Maximum value of sensitivity function def sens_min(om): return -sp.signal.bode(sp_sens, w=om)[1] with warnings.catch_warnings(): - warnings.simplefilter("ignore") - # Find first local maxima in sensitivity function + warnings.simplefilter("ignore") # NJA: Lots of scipy errors because of poorly posed state matrices - ignore them + + # Find first local maximum in bode magnitude of the sensitivity function ws, m, _ = sp.signal.bode(sp_sens, n=100000) m0 = m[0] m1 = m[1] @@ -128,27 +140,44 @@ def sens_min(om): return -sp.signal.bode(sp_sens, w=om)[1] i += 1 w0 = ws[i] + # --- Find important magnitudes and related frequencies with just the sampled values --- + # # NJA - optimization is run in the next "step" to fine tune these values further + # magnitude of sensitivity function at first local maximum sm_mag = sp.signal.freqresp(sp_plant, w=w0)[1] sm = np.sqrt((1 - np.abs(sm_mag.real))**2 + sm_mag.imag**2) + # magnitude and frequency of distance to critical point on nyquist nearest_nyquist = nyquist_min(ws).min() nearest_nyquist_freq = ws[nyquist_min(ws).argmin()] mag_at_min = sp.signal.freqresp(sp_plant, w=nearest_nyquist_freq)[1] + + # If any poles of the sensitivity function are unstable, solve an optimization problem on the Nyquist trajectory + # to minimize the distance to the critical point over all frequencies. Start the gradient-based optimization at + # the nearest value calculated in the sweep above + #: NJA - optimization is done to reduce precision errors if any(sp_sens.poles > 0): if nearest_nyquist < sm: res = sp.optimize.minimize(nyquist_min, nearest_nyquist_freq, method='SLSQP', options={ 'finite_diff_rel_step': 1e-8}) - sm2 = min(abs(res.fun), abs(nearest_nyquist)) - + # Make sure this didn't fail + if res.status != 0: + # if optimization failed, just use the closest value from the initial sweep + sm2 = nearest_nyquist + else: + sm2 = min(abs(res.fun), abs(nearest_nyquist)) + + # Dubious error catching because of strange nyquist shapes in unstable systems sm_list = [sm, sm2] mag_list = [np.abs(sm_mag), np.abs(mag_at_min)] sm = sm_list[np.argmax(mag_list)] - sm *= -1 # Flip sign because it's unstable - else: + sm *= -1 # Flip sign to have a "negative sensitivity margin" because it's unstable + else: # system is stable res = sp.optimize.minimize(nyquist_min, nearest_nyquist_freq, method='SLSQP', options={'finite_diff_rel_step': 1e-6}) - sm = min(res.fun, nearest_nyquist) - - + if res.status != 0: + # if optimization failed, just use the closest value from the initial sweep + sm = nearest_nyquist + else: + sm = min(res.fun, nearest_nyquist) return sm @@ -172,10 +201,16 @@ def interp_plant(linturb, v, return_scipy=True): ''' # Find interpolated plant on v - Ap = interp_matrix(linturb.u_h, linturb.A_ops, v) - Bp = interp_matrix(linturb.u_h, linturb.B_ops, v) - Cp = interp_matrix(linturb.u_h, linturb.C_ops, v) - Dp = interp_matrix(linturb.u_h, linturb.D_ops, v) + if np.shape(linturb.A_ops)[2] > 1: + Ap = interp_matrix(linturb.u_h, linturb.A_ops, v) + Bp = interp_matrix(linturb.u_h, linturb.B_ops, v) + Cp = interp_matrix(linturb.u_h, linturb.C_ops, v) + Dp = interp_matrix(linturb.u_h, linturb.D_ops, v) + else: + Ap = np.squeeze(linturb.A_ops, axis=2) + Bp = np.squeeze(linturb.B_ops, axis=2) + Cp = np.squeeze(linturb.C_ops, axis=2) + Dp = np.squeeze(linturb.D_ops, axis=2) if return_scipy: P = sp.signal.StateSpace(Ap, Bp, Cp, Dp) @@ -234,6 +269,7 @@ def add_pcomp(linturb, k_float): Modified linturb with parallel compensation term included ''' + # Find relevant state and input indices state_str = 'derivative of 1st tower fore-aft' state_idx = np.flatnonzero(np.core.defchararray.find( linturb.DescStates, state_str) > -1).tolist() @@ -241,8 +277,10 @@ def add_pcomp(linturb, k_float): input_idx = np.flatnonzero(np.core.defchararray.find( linturb.DescCntrlInpt, input_str) > -1).tolist() + # Modify linear system to be \dot{x} = (A+B_fK)x + Bu, + # - B_fKx accounts for parallel compensation in the linear system K = np.zeros((linturb.B_ops.shape[1], linturb.A_ops.shape[1], linturb.A_ops.shape[2])) - K[input_idx, state_idx, :] = -k_float # NJA: negative to account of OF linearization sign conventions + K[input_idx, state_idx, :] = -k_float # NJA: negative to account for OpenFAST linearization sign conventions linturb2 = copy.copy(linturb) linturb2.A_ops = linturb.A_ops + linturb.B_ops * K diff --git a/ROSCO_toolbox/linear/lin_vis.py b/ROSCO_toolbox/linear/lin_vis.py new file mode 100644 index 00000000..35d50714 --- /dev/null +++ b/ROSCO_toolbox/linear/lin_vis.py @@ -0,0 +1,87 @@ +''' +Visualization helpers for linear models +''' +from ROSCO_toolbox.linear.lin_util import add_pcomp, pc_openloop +import numpy as np +import matplotlib.pyplot as plt +import scipy as sp + + +class lin_plotting(): + def __init__(self, controller, turbine, linturb): + ''' + Parameters + ---------- + controller: object + ROSCO controller object + turbine: object + ROSCO turbine object + linturb: object + ROSCO linturb object + ''' + self.turbine = turbine + self.controller = controller + self.linturb = linturb + + def plot_nyquist(self, u, omega, k_float=0.0, xlim=None, ylim=None, fig=None, ax=None, num='Nyquist', **kwargs): + ''' + Plot nyquist diagram + + Parameters: + ----------- + u: float + windspeed for linear model + omega: float + controller bandwidth + k_float: float, optional + parallel compensation feedback gain + ''' + if fig and ax: + self.fig = fig + self.ax = ax + else: + self.fig, self.ax = plt.subplots(1,1,num=num) + w, H = self.get_nyquistdata(u, omega, k_float=k_float) + self.line, = self.ax.plot(H.real, H.imag, **kwargs) + plt.scatter(-1,0,marker='x',color='r') + + self.ax.set_xlim(xlim) + self.ax.set_ylim(ylim) + plt.xlabel('Real Axis') + plt.ylabel('Imaginary Axis') + plt.title('Nyquist Diagram\n$u = {}, \omega = {}, k_f = {}$'.format(u,omega,k_float)) + plt.grid(True) + + + def get_nyquistdata(self, u, omega, k_float=0.0): + ''' + Gen nyquist diagram data to plot + + Parameters: + ----------- + u: float + windspeed for linear model + omega: float + controller bandwidth + k_float: float, optional + parallel compensation feedback gain + ''' + self.controller.omega_pc = omega + self.controller.U_pc = u + self.controller.tune_controller(self.turbine) + if k_float: + linturb = add_pcomp(self.linturb, k_float) + else: + linturb = self.linturb + sys_ol = pc_openloop(linturb, self.controller, u) + sys_sp = sp.signal.StateSpace(sys_ol.A, sys_ol.B, sys_ol.C, sys_ol.D) + w, H = sp.signal.freqresp(sys_sp) + + return w, H + +def sm_circle(r): + theta = np.linspace(0,2*np.pi,100) + x = r * np.cos(theta) - 1 + y = r * np.sin(theta) + + return x,y diff --git a/ROSCO_toolbox/linear/linear_models.py b/ROSCO_toolbox/linear/linear_models.py index 9b46548a..5a4d6969 100644 --- a/ROSCO_toolbox/linear/linear_models.py +++ b/ROSCO_toolbox/linear/linear_models.py @@ -23,7 +23,7 @@ class LinearTurbineModel(object): - def __init__(self, lin_file_dir, lin_file_names, nlin=12, reduceStates=False, fromMat=False, lin_file=None, rm_hydro=False, load_parallel=False): + def __init__(self, lin_file_dir, lin_file_names, nlin=12, reduceStates=False, fromMat=False, fromPkl=False, mat_file=None, rm_hydro=False, load_parallel=False): ''' inputs: lin_file_dir (string) - directory of linear file outputs from OpenFAST @@ -35,7 +35,7 @@ def __init__(self, lin_file_dir, lin_file_names, nlin=12, reduceStates=False, fr rm_hydro (bool) - remove hydrodynamic states load_parallel (bool) - run mbc3 usying multiprocessing ''' - if not fromMat: + if not fromMat and not fromPkl: # Number of linearization cases or OpenFAST sims, different from nlin/NLinTimes in OpenFAST n_lin_cases = len(lin_file_names) @@ -187,9 +187,56 @@ def __init__(self, lin_file_dir, lin_file_names, nlin=12, reduceStates=False, fr # Trim the system self.trim_system(rm_azimuth=True, rm_hydro=rm_hydro) + elif fromPkl: + import pickle + if isinstance(lin_file_names, list): + print('list') + if len(lin_file_names) != 1: + raise TypeError( + 'lin_file_names must be a string or list of length 1 to import matrices from a pickle.') + else: + linfile = lin_file_names[0] + elif isinstance(lin_file_names, str): + linfile = lin_file_names + else: + raise TypeError( + 'lin_file_names must be a string or list of length 1 to import matrices from a pickle.') + + fname = os.path.join(lin_file_dir, linfile) + lin_mats = pickle.load(open(fname, 'rb'))[0] + print('Loading ABCD from ',fname) + self.A_ops = lin_mats['A'] + self.B_ops = lin_mats['B'] + self.C_ops = lin_mats['C'] + self.D_ops = lin_mats['D'] + self.x_ops = lin_mats['x_ops'] + self.u_ops = lin_mats['u_ops'] + self.y_ops = lin_mats['y_ops'] + self.u_h = lin_mats['u_h'] + self.omega_rpm = lin_mats['omega_rpm'] + self.DescCntrlInpt = lin_mats['DescCntrlInpt'] + self.DescStates = lin_mats['DescStates'] + self.DescOutput = lin_mats['DescOutput'] + self.StateDerivOrder = lin_mats['StateDerivOrder'] + self.ind_fast_inps = lin_mats['ind_fast_inps'] + self.ind_fast_outs = lin_mats['ind_fast_outs'] + + # Convert output RPM to rad/s + rpm_idx = np.flatnonzero(np.core.defchararray.find(self.DescOutput, 'rpm') > -1).tolist() + self.C_ops[rpm_idx, :, :] = rpm2radps(self.C_ops[rpm_idx, :, :]) + self.DescOutput = [desc.replace('rpm', 'rad/s') for desc in self.DescOutput] + # Convert output deg to rad + deg_idx = np.flatnonzero(np.core.defchararray.find(self.DescOutput, 'deg') > -1).tolist() + self.C_ops[deg_idx, :, :] = deg2rad(self.C_ops[deg_idx, :, :]) + self.DescOutput = [desc.replace('deg', 'rad') for desc in self.DescOutput] + + # Other important things + self.n_lin_cases = lin_mats['A'].shape[2] + # Trim the system + self.trim_system(rm_azimuth=True, rm_hydro=rm_hydro) else: # from matlab .mat file m - matDict = loadmat(lin_file) + matDict = loadmat(mat_file) # operating points # u_ops \in real(n_inps,n_ops) diff --git a/ROSCO_toolbox/linear/robust_scheduling.py b/ROSCO_toolbox/linear/robust_scheduling.py index dae86c5b..70bc418e 100644 --- a/ROSCO_toolbox/linear/robust_scheduling.py +++ b/ROSCO_toolbox/linear/robust_scheduling.py @@ -1,5 +1,6 @@ ''' Methods for finding robust gain schedules +- Implemented in the OpenMDAO framework ''' import numpy as np @@ -16,6 +17,7 @@ from ROSCO_toolbox.inputs.validation import load_rosco_yaml from ROSCO_toolbox.utilities import list_check + class RobustScheduling(om.ExplicitComponent): 'Finding Robust gain schedules for pitch controllers in FOWTs' @@ -37,7 +39,7 @@ def setup(self): 'Error: omega_pc and zeta_pc must be scalars for robust controller tuning.') # Load ROSCO Turbine and Controller - if 'dict_inputs' in ROSCO_options.keys(): # Allow for turbine parameters to be passed in as a dictionary + if 'dict_inputs' in ROSCO_options.keys(): # Allow for turbine parameters to be passed in as a dictionary (from WEIS) dict_inputs = ROSCO_options['dict_inputs'] # Define turbine based on inputs self.turbine = type('', (), {})() @@ -78,7 +80,7 @@ def setup(self): self.controller = ROSCO_controller.Controller(ROSCO_options['controller_params']) self.controller.tune_controller(self.turbine) - else: + else: # otherwise define controller and turbine objection self.controller, self.turbine = load_ROSCO(ROSCO_options['path_params'], ROSCO_options['turbine_params'], ROSCO_options['controller_params']) @@ -100,6 +102,9 @@ def setup(self): desc='Maximized controller bandwidth') def compute(self, inputs, outputs): + ''' + Computes the stability margin for a given controller bandwidth (omega) + ''' k_float = inputs['k_float'][0] if k_float: linturb = add_pcomp(self.linturb, k_float) @@ -110,6 +115,7 @@ def compute(self, inputs, outputs): self.controller.tune_controller(self.turbine) sm = smargin(linturb, self.controller, inputs['u_eval'][0]) + print('omega = {}, sm = {}'.format(inputs['omega'][0], sm)) omega = inputs['omega'] # Outputs @@ -119,10 +125,14 @@ def compute(self, inputs, outputs): class rsched_driver(): ''' - A driver for scheduling robust controllers + A driver for scheduling robust controllers. + Wrapper for the RobustScheduling OpenMDAO Explicit Component + + - Example 12 shows how to use this ''' def __init__(self, options): + # initialize options self.linturb_options = options['linturb_options'] self.ROSCO_options = options['ROSCO_options'] self.path_options = options['path_options'] @@ -134,6 +144,7 @@ def __init__(self, options): self.output_dir = self.path_options['output_dir'] self.output_name = self.path_options['output_name'] + # setup levels, especially useful for DOEs if 'levels' in self.opt_options.keys(): self.opt_options['levels'] = self.opt_options['levels'] else: @@ -151,12 +162,21 @@ def setup(self): Setup the OpenMDAO problem ''' # Initialize problem - self.om_problem = om.Problem() + from openmdao.utils.mpi import MPI, FakeComm + if MPI: + self.om_problem = om.Problem(comm=FakeComm()) + self.om_problem._run_root_only = True + self.om_problem.comm.allgather = MPI.COMM_WORLD.allgather + self.om_problem.comm.Bcast = MPI.COMM_WORLD.Bcast + else: + self.om_problem = om.Problem() - # Add subsystem + # Add robust scheduling subsystem self.om_problem.model.add_subsystem('r_sched', RobustScheduling(linturb_options=self.linturb_options, ROSCO_options=self.ROSCO_options)) + # Setup design of experiments or optimization problem. + # - NJA: DOEs can only be done for one wind speed at a time if self.opt_options['driver'] == 'design_of_experiments': self.om_problem = self.init_doe(self.om_problem, levels=self.opt_options['levels']) if list_check(self.opt_options['windspeed']): @@ -166,7 +186,7 @@ def setup(self): ValueError( 'Can only run design of experiments for a single opt_options["windspeed"]') elif self.opt_options['driver'] == 'optimization': - self.om_problem = self.init_doe(self.om_problem, levels=self.opt_options['levels']) + self.om_problem = self.init_optimization(self.om_problem) else: ValueError( "self.opt_options['driver'] must be either 'design_of_experiments' or 'optimization'.") @@ -180,7 +200,6 @@ def setup(self): # Setup OM Problem self.om_problem.setup() - # Set constant values if not list_check(self.opt_options['omega']): self.om_problem.set_val('r_sched.omega', self.opt_options['omega']) @@ -192,12 +211,8 @@ def setup(self): self.om_doe = self.om_problem self.om_doe = self.add_dv(self.om_doe, ['omega', 'k_float']) if self.opt_options['driver'] == 'optimization': - # self.om_doe = copy.deepcopy(self.om_problem) - # self.om_doe = self.add_dv(self.om_doe, ['omega']) - self.om_opt = self.om_problem self.om_opt = self.add_dv(self.om_opt, ['omega', 'k_float']) - self.om_opt = self.init_optimization(self.om_opt) def execute(self): ''' @@ -215,20 +230,31 @@ def execute(self): self.omegas = [] self.k_floats = [] self.sms = [] + + # Iterate optimization over each wind speed in opt_options for u in self.opt_options['windspeed']: - om0 = 0.1 - # Setup optimization - self.om_opt.set_val('r_sched.u_eval', u) - self.om_opt.set_val('r_sched.omega', om0) - - # Run optimization - print('Finding ROSCO tuning parameters for u = {}, sm = {}'.format( - u, self.linturb_options['stability_margin'])) - opt_logfile = os.path.join( - self.output_dir, self.output_name + '.' + str(u) + ".opt.sql") - self.om_opt = self.setup_recorder(self.om_opt, opt_logfile) - self.om_opt.run_driver() - self.om_opt.cleanup() + om0 = 0.1 # Initial omega - hard coded + try_count = 0 + while try_count < 3: # Allow this to try three times before failing. NJA: sometimes small initial condition changes can improve convergence + # Setup optimization + self.om_opt.set_val('r_sched.u_eval', u) + self.om_opt.set_val('r_sched.omega', om0) + + # Run optimization + print('Finding ROSCO tuning parameters for u = {}, sm = {}, omega_pc = {}, k_float = {}'.format( + u, self.opt_options['stability_margin'], self.opt_options['omega'], self.opt_options['k_float'])) + opt_logfile = os.path.join( + self.output_dir, self.output_name + '.' + str(u) + ".opt.sql") + self.om_opt = self.setup_recorder(self.om_opt, opt_logfile) + self.om_opt.run_driver() + self.om_opt.cleanup() + + if self.om_opt.driver.fail: + # Restart with a new initial omega if the optimizer failed + try_count += 1 + om0 = np.random.random_sample(1)*self.opt_options['omega'][-1] + else: + try_count += 1 # save values self.omegas.append(self.om_opt.get_val('r_sched.omega')[0]) @@ -236,6 +262,9 @@ def execute(self): self.sms.append(self.om_opt.get_val('r_sched.sm')[0]) def plot_schedule(self): + ''' + Plots tuning value and stability margins w.r.t. wind speed + ''' fig, ax = plt.subplots(3, 1, constrained_layout=True, sharex=True) ax[0].plot(self.opt_options['windspeed'], self.omegas) ax[0].set_ylabel('omega_pc') @@ -250,7 +279,17 @@ def plot_schedule(self): plt.show() def add_dv(self, om_problem, opt_vars): - '''add design variables''' + ''' + Add design variables to OM problem + + Parameters: + ----------- + om_problem: om.problem + RobustScheduling OpenMDAO problem + opt_vars: list + Variables to optimize, e.g.:['omega', 'k_float'] + + ''' if 'omega' in opt_vars and list_check(self.opt_options['omega']): om_problem.model.add_design_var( @@ -261,6 +300,7 @@ def add_dv(self, om_problem, opt_vars): 'r_sched.k_float', lower=self.opt_options['k_float'][0], upper=self.opt_options['k_float'][1], ref=100) # Make sure design variables are stored appropriately in OM problem + # - NJA: This is mostly needed for WEIS integration as a nested OpenMDAO problem om_problem.model._design_vars = om_problem.model._static_design_vars return om_problem @@ -279,10 +319,9 @@ def init_optimization(self, om_problem): def init_doe(self, om_problem, levels=20): '''Initialize DOE driver''' om_problem.driver = om.DOEDriver(om.FullFactorialGenerator(levels=levels)) + # om_problem.driver = om.DOEDriver(om.LatinHypercubeGenerator(samples=levels)) # om_problem.driver = om.DOEDriver(om.UniformGenerator(num_samples=20)) os.makedirs(self.output_dir, exist_ok=True) - # om_problem.driver.options['run_parallel'] = True - # om_problem.driver.options['procs_per_model'] = 1 return om_problem @staticmethod @@ -318,6 +357,23 @@ def post_doe(self, save_csv=False): def load_DOE(doe_logs, outfile_name=None): + ''' + Loads and processes doe log files + - OpenMDAO DOEs generate a large set of log files, this function collects them + + Parameters: + ----------- + doe_logs: str,list + string (single) or list (multiple) of doe log file names + outfile_name: str, optional + name of output .csv file to save data + + Returns: + -------- + df: pd.DataFrame + Pandas dataframe containing collected DOE data + + ''' if isinstance(doe_logs, str): doe_logs = [doe_logs] @@ -360,6 +416,7 @@ def load_DOE(doe_logs, outfile_name=None): def load_OMsql(log): + '''load OpenMDAO sql file''' print('loading {}'.format(log)) cr = om.CaseReader(log) rec_data = {} @@ -375,6 +432,16 @@ def load_OMsql(log): def load_linturb(linfile_path, load_parallel=False): + ''' + Load linear turbine models + + Parameters: + ----------- + linfile_path: string + Path to folder containing .lin files + load_parllel: bool + Load parallel True/False + ''' # Parse openfast linearization filenames filenames = glob.glob(os.path.join(linfile_path, '*.lin')) linfiles = [os.path.split(file)[1] for file in filenames] @@ -386,7 +453,20 @@ def load_linturb(linfile_path, load_parallel=False): return linturb + def load_ROSCO(path_params, turbine_params, controller_params): + ''' + Load ROSCO controller and turbine objects + + Parameters: + ----------- + path_params: dict + Path parameters from tuning yaml + turbine_params: dict + Turbine parameters from tuning yaml + controller_params: dict + Controller parameters from tuning yaml + ''' turbine = ROSCO_turbine.Turbine(turbine_params) controller = ROSCO_controller.Controller(controller_params) @@ -398,7 +478,6 @@ def load_ROSCO(path_params, turbine_params, controller_params): if __name__ == '__main__': - # Setup linear turbine paths linfile_path = os.path.join(os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__)))), 'Test_Cases', 'IEA-15-240-RWT-UMaineSemi', 'linearizations') diff --git a/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py b/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py index 04746f82..72fe0763 100644 --- a/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py +++ b/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py @@ -3,7 +3,7 @@ from ROSCO_toolbox.ofTools.case_gen.CaseGen_General import CaseGen_General from ROSCO_toolbox.ofTools.case_gen.CaseGen_IEC import CaseGen_IEC -from ROSCO_toolbox.ofTools.case_gen.HH_WindFile import HH_StepFile +from ROSCO_toolbox.ofTools.case_gen.HH_WindFile import HH_StepFile, HH_WindFile # ROSCO from ROSCO_toolbox import controller as ROSCO_controller @@ -34,7 +34,7 @@ def set_channels(): "NcIMUTAxs", "NcIMUTAys", "NcIMUTAzs", "NcIMURAxs", "NcIMURAys", "NcIMURAzs", \ "NacYaw", "Wind1VelX", "Wind1VelY", "Wind1VelZ", "LSSTipMxa","LSSTipMya",\ "LSSTipMza","LSSTipMxs","LSSTipMys","LSSTipMzs","LSShftFys","LSShftFzs", \ - "TipRDxr", "TipRDyr", "TipRDzr","RtVAvgxh"]: + "TipRDxr", "TipRDyr", "TipRDzr","RtVAvgxh","RtAeroFxh"]: channels[var] = True return channels @@ -66,22 +66,14 @@ def load_tuning_yaml(tuning_yaml): # ############################################################################################## -def power_curve(run_dir): - # Constant wind speed, multiple wind speeds, define below - - # Runtime - T_max = 400. - - # Run conditions - U = np.arange(4,14.5,.5).tolist() - U = np.linspace(9.5,12,num=16) - +def base_op_case(): case_inputs = {} - # simulation settings - case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} + case_inputs[("Fst","OutFileFmt")] = {'vals':[3], 'group':0} + # DOFs + case_inputs[("ElastoDyn","GenDOF")] = {'vals':['True'], 'group':0} if False: case_inputs[("ElastoDyn","YawDOF")] = {'vals':['True'], 'group':0} case_inputs[("ElastoDyn","FlapDOF1")] = {'vals':['False'], 'group':0} @@ -100,9 +92,6 @@ def power_curve(run_dir): case_inputs[("ElastoDyn","PtfmRDOF")] = {'vals':['False'], 'group':0} case_inputs[("ElastoDyn","PtfmYDOF")] = {'vals':['False'], 'group':0} - # wind inflow - case_inputs[("InflowWind","WindType")] = {'vals':[1], 'group':0} - case_inputs[("InflowWind","HWindSpeed")] = {'vals':U, 'group':1} # Stop Generator from Turning Off case_inputs[('ServoDyn', 'GenTiStr')] = {'vals': ['True'], 'group': 0} @@ -110,7 +99,11 @@ def power_curve(run_dir): case_inputs[('ServoDyn', 'SpdGenOn')] = {'vals': [0.], 'group': 0} case_inputs[('ServoDyn', 'TimGenOn')] = {'vals': [0.], 'group': 0} case_inputs[('ServoDyn', 'GenModel')] = {'vals': [1], 'group': 0} - + + case_inputs[('ServoDyn', 'VSContrl')] = {'vals': [5], 'group': 0} + case_inputs[('ServoDyn', 'PCMode')] = {'vals': [5], 'group': 0} + case_inputs[('ServoDyn', 'HSSBrMode')] = {'vals': [5], 'group': 0} + case_inputs[('ServoDyn', 'YCMode')] = {'vals': [5], 'group': 0} # AeroDyn case_inputs[("AeroDyn15", "WakeMod")] = {'vals': [1], 'group': 0} @@ -130,41 +123,63 @@ def power_curve(run_dir): return case_inputs - # # Controller - # if rosco_dll: - # # Need to update this to ROSCO with power control!!! - # case_inputs[("ServoDyn","DLL_FileName")] = {'vals':[rosco_dll], 'group':0} +def power_curve(**wind_case_opts): + # Constant wind speed, multiple wind speeds, define below - # # Control (DISCON) Inputs - # discon_vt = ROSCO_utilities.read_DISCON(discon_file) - # for discon_input in discon_vt: - # case_inputs[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} + # Runtime + T_max = 400. - # from weis.aeroelasticse.CaseGen_General import CaseGen_General - # case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=runDir, namebase=namebase) + if 'U' in wind_case_opts: + U = wind_case_opts['U'] + else: # default + # Run conditions + U = np.arange(4,14.5,.5).tolist() + U = np.linspace(9.5,12,num=16) + + + case_inputs = base_op_case() + # simulation settings + case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} - # channels = set_channels() + # wind inflow + case_inputs[("InflowWind","WindType")] = {'vals':[1], 'group':0} + case_inputs[("InflowWind","HWindSpeed")] = {'vals':U, 'group':1} - return case_list, case_name_list, channels + return case_inputs -def simp_step(run_dir): +def simp_step(**wind_case_opts): # Set up cases for FIW-JIP project # 3.x in controller tuning register - # Default Runtime - T_max = 300. + if 'T_Max' in wind_case_opts: + T_max = wind_case_opts['T_Max'] + else: #default + T_max = 300. + + if 'U_start' in wind_case_opts: + U_start = wind_case_opts['U_start'] + else: #default + U_start = [16] + + if 'U_end' in wind_case_opts: + U_end = wind_case_opts['U_end'] + else: #default + U_end = [17] + + if 'T_step' in wind_case_opts: + T_step = wind_case_opts['T_step'] + else: #default + T_step = 150 # Step Wind Setup # Make Default step wind object hh_step = HH_StepFile() hh_step.t_max = T_max - hh_step.t_step = 150 - hh_step.wind_directory = run_dir + hh_step.t_step = T_step + hh_step.wind_directory = wind_case_opts['wind_dir'] # Run conditions - U_start = [16] - U_end = [17] step_wind_files = [] for u_s,u_e in zip(U_start,U_end): @@ -176,126 +191,18 @@ def simp_step(run_dir): step_wind_files.append(hh_step.filename) - case_inputs = {} + case_inputs = base_op_case() # simulation settings case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} - case_inputs[("Fst","OutFileFmt")] = {'vals':[2], 'group':0} # case_inputs[("Fst","DT")] = {'vals':[1/80], 'group':0} - - # DOFs - # case_inputs[("ElastoDyn","YawDOF")] = {'vals':['True'], 'group':0} - # case_inputs[("ElastoDyn","FlapDOF1")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","FlapDOF2")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","EdgeDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","DrTrDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","GenDOF")] = {'vals':['True'], 'group':0} - # case_inputs[("ElastoDyn","TwFADOF1")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","TwFADOF2")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","TwSSDOF1")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","TwSSDOF2")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmSgDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmHvDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmPDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmSwDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmRDOF")] = {'vals':['False'], 'group':0} - # case_inputs[("ElastoDyn","PtfmYDOF")] = {'vals':['False'], 'group':0} # wind inflow case_inputs[("InflowWind","WindType")] = {'vals':[2], 'group':0} case_inputs[("InflowWind","Filename_Uni")] = {'vals':step_wind_files, 'group':1} - - # Stop Generator from Turning Off - case_inputs[('ServoDyn', 'GenTiStr')] = {'vals': ['True'], 'group': 0} - case_inputs[('ServoDyn', 'GenTiStp')] = {'vals': ['True'], 'group': 0} - case_inputs[('ServoDyn', 'SpdGenOn')] = {'vals': [0.], 'group': 0} - case_inputs[('ServoDyn', 'TimGenOn')] = {'vals': [0.], 'group': 0} - case_inputs[('ServoDyn', 'GenModel')] = {'vals': [1], 'group': 0} - - - # AeroDyn - case_inputs[("AeroDyn15", "WakeMod")] = {'vals': [1], 'group': 0} - case_inputs[("AeroDyn15", "AFAeroMod")] = {'vals': [2], 'group': 0} - case_inputs[("AeroDyn15", "TwrPotent")] = {'vals': [0], 'group': 0} - case_inputs[("AeroDyn15", "TwrShadow")] = {'vals': ['False'], 'group': 0} - case_inputs[("AeroDyn15", "TwrAero")] = {'vals': ['False'], 'group': 0} - case_inputs[("AeroDyn15", "SkewMod")] = {'vals': [1], 'group': 0} - case_inputs[("AeroDyn15", "TipLoss")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "HubLoss")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "TanInd")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "AIDrag")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "TIDrag")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "IndToler")] = {'vals': [1.e-5], 'group': 0} - case_inputs[("AeroDyn15", "MaxIter")] = {'vals': [5000], 'group': 0} - case_inputs[("AeroDyn15", "UseBlCm")] = {'vals': ['True'], 'group': 0} - - - - # # Tune Floating Feedback Gain - # if tune == 'fl_gain': - # case_inputs[('DISCON_in','Fl_Kp')] = {'vals': np.linspace(0,-18,6,endpoint=True).tolist(), 'group': 2} - - # elif tune == 'fl_phase': - # case_inputs[('DISCON_in','Fl_Kp')] = {'vals': 8*[-25], 'group': 2} - # case_inputs[('DISCON_in','F_FlCornerFreq')] = {'vals': 8*[0.300], 'group': 2} - # case_inputs[('DISCON_in','F_FlHighPassFreq')] = {'vals':[0.001,0.005,0.010,0.020,0.030,0.042,0.060,0.100], 'group': 2} - # case_inputs[('meta','Fl_Phase')] = {'vals':8*[-50],'group':2} - - # elif tune == 'pc_mode': - # # define omega, zeta - # omega = np.linspace(.05,.25,8,endpoint=True).tolist() - # zeta = np.linspace(1,3,3,endpoint=True).tolist() - - # control_case_inputs = sweep_pc_mode(omega,zeta) - # case_inputs.update(control_case_inputs) - - - # elif tune == 'ps_perc': - # # Set sweep limits here - # ps_perc = np.linspace(.75,1,num=8,endpoint=True).tolist() - - # # load default params - # weis_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - # control_param_yaml = os.path.join(weis_dir,'examples/OpenFAST_models/CT15MW-spar/ServoData/IEA15MW-CT-spar.yaml') - # inps = yaml.safe_load(open(control_param_yaml)) - # path_params = inps['path_params'] - # turbine_params = inps['turbine_params'] - # controller_params = inps['controller_params'] - - # # make default controller, turbine objects for ROSCO_toolbox - # turbine = ROSCO_turbine.Turbine(turbine_params) - # turbine.load_from_fast( path_params['FAST_InputFile'],path_params['FAST_directory'], dev_branch=True) - - # controller = ROSCO_controller.Controller(controller_params) - - # # tune default controller - # controller.tune_controller(turbine) - - # # Loop through and make min pitch tables - # ps_ws = [] - # ps_mp = [] - # m_ps = [] # flattened (omega,zeta) pairs - # for p in ps_perc: - # controller.ps_percent = p - # controller.tune_controller(turbine) - # m_ps.append(controller.ps_min_bld_pitch) - - # # add control gains to case_list - # case_inputs[('meta','ps_perc')] = {'vals': ps_perc, 'group': 2} - # case_inputs[('DISCON_in', 'PS_BldPitchMin')] = {'vals': m_ps, 'group': 2} - - # elif tune == 'max_tq': - # case_inputs[('DISCON_in','VS_MaxTq')] = {'vals': [19624046.66639, 1.5*19624046.66639], 'group': 3} - - # elif tune == 'yaw': - # case_inputs[('ElastoDyn','NacYaw')] = {'vals': [-10,0,10], 'group': 3} - - - return case_inputs - -def steps(discon_file,runDir, namebase,rosco_dll=''): +def single_steps(discon_file,runDir, namebase,rosco_dll=''): # Set up cases for FIW-JIP project # 3.x in controller tuning register @@ -331,129 +238,132 @@ def steps(discon_file,runDir, namebase,rosco_dll=''): step_wind_files.append(hh_step.filename) - case_inputs = {} - # simulation settings - case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} - case_inputs[("Fst","OutFileFmt")] = {'vals':[2], 'group':0} + case_inputs = base_op_case() - # DOFs - if True: - case_inputs[("ElastoDyn","YawDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","FlapDOF1")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","FlapDOF2")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","EdgeDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","DrTrDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","GenDOF")] = {'vals':['True'], 'group':0} - case_inputs[("ElastoDyn","TwFADOF1")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","TwFADOF2")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","TwSSDOF1")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","TwSSDOF2")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmSgDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmHvDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmPDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmSwDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmRDOF")] = {'vals':['False'], 'group':0} - case_inputs[("ElastoDyn","PtfmYDOF")] = {'vals':['False'], 'group':0} - # wind inflow case_inputs[("InflowWind","WindType")] = {'vals':[2], 'group':0} - case_inputs[("InflowWind","Filename")] = {'vals':step_wind_files, 'group':1} - + case_inputs[("InflowWind","Filename_Uni")] = {'vals':step_wind_files, 'group':1} - # Stop Generator from Turning Off - case_inputs[('ServoDyn', 'GenTiStr')] = {'vals': ['True'], 'group': 0} - case_inputs[('ServoDyn', 'GenTiStp')] = {'vals': ['True'], 'group': 0} - case_inputs[('ServoDyn', 'SpdGenOn')] = {'vals': [0.], 'group': 0} - case_inputs[('ServoDyn', 'TimGenOn')] = {'vals': [0.], 'group': 0} - case_inputs[('ServoDyn', 'GenModel')] = {'vals': [1], 'group': 0} - +def steps(**wind_case_opts): + # Muliple steps in same simulation at time, wind breakpoints, this function adds zero-order hold, 100 seconds to end - # AeroDyn - case_inputs[("AeroDyn15", "WakeMod")] = {'vals': [1], 'group': 0} - case_inputs[("AeroDyn15", "AFAeroMod")] = {'vals': [2], 'group': 0} - case_inputs[("AeroDyn15", "TwrPotent")] = {'vals': [0], 'group': 0} - case_inputs[("AeroDyn15", "TwrShadow")] = {'vals': ['False'], 'group': 0} - case_inputs[("AeroDyn15", "TwrAero")] = {'vals': ['False'], 'group': 0} - case_inputs[("AeroDyn15", "SkewMod")] = {'vals': [1], 'group': 0} - case_inputs[("AeroDyn15", "TipLoss")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "HubLoss")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "TanInd")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "AIDrag")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "TIDrag")] = {'vals': ['True'], 'group': 0} - case_inputs[("AeroDyn15", "IndToler")] = {'vals': [1.e-5], 'group': 0} - case_inputs[("AeroDyn15", "MaxIter")] = {'vals': [5000], 'group': 0} - case_inputs[("AeroDyn15", "UseBlCm")] = {'vals': ['True'], 'group': 0} + if 'tt' in wind_case_opts and 'U' in wind_case_opts: + tt = wind_case_opts['tt'] + U = wind_case_opts['U'] + else: + raise Exception('You must define tt and U in **wind_case_opts dict to use steps() fcn') - # Controller - if rosco_dll: - # Need to update this to ROSCO with power control!!! - case_inputs[("ServoDyn","DLL_FileName")] = {'vals':[rosco_dll], 'group':0} + if 'dt' in wind_case_opts: + dt = wind_case_opts['dt'] + else: + dt = 0.05 - # Control (DISCON) Inputs - discon_vt = ROSCO_utilities.read_DISCON(discon_file) - for discon_input in discon_vt: - case_inputs[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} + if 'U_0' in wind_case_opts: + U_0 = wind_case_opts['U_0'] + else: + U_0 = U[0] - from weis.aeroelasticse.CaseGen_General import CaseGen_General - case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=runDir, namebase=namebase) + if 'T_max' in wind_case_opts: + T_max = wind_case_opts['T_max'] + else: + T_max = tt[-1] + 100 - channels = set_channels() + if len(tt) != len(U): + raise Exception('steps: len(tt) and len(U) must be the same') - return case_list, case_name_list, channels + # Make Default step wind object + hh_wind = HH_WindFile() + hh_wind.t_max = T_max + hh_wind.filename = os.path.join(wind_case_opts['run_dir'],'steps.hh') -def sweep_pc_mode(cont_yaml,omega=np.linspace(.05,.35,8,endpoint=True).tolist(),zeta=[1.5],group=2): - + # Step Wind Setup + hh_wind.time = [0] + hh_wind.wind_speed = [U_0] + for t, u in zip(tt,U): + hh_wind.time.append(t-dt) + hh_wind.wind_speed.append(hh_wind.wind_speed[-1]) + + hh_wind.time.append(t) + hh_wind.wind_speed.append(u) + + hh_wind.wind_dir = [0] * len(hh_wind.time) + hh_wind.vert_speed = [0] * len(hh_wind.time) + hh_wind.horiz_shear = [0] * len(hh_wind.time) + hh_wind.vert_shear = [0] * len(hh_wind.time) + hh_wind.linv_shear = [0] * len(hh_wind.time) + hh_wind.gust_speed = [0] * len(hh_wind.time) - inps = yaml.safe_load(open(cont_yaml)) - path_params = inps['path_params'] - turbine_params = inps['turbine_params'] - controller_params = inps['controller_params'] + if False: + hh_wind.plot() - # make default controller, turbine objects for ROSCO_toolbox - turbine = ROSCO_turbine.Turbine(turbine_params) - turbine.load_from_fast( path_params['FAST_InputFile'],path_params['FAST_directory'], dev_branch=True) - - controller = ROSCO_controller.Controller(controller_params) - - # tune default controller - controller.tune_controller(turbine) - - # check if inputs are lists - if not isinstance(omega,list): - omega = [omega] - if not isinstance(zeta,list): - zeta = [zeta] - - # Loop through and make PI gains - pc_kp = [] - pc_ki = [] - m_omega = [] # flattened (omega,zeta) pairs - m_zeta = [] # flattened (omega,zeta) pairs - for o in omega: - for z in zeta: - controller.omega_pc = o - controller.zeta_pc = z - controller.tune_controller(turbine) - pc_kp.append(controller.pc_gain_schedule.Kp.tolist()) - pc_ki.append(controller.pc_gain_schedule.Ki.tolist()) - m_omega.append(o) - m_zeta.append(z) + hh_wind.write() + case_inputs = base_op_case() - # add control gains to case_list - case_inputs = {} - case_inputs[('meta','omega')] = {'vals': m_omega, 'group': group} - case_inputs[('meta','zeta')] = {'vals': m_zeta, 'group': group} - case_inputs[('DISCON_in', 'PC_GS_KP')] = {'vals': pc_kp, 'group': group} - case_inputs[('DISCON_in', 'PC_GS_KI')] = {'vals': pc_ki, 'group': group} + case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} + + + # wind inflow + case_inputs[("InflowWind","WindType")] = {'vals':[2], 'group':0} + case_inputs[("InflowWind","Filename_Uni")] = {'vals':[hh_wind.filename], 'group':0} + + return case_inputs + +def turb_bts(**wind_case_opts): + ''' + Turbulent wind input from bts file + Expected inputs: + TMax TODO: someday make all TMaxs TMax + wind_inputs (list of string wind inputs filenames) + ''' + + if 'TMax' in wind_case_opts: + TMax = wind_case_opts['TMax'] + else: + TMax = 720 + + if 'wind_filenames' not in wind_case_opts: + raise Exception('Define wind_filenames when using turb_bts case generator') + + # wind inflow + case_inputs = base_op_case() + case_inputs[("Fst","TMax")] = {'vals':[TMax], 'group':0} + case_inputs[("InflowWind","WindType")] = {'vals':[3], 'group':0} + case_inputs[("InflowWind","FileName_BTS")] = {'vals':wind_case_opts['wind_filenames'], 'group':1} return case_inputs -# Control sweep functions -# function(controller,turbine,start_group) +def user_hh(**wind_case_opts): + ''' + Uniform, hub-height wind file + Expected inputs: + TMax TODO: someday make all TMaxs TMax + wind_inputs (list of string wind inputs filenames) + ''' + if 'TMax' in wind_case_opts: + TMax = wind_case_opts['TMax'] + else: + TMax = 720 -def sweep_rated_torque(tuning_yaml,start_group): + if 'wind_filenames' not in wind_case_opts: + raise Exception('Define wind_filenames when using turb_bts case generator') + + # wind inflow + case_inputs = base_op_case() + case_inputs[("Fst","TMax")] = {'vals':[TMax], 'group':0} + case_inputs[("InflowWind","WindType")] = {'vals':[2], 'group':0} + case_inputs[("InflowWind","Filename_Uni")] = {'vals':wind_case_opts['wind_filenames'], 'group':1} + + return case_inputs + +############################################################################################## +# +# Control sweep cases +# +############################################################################################## + +def sweep_rated_torque(start_group, **control_sweep_opts): # Sweep multiplier of original rated torque multipliers = np.linspace(1,1.5,5) @@ -500,7 +410,166 @@ def sweep_rated_torque(tuning_yaml,start_group): case_inputs_control[('DISCON_in',discon_input)] = {'vals': discon_array[discon_input], 'group': start_group} return case_inputs_control - + +def sweep_pitch_act(start_group, **control_sweep_opts): + if 'act_bw' in control_sweep_opts: + act_bw = control_sweep_opts['act_bw'] + else: + raise Exception('Define act_bw to sweep or program something else.') + + case_inputs_control = {} + case_inputs_control[('DISCON_in','PA_CornerFreq')] = {'vals': act_bw.tolist(), 'group': start_group} + + return case_inputs_control + +def sweep_ipc_gains(start_group, **control_sweep_opts): + case_inputs_control = {} + + kis = np.linspace(0,3,6).tolist() + # kis = [0.,0.6,1.2,1.8,2.4,3.] + KIs = [[ki * 1e-8,0.] for ki in kis] + case_inputs_control[('DISCON_in','IPC_ControlMode')] = {'vals': [1], 'group': 0} + # case_inputs_control[('DISCON_in','IPC_KI')] = {'vals': [[0.,0.],[1e-8,0.]], 'group': start_group} + case_inputs_control[('DISCON_in','IPC_KI')] = {'vals': KIs, 'group': start_group} + case_inputs_control[('DISCON_in','IPC_aziOffset')] = {'vals': [[0.0,0]], 'group': 0} + case_inputs_control[('DISCON_in','IPC_IntSat')] = {'vals': [0.2618], 'group': 0} + + # [-0.5236,-0.43633,-0.34907,-0.2618,-0.17453,-0.087266 0 0.087266 0.17453 0.2618 0.34907 0.43633 0.5236 0.61087 0.69813 0.7854' + + return case_inputs_control + +def sweep_fad_gains(start_group, **control_sweep_opts): + case_inputs_control = {} + g = np.array([0.,0.5,1.,1.5,2.0,2.5,3.0,3.5,4.0,5.0]) + case_inputs_control[('DISCON_in','TD_Mode')] = {'vals': [1], 'group': start_group} + case_inputs_control[('DISCON_in','FA_KI')] = {'vals': (g*0.0175).tolist(), 'group': start_group+1} + case_inputs_control[('DISCON_in','FA_HPFCornerFreq')] = {'vals': [0.1], 'group': start_group} + case_inputs_control[('DISCON_in','FA_IntSat')] = {'vals': [0.2618], 'group': start_group} + + # [-0.5236,-0.43633,-0.34907,-0.2618,-0.17453,-0.087266 0 0.087266 0.17453 0.2618 0.34907 0.43633 0.5236 0.61087 0.69813 0.7854' + + return case_inputs_control + +def sweep_max_torque(start_group, **control_sweep_opts): + case_inputs_control = {} + max_torque = np.array([1.,1.05,1.1,1.15,1.2]) * 18651.96057000 + case_inputs_control[('DISCON_in','VS_MaxTq')] = {'vals': max_torque, 'group': start_group} + + return case_inputs_control + +def sweep_ps_percent(start_group, **control_sweep_opts): + case_inputs_control = {} + + # Set sweep limits here + ps_perc = np.linspace(.7,1,num=8,endpoint=True).tolist() + + # load default params + control_param_yaml = control_sweep_opts['tuning_yaml'] + inps = load_rosco_yaml(control_param_yaml) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # make default controller, turbine objects for ROSCO_toolbox + turbine = ROSCO_turbine.Turbine(turbine_params) + turbine.load_from_fast( path_params['FAST_InputFile'],path_params['FAST_directory'], dev_branch=True) + + controller = ROSCO_controller.Controller(controller_params) + + # tune default controller + controller.tune_controller(turbine) + + # Loop through and make min pitch tables + ps_ws = [] + ps_mp = [] + m_ps = [] # flattened (omega,zeta) pairs + for p in ps_perc: + controller.ps_percent = p + controller.tune_controller(turbine) + m_ps.append(controller.ps_min_bld_pitch) + ps_ws.append(controller.v) + + # add control gains to case_list + # case_inputs_control[('meta','ps_perc')] = {'vals': ps_perc, 'group': start_group} + case_inputs_control[('DISCON_in', 'PS_BldPitchMin')] = {'vals': m_ps, 'group': start_group} + case_inputs_control[('DISCON_in', 'PS_WindSpeeds')] = {'vals': ps_ws, 'group': start_group} + + return case_inputs_control + + +# def sweep_pc_mode(cont_yaml,omega=np.linspace(.05,.35,8,endpoint=True).tolist(),zeta=[1.5],group=2): + + +# inps = yaml.safe_load(open(cont_yaml)) +# path_params = inps['path_params'] +# turbine_params = inps['turbine_params'] +# controller_params = inps['controller_params'] + +# # make default controller, turbine objects for ROSCO_toolbox +# turbine = ROSCO_turbine.Turbine(turbine_params) +# turbine.load_from_fast( path_params['FAST_InputFile'],path_params['FAST_directory'], dev_branch=True) + +# controller = ROSCO_controller.Controller(controller_params) + +# # tune default controller +# controller.tune_controller(turbine) + +# # check if inputs are lists +# if not isinstance(omega,list): +# omega = [omega] +# if not isinstance(zeta,list): +# zeta = [zeta] + +# # Loop through and make PI gains +# pc_kp = [] +# pc_ki = [] +# m_omega = [] # flattened (omega,zeta) pairs +# m_zeta = [] # flattened (omega,zeta) pairs +# for o in omega: +# for z in zeta: +# controller.omega_pc = o +# controller.zeta_pc = z +# controller.tune_controller(turbine) +# pc_kp.append(controller.pc_gain_schedule.Kp.tolist()) +# pc_ki.append(controller.pc_gain_schedule.Ki.tolist()) +# m_omega.append(o) +# m_zeta.append(z) + +# # add control gains to case_list +# case_inputs = {} +# case_inputs[('meta','omega')] = {'vals': m_omega, 'group': group} +# case_inputs[('meta','zeta')] = {'vals': m_zeta, 'group': group} +# case_inputs[('DISCON_in', 'PC_GS_KP')] = {'vals': pc_kp, 'group': group} +# case_inputs[('DISCON_in', 'PC_GS_KI')] = {'vals': pc_ki, 'group': group} + +# return case_inputs + + ## Old sweep functions + # # Tune Floating Feedback Gain + # if tune == 'fl_gain': + # case_inputs[('DISCON_in','Fl_Kp')] = {'vals': np.linspace(0,-18,6,endpoint=True).tolist(), 'group': 2} + + # elif tune == 'fl_phase': + # case_inputs[('DISCON_in','Fl_Kp')] = {'vals': 8*[-25], 'group': 2} + # case_inputs[('DISCON_in','F_FlCornerFreq')] = {'vals': 8*[0.300], 'group': 2} + # case_inputs[('DISCON_in','F_FlHighPassFreq')] = {'vals':[0.001,0.005,0.010,0.020,0.030,0.042,0.060,0.100], 'group': 2} + # case_inputs[('meta','Fl_Phase')] = {'vals':8*[-50],'group':2} + + # elif tune == 'pc_mode': + # # define omega, zeta + # omega = np.linspace(.05,.25,8,endpoint=True).tolist() + # zeta = np.linspace(1,3,3,endpoint=True).tolist() + + # control_case_inputs = sweep_pc_mode(omega,zeta) + # case_inputs.update(control_case_inputs) + + + + # elif tune == 'max_tq': + # case_inputs[('DISCON_in','VS_MaxTq')] = {'vals': [19624046.66639, 1.5*19624046.66639], 'group': 3} + + # elif tune == 'yaw': + # case_inputs[('ElastoDyn','NacYaw')] = {'vals': [-10,0,10], 'group': 3} diff --git a/ROSCO_toolbox/ofTools/case_gen/runFAST_pywrapper.py b/ROSCO_toolbox/ofTools/case_gen/runFAST_pywrapper.py index e0715713..886d496b 100644 --- a/ROSCO_toolbox/ofTools/case_gen/runFAST_pywrapper.py +++ b/ROSCO_toolbox/ofTools/case_gen/runFAST_pywrapper.py @@ -5,23 +5,56 @@ """ # Hacky way of doing relative imports from __future__ import print_function -import os, sys, time +import os, platform import multiprocessing as mp -# sys.path.insert(0, os.path.abspath("..")) -from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_Common, InputReader_OpenFAST, InputReader_FAST7 -from ROSCO_toolbox.ofTools.fast_io.FAST_writer import InputWriter_Common, InputWriter_OpenFAST, InputWriter_FAST7 -from ROSCO_toolbox.ofTools.fast_io.FAST_wrapper import FastWrapper -from ROSCO_toolbox.ofTools.fast_io.FAST_post import FAST_IO_timeseries +from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST +from ROSCO_toolbox.ofTools.fast_io.FAST_writer import InputWriter_OpenFAST +from ROSCO_toolbox.ofTools.fast_io.FAST_wrapper import FAST_wrapper + +# TODO: import weis and use library, import pCrunch and re-enable post-processing features available here import numpy as np +mactype = platform.system().lower() +if mactype in ["linux", "linux2"]: + libext = ".so" +elif mactype in ["win32", "windows", "cygwin"]: #NOTE: platform.system()='Windows', sys.platform='win32' + libext = '.dll' +elif mactype == "darwin": + libext = '.dylib' +else: + raise ValueError('Unknown platform type: '+mactype) + + +# magnitude_channels_default = { +# 'LSShftF': ["RotThrust", "LSShftFys", "LSShftFzs"], +# 'LSShftM': ["RotTorq", "LSSTipMys", "LSSTipMzs"], +# 'RootMc1': ["RootMxc1", "RootMyc1", "RootMzc1"], +# 'RootMc2': ["RootMxc2", "RootMyc2", "RootMzc2"], +# 'RootMc3': ["RootMxc3", "RootMyc3", "RootMzc3"], +# 'TipDc1': ['TipDxc1', 'TipDyc1', 'TipDzc1'], +# 'TipDc2': ['TipDxc2', 'TipDyc2', 'TipDzc2'], +# 'TipDc3': ['TipDxc3', 'TipDyc3', 'TipDzc3'], +# 'TwrBsM': ['TwrBsMxt', 'TwrBsMyt', 'TwrBsMzt'], +# } + +# fatigue_channels_default = { +# 'RootMc1': FatigueParams(slope=10), +# 'RootMc2': FatigueParams(slope=10), +# 'RootMc3': FatigueParams(slope=10), +# 'RootMyb1': FatigueParams(slope=10), +# 'RootMyb2': FatigueParams(slope=10), +# 'RootMyb3': FatigueParams(slope=10), +# 'TwrBsM': FatigueParams(slope=4), +# 'LSShftM': FatigueParams(slope=4), +# } class runFAST_pywrapper(object): def __init__(self, **kwargs): - self.FAST_ver = 'OPENFAST' #(FAST7, FAST8, OPENFAST) self.FAST_exe = None + self.FAST_lib = None self.FAST_InputFile = None self.FAST_directory = None self.FAST_runDirectory = None @@ -31,8 +64,15 @@ def __init__(self, **kwargs): self.fst_vt = {} self.case = {} # dictionary of variable values to change self.channels = {} # dictionary of output channels to change - self.debug_level = 0 - + self.keep_time = False + self.use_exe = True # use openfast executable instead of library, helpful for debugging sometimes + self.goodman = False + # self.magnitude_channels = magnitude_channels_default + # self.fatigue_channels = fatigue_channels_default + self.la = None # Will be initialized on first run through + self.allow_fails = False + self.fail_value = 9999 + self.overwrite_outfiles = True # True: existing output files will be overwritten, False: if output file with the same name already exists, OpenFAST WILL NOT RUN; This is primarily included for code debugging with OpenFAST in the loop or for specific Optimization Workflows where OpenFAST is to be run periodically instead of for every objective function anaylsis # Optional population class attributes from key word arguments @@ -44,29 +84,29 @@ def __init__(self, **kwargs): super(runFAST_pywrapper, self).__init__() + # def init_crunch(self): + # if self.la is None: + # self.la = LoadsAnalysis( + # outputs=[], + # magnitude_channels=self.magnitude_channels, + # fatigue_channels=self.fatigue_channels, + # #extreme_channels=channel_extremes_default, + # ) + def execute(self): # FAST version specific initialization - if self.FAST_ver.lower() == 'fast7': - reader = InputReader_FAST7(FAST_ver=self.FAST_ver) - writer = InputWriter_FAST7(FAST_ver=self.FAST_ver) - elif self.FAST_ver.lower() in ['fast8','openfast']: - reader = InputReader_OpenFAST(FAST_ver=self.FAST_ver) - writer = InputWriter_OpenFAST(FAST_ver=self.FAST_ver) - wrapper = FastWrapper(FAST_ver=self.FAST_ver, debug_level=self.debug_level) + reader = InputReader_OpenFAST() + writer = InputWriter_OpenFAST() # Read input model, FAST files or Yaml if self.fst_vt == {}: - if self.read_yaml: - reader.FAST_yamlfile = self.FAST_yamlfile_in - reader.read_yaml() - else: - reader.FAST_InputFile = self.FAST_InputFile - reader.FAST_directory = self.FAST_directory - reader.execute() + reader.FAST_InputFile = self.FAST_InputFile + reader.FAST_directory = self.FAST_directory + reader.execute() # Initialize writer variables with input model - writer.fst_vt = reader.fst_vt + writer.fst_vt = self.fst_vt = reader.fst_vt else: writer.fst_vt = self.fst_vt writer.FAST_runDirectory = self.FAST_runDirectory @@ -83,34 +123,116 @@ def execute(self): writer.FAST_yamlfile = self.FAST_yamlfile_out writer.write_yaml() - # Run FAST - wrapper.FAST_exe = self.FAST_exe - wrapper.FAST_InputFile = os.path.split(writer.FAST_InputFileOut)[1] - wrapper.FAST_directory = os.path.split(writer.FAST_InputFileOut)[0] + # Make sure pCrunch is ready + # self.init_crunch() + + if not self.use_exe: # Use library + raise Exception('ROSCO ofTools does not support running OpenFAST from a library, need to import WEIS.') - FAST_Output = os.path.join(wrapper.FAST_directory, wrapper.FAST_InputFile[:-3]+'outb') - FAST_Output_txt = os.path.join(wrapper.FAST_directory, wrapper.FAST_InputFile[:-3]+'out') + # FAST_directory = os.path.split(writer.FAST_InputFileOut)[0] + + # orig_dir = os.getcwd() + # os.chdir(FAST_directory) + + # openfastlib = FastLibAPI(self.FAST_lib, os.path.abspath(os.path.basename(writer.FAST_InputFileOut))) + # openfastlib.fast_run() - #check if OpenFAST is set not to overwrite existing output files, TODO: move this further up in the workflow for minor computation savings - if self.overwrite_outfiles or (not self.overwrite_outfiles and not (os.path.exists(FAST_Output) or os.path.exists(FAST_Output_txt))): - wrapper.execute() - else: - if self.debug_level>0: - print('OpenFAST not execute: Output file "%s" already exists. To overwrite this output file, set "overwrite_outfiles = True".'%FAST_Output) + # output_dict = {} + # for i, channel in enumerate(openfastlib.output_channel_names): + # output_dict[channel] = openfastlib.output_values[:,i] + # del(openfastlib) + + # # Add channel to indicate failed run + # output_dict['openfast_failed'] = np.zeros(len(output_dict[channel])) + + # output = OpenFASTOutput.from_dict(output_dict, self.FAST_namingOut, magnitude_channels=self.magnitude_channels) + + # # if save_file: write_fast + # os.chdir(orig_dir) + + # if not self.keep_time: output_dict = None + + else: # use executable + wrapper = FAST_wrapper() + + # Run FAST + wrapper.FAST_exe = self.FAST_exe + wrapper.FAST_InputFile = os.path.split(writer.FAST_InputFileOut)[1] + wrapper.FAST_directory = os.path.split(writer.FAST_InputFileOut)[0] + + wrapper.allow_fails = self.allow_fails + wrapper.fail_value = self.fail_value + + FAST_Output = os.path.join(wrapper.FAST_directory, wrapper.FAST_InputFile[:-3]+'outb') + FAST_Output_txt = os.path.join(wrapper.FAST_directory, wrapper.FAST_InputFile[:-3]+'out') + + #check if OpenFAST is set not to overwrite existing output files, TODO: move this further up in the workflow for minor computation savings + if self.overwrite_outfiles or (not self.overwrite_outfiles and not (os.path.exists(FAST_Output) or os.path.exists(FAST_Output_txt))): + failed = wrapper.execute() + if failed: + print('OpenFAST Failed! Please check the run logs.') + if self.allow_fails: + print(f'OpenFAST failures are allowed. All outputs set to {self.fail_value}') + else: + raise Exception('OpenFAST Failed! Please check the run logs.') + else: + failed = False + print('OpenFAST not executed: Output file "%s" already exists. To overwrite this output file, set "overwrite_outfiles = True".'%FAST_Output) + + # if not failed: + # if os.path.exists(FAST_Output): + # output_init = OpenFASTBinary(FAST_Output, magnitude_channels=self.magnitude_channels) + # elif os.path.exists(FAST_Output_txt): + # output_init = OpenFASTAscii(FAST_Output, magnitude_channels=self.magnitude_channels) + + # output_init.read() + + # # Make output dict + # output_dict = {} + # for i, channel in enumerate(output_init.channels): + # output_dict[channel] = output_init.df[channel].to_numpy() + + # # Add channel to indicate failed run + # output_dict['openfast_failed'] = np.zeros(len(output_dict[channel])) + + # # Re-make output + # output = OpenFASTOutput.from_dict(output_dict, self.FAST_namingOut) + + # else: # fill with -9999s + # output_dict = {} + # output_dict['Time'] = np.arange(self.fst_vt['Fst']['TStart'],self.fst_vt['Fst']['TMax'],self.fst_vt['Fst']['DT']) + # for module in self.fst_vt['outlist']: + # for channel in self.fst_vt['outlist'][module]: + # if self.fst_vt['outlist'][module][channel]: + # output_dict[channel] = np.full(len(output_dict['Time']),fill_value=self.fail_value, dtype=np.uint8) + + # # Add channel to indicate failed run + # output_dict['openfast_failed'] = np.ones(len(output_dict['Time']), dtype=np.uint8) + + # output = OpenFASTOutput.from_dict(output_dict, self.FAST_namingOut, magnitude_channels=self.magnitude_channels) + + + + # # Trim Data + # if self.fst_vt['Fst']['TStart'] > 0.0: + # output.trim_data(tmin=self.fst_vt['Fst']['TStart'], tmax=self.fst_vt['Fst']['TMax']) + # case_name, sum_stats, extremes, dels, damage = self.la._process_output(output, + # return_damage=True, + # goodman_correction=self.goodman) + + # return case_name, sum_stats, extremes, dels, damage, output_dict - return FAST_Output class runFAST_pywrapper_batch(object): - def __init__(self, **kwargs): + def __init__(self): - self.FAST_ver = 'OpenFAST' run_dir = os.path.dirname( os.path.dirname( os.path.dirname( os.path.realpath(__file__) ) ) ) + os.sep self.FAST_exe = os.path.join(run_dir, 'local/bin/openfast') # Path to executable + # self.FAST_lib = os.path.join(lib_dir, 'libopenfastlib'+libext) self.FAST_InputFile = None self.FAST_directory = None self.FAST_runDirectory = None - self.debug_level = 0 self.read_yaml = False self.FAST_yamlfile_in = '' @@ -123,30 +245,85 @@ def __init__(self, **kwargs): self.channels = {} self.overwrite_outfiles = True - + self.keep_time = False + + self.goodman = False + # self.magnitude_channels = magnitude_channels_default + # self.fatigue_channels = fatigue_channels_default + self.la = None + self.use_exe = True + self.allow_fails = False + self.fail_value = 9999 + self.post = None - # Optional population of class attributes from key word arguments - for (k, w) in kwargs.items(): - try: - setattr(self, k, w) - except: - pass + # def init_crunch(self): + # if self.la is None: + # self.la = LoadsAnalysis( + # outputs=[], + # magnitude_channels=self.magnitude_channels, + # fatigue_channels=self.fatigue_channels, + # #extreme_channels=channel_extremes_default, + # ) - super(runFAST_pywrapper_batch, self).__init__() + def create_case_data(self): - + case_data_all = [] + for i in range(len(self.case_list)): + case_data = {} + case_data['case'] = self.case_list[i] + case_data['case_name'] = self.case_name_list[i] + case_data['FAST_exe'] = self.FAST_exe + # case_data['FAST_lib'] = self.FAST_lib + case_data['FAST_runDirectory'] = self.FAST_runDirectory + case_data['FAST_InputFile'] = self.FAST_InputFile + case_data['FAST_directory'] = self.FAST_directory + case_data['read_yaml'] = self.read_yaml + case_data['FAST_yamlfile_in'] = self.FAST_yamlfile_in + case_data['fst_vt'] = self.fst_vt + case_data['write_yaml'] = self.write_yaml + case_data['FAST_yamlfile_out'] = self.FAST_yamlfile_out + case_data['channels'] = self.channels + case_data['overwrite_outfiles'] = self.overwrite_outfiles + case_data['use_exe'] = self.use_exe + case_data['allow_fails'] = self.allow_fails + case_data['fail_value'] = self.fail_value + case_data['keep_time'] = self.keep_time + case_data['goodman'] = self.goodman + # case_data['magnitude_channels'] = self.magnitude_channels + # case_data['fatigue_channels'] = self.fatigue_channels + case_data['post'] = self.post + + case_data_all.append(case_data) + + return case_data_all + def run_serial(self): # Run batch serially - if not os.path.exists(self.FAST_runDirectory): os.makedirs(self.FAST_runDirectory) - out = [None]*len(self.case_list) - for i, (case, case_name) in enumerate(zip(self.case_list, self.case_name_list)): - out[i] = eval(case, case_name, self.FAST_ver, self.FAST_exe, self.FAST_runDirectory, self.FAST_InputFile, self.FAST_directory, self.read_yaml, self.FAST_yamlfile_in, self.fst_vt, self.write_yaml, self.FAST_yamlfile_out, self.channels, self.debug_level, self.overwrite_outfiles, self.post) + # self.init_crunch() + + case_data_all = self.create_case_data() + + # ss = {} + # et = {} + # dl = {} + # dam = {} + # ct = [] + for c in case_data_all: + # _name, _ss, _et, _dl, _dam, _ct = evaluate(c) + evaluate(c) + # ss[_name] = _ss + # et[_name] = _et + # dl[_name] = _dl + # dam[_name] = _dam + # ct.append(_ct) + + # summary_stats, extreme_table, DELs, Damage = self.la.post_process(ss, et, dl, dam) - return out + # return summary_stats, extreme_table, DELs, Damage, ct def run_multi(self, cores=None): # Run cases in parallel, threaded with multiprocessing module @@ -158,35 +335,32 @@ def run_multi(self, cores=None): cores = mp.cpu_count() pool = mp.Pool(cores) - case_data_all = [] - for i in range(len(self.case_list)): - case_data = [] - case_data.append(self.case_list[i]) - case_data.append(self.case_name_list[i]) - case_data.append(self.FAST_ver) - case_data.append(self.FAST_exe) - case_data.append(self.FAST_runDirectory) - case_data.append(self.FAST_InputFile) - case_data.append(self.FAST_directory) - case_data.append(self.read_yaml) - case_data.append(self.FAST_yamlfile_in) - case_data.append(self.fst_vt) - case_data.append(self.write_yaml) - case_data.append(self.FAST_yamlfile_out) - case_data.append(self.channels) - case_data.append(self.debug_level) - case_data.append(self.overwrite_outfiles) - case_data.append(self.post) + # self.init_crunch() - case_data_all.append(case_data) + case_data_all = self.create_case_data() - output = pool.map(eval_multi, case_data_all) + output = pool.map(evaluate_multi, case_data_all) pool.close() pool.join() - return output + # ss = {} + # et = {} + # dl = {} + # dam = {} + # ct = [] + # for _name, _ss, _et, _dl, _dam, _ct in output: + # ss[_name] = _ss + # et[_name] = _et + # dl[_name] = _dl + # dam[_name] = _dam + # ct.append(_ct) + + # summary_stats, extreme_table, DELs, Damage = self.la.post_process(ss, et, dl, dam) + + # return summary_stats, extreme_table, DELs, Damage, ct def run_mpi(self, mpi_comm_map_down): + # Run in parallel with mpi from mpi4py import MPI @@ -203,27 +377,9 @@ def run_mpi(self, mpi_comm_map_down): if not os.path.exists(self.FAST_runDirectory) and rank == 0: os.makedirs(self.FAST_runDirectory) - case_data_all = [] - for i in range(N_cases): - case_data = [] - case_data.append(self.case_list[i]) - case_data.append(self.case_name_list[i]) - case_data.append(self.FAST_ver) - case_data.append(self.FAST_exe) - case_data.append(self.FAST_runDirectory) - case_data.append(self.FAST_InputFile) - case_data.append(self.FAST_directory) - case_data.append(self.read_yaml) - case_data.append(self.FAST_yamlfile_in) - case_data.append(self.fst_vt) - case_data.append(self.write_yaml) - case_data.append(self.FAST_yamlfile_out) - case_data.append(self.channels) - case_data.append(self.debug_level) - case_data.append(self.overwrite_outfiles) - case_data.append(self.post) + # self.init_crunch() - case_data_all.append(case_data) + case_data_all = self.create_case_data() output = [] for i in range(N_loops): @@ -231,7 +387,7 @@ def run_mpi(self, mpi_comm_map_down): idx_e = min((i+1)*size, N_cases) for j, case_data in enumerate(case_data_all[idx_s:idx_e]): - data = [eval_multi, case_data] + data = [evaluate_multi, case_data] rank_j = sub_ranks[j] comm.send(data, dest=rank_j, tag=0) @@ -241,291 +397,45 @@ def run_mpi(self, mpi_comm_map_down): data_out = comm.recv(source=rank_j, tag=1) output.append(data_out) - return output - - - # def run_mpi(self, comm=None): - # # Run in parallel with mpi - # from mpi4py import MPI - - # # mpi comm management - # if not comm: - # comm = MPI.COMM_WORLD - # size = comm.Get_size() - # rank = comm.Get_rank() - - # N_cases = len(self.case_list) - # N_loops = int(np.ceil(float(N_cases)/float(size))) + # ss = {} + # et = {} + # dl = {} + # dam = {} + # ct = [] + # for _name, _ss, _et, _dl, _dam, _ct in output: + # ss[_name] = _ss + # et[_name] = _et + # dl[_name] = _dl + # dam[_name] = _dam + # ct.append(_ct) + + # summary_stats, extreme_table, DELs, Damage = self.la.post_process(ss, et, dl, dam) - # # file management - # if not os.path.exists(self.FAST_runDirectory) and rank == 0: - # os.makedirs(self.FAST_runDirectory) - - # if rank == 0: - # case_data_all = [] - # for i in range(N_cases): - # case_data = [] - # case_data.append(self.case_list[i]) - # case_data.append(self.case_name_list[i]) - # case_data.append(self.FAST_ver) - # case_data.append(self.FAST_exe) - # case_data.append(self.FAST_runDirectory) - # case_data.append(self.FAST_InputFile) - # case_data.append(self.FAST_directory) - # case_data.append(self.read_yaml) - # case_data.append(self.FAST_yamlfile_in) - # case_data.append(self.fst_vt) - # case_data.append(self.write_yaml) - # case_data.append(self.FAST_yamlfile_out) - # case_data.append(self.channels) - # case_data.append(self.debug_level) - # case_data.append(self.post) - - # case_data_all.append(case_data) - # else: - # case_data_all = [] - - # output = [] - # for i in range(N_loops): - # # if # of cases left to run is less than comm size, split comm - # n_resid = N_cases - i*size - # if n_resid < size: - # split_comm = True - # color = np.zeros(size) - # for i in range(n_resid): - # color[i] = 1 - # color = [int(j) for j in color] - # comm_i = MPI.COMM_WORLD.Split(color, 1) - # else: - # split_comm = False - # comm_i = comm - - # # position in case list - # idx_s = i*size - # idx_e = min((i+1)*size, N_cases) - - # # scatter out cases - # if split_comm: - # if color[rank] == 1: - # case_data_i = comm_i.scatter(case_data_all[idx_s:idx_e], root=0) - # else: - # case_data_i = comm_i.scatter(case_data_all[idx_s:idx_e], root=0) - - # # eval - # out = eval_multi(case_data_i) - - # # gather results - # if split_comm: - # if color[rank] == 1: - # output_i = comm_i.gather(out, root=0) - # else: - # output_i = comm_i.gather(out, root=0) - - # if rank == 0: - # output.extend(output_i) + # return summary_stats, extreme_table, DELs, Damage, ct - # return output - -def eval(case, case_name, FAST_ver, FAST_exe, FAST_runDirectory, FAST_InputFile, FAST_directory, read_yaml, FAST_yamlfile_in, fst_vt, write_yaml, FAST_yamlfile_out, channels, debug_level, overwrite_outfiles, post): +def evaluate(indict): # Batch FAST pyWrapper call, as a function outside the runFAST_pywrapper_batch class for pickle-ablility - fast = runFAST_pywrapper(FAST_ver=FAST_ver) - fast.FAST_exe = FAST_exe - fast.FAST_InputFile = FAST_InputFile - fast.FAST_directory = FAST_directory - fast.FAST_runDirectory = FAST_runDirectory - - fast.read_yaml = read_yaml - fast.FAST_yamlfile_in = FAST_yamlfile_in - fast.fst_vt = fst_vt - fast.write_yaml = write_yaml - fast.FAST_yamlfile_out = FAST_yamlfile_out - - fast.FAST_namingOut = case_name - fast.case = case - fast.channels = channels - fast.debug_level = debug_level - - fast.overwrite_outfiles = overwrite_outfiles - - FAST_Output = fast.execute() - - # Post process - if post: - out = post(FAST_Output) - else: - out = [] - - return out + # Could probably do this with vars(fast), but this gives tighter control + known_keys = ['case', 'case_name', 'FAST_exe', 'FAST_lib', 'FAST_runDirectory', + 'FAST_InputFile', 'FAST_directory', 'read_yaml', 'FAST_yamlfile_in', 'fst_vt', + 'write_yaml', 'FAST_yamlfile_out', 'channels', 'overwrite_outfiles', 'keep_time', + 'goodman','magnitude_channels','fatigue_channels','post','use_exe','allow_fails','fail_value'] + + fast = runFAST_pywrapper() + for k in indict: + if k == 'case_name': + fast.FAST_namingOut = indict['case_name'] + elif k in known_keys: + setattr(fast, k, indict[k]) + else: + print(f'WARNING: Unknown OpenFAST executation parameter, {k}') + + return fast.execute() -def eval_multi(data): +def evaluate_multi(indict): # helper function for running with multiprocessing.Pool.map # converts list of arguement values to arguments - return eval(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]) - -def example_runFAST_pywrapper_batch(): - """ - Example of running a batch of cases, in serial or in parallel - """ - fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST') - - # fastBatch.FAST_exe = 'C:/Users/egaertne/WT_Codes/openfast/build/glue-codes/fast/openfast.exe' # Path to executable - # fastBatch.FAST_InputFile = '5MW_Land_DLL_WTurb.fst' # FAST input file (ext=.fst) - # fastBatch.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/openfast/glue-codes/fast/5MW_Land_DLL_WTurb' # Path to fst directory files - # fastBatch.FAST_runDirectory = 'temp/OpenFAST' - # fastBatch.debug_level = 2 - fastBatch.FAST_exe = '/projects/windse/importance_sampling/WT_Codes/openfast/build/glue-codes/openfast/openfast' # Path to executable - fastBatch.FAST_InputFile = '5MW_Land_DLL_WTurb.fst' # FAST input file (ext=.fst) - fastBatch.FAST_directory = "/projects/windse/importance_sampling/WISDEM/xloads_tc/templates/openfast/5MW_Land_DLL_WTurb-Shutdown" # Path to fst directory files - fastBatch.FAST_runDirectory = 'temp/OpenFAST' - fastBatch.debug_level = 2 - fastBatch.post = FAST_IO_timeseries - - - ## Define case list explicitly - # case_list = [{}, {}] - # case_list[0]['Fst', 'TMax'] = 4. - # case_list[1]['Fst', 'TMax'] = 5. - # case_name_list = ['test01', 'test02'] - - ## Generate case list using General Case Generator - ## Specify several variables that change independently or collectly - case_inputs = {} - case_inputs[("Fst","TMax")] = {'vals':[5.], 'group':0} - case_inputs[("InflowWind","WindType")] = {'vals':[1], 'group':0} - case_inputs[("Fst","OutFileFmt")] = {'vals':[2], 'group':0} - case_inputs[("InflowWind","HWindSpeed")] = {'vals':[8., 9., 10., 11., 12.], 'group':1} - case_inputs[("ElastoDyn","RotSpeed")] = {'vals':[9.156, 10.296, 11.431, 11.89, 12.1], 'group':1} - case_inputs[("ElastoDyn","BlPitch1")] = {'vals':[0., 0., 0., 0., 3.823], 'group':1} - case_inputs[("ElastoDyn","BlPitch2")] = case_inputs[("ElastoDyn","BlPitch1")] - case_inputs[("ElastoDyn","BlPitch3")] = case_inputs[("ElastoDyn","BlPitch1")] - case_inputs[("ElastoDyn","GenDOF")] = {'vals':['True','False'], 'group':2} - - from CaseGen_General import CaseGen_General - case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=fastBatch.FAST_runDirectory, namebase='testing') - - fastBatch.case_list = case_list - fastBatch.case_name_list = case_name_list - - # fastBatch.run_serial() - # fastBatch.run_multi(2) - fastBatch.run_mpi() - - -def example_runFAST_CaseGenIEC(): - - from CaseGen_IEC import CaseGen_IEC - iec = CaseGen_IEC() - - # Turbine Data - iec.init_cond = {} # can leave as {} if data not available - iec.init_cond[("ElastoDyn","RotSpeed")] = {'U':[3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25]} - iec.init_cond[("ElastoDyn","RotSpeed")]['val'] = [6.972, 7.183, 7.506, 7.942, 8.469, 9.156, 10.296, 11.431, 11.89, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1, 12.1] - iec.init_cond[("ElastoDyn","BlPitch1")] = {'U':[3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25]} - iec.init_cond[("ElastoDyn","BlPitch1")]['val'] = [0., 0., 0., 0., 0., 0., 0., 0., 0., 3.823, 6.602, 8.668, 10.450, 12.055, 13.536, 14.920, 16.226, 17.473, 18.699, 19.941, 21.177, 22.347, 23.469] - iec.init_cond[("ElastoDyn","BlPitch2")] = iec.init_cond[("ElastoDyn","BlPitch1")] - iec.init_cond[("ElastoDyn","BlPitch3")] = iec.init_cond[("ElastoDyn","BlPitch1")] - - iec.Turbine_Class = 'I' # I, II, III, IV - iec.Turbulence_Class = 'A' - iec.D = 126. - iec.z_hub = 90. - - # DLC inputs - iec.dlc_inputs = {} - iec.dlc_inputs['DLC'] = [1.1, 1.5] - iec.dlc_inputs['U'] = [[8, 9, 10], [12]] - iec.dlc_inputs['Seeds'] = [[5, 6, 7], []] - iec.dlc_inputs['Yaw'] = [[], []] - - iec.transient_dir_change = 'both' # '+','-','both': sign for transient events in EDC, EWS - iec.transient_shear_orientation = 'both' # 'v','h','both': vertical or horizontal shear for EWS - - # Naming, file management, etc - iec.wind_dir = 'temp/wind' - iec.case_name_base = 'testing' - iec.Turbsim_exe = 'C:/Users/egaertne/WT_Codes/Turbsim_v2.00.07/bin/TurbSim_x64.exe' - iec.debug_level = 2 - iec.parallel_windfile_gen = True - iec.cores = 4 - iec.run_dir = 'temp/OpenFAST' - - # Run case generator / wind file writing - case_inputs = {} - case_inputs[('Fst','OutFileFmt')] = {'vals':[1], 'group':0} - case_list, case_name_list, dlc_list = iec.execute(case_inputs=case_inputs) - - # Run FAST cases - fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST') - fastBatch.FAST_exe = 'C:/Users/egaertne/WT_Codes/openfast/build/glue-codes/fast/openfast.exe' # Path to executable - fastBatch.FAST_InputFile = '5MW_Land_DLL_WTurb.fst' # FAST input file (ext=.fst) - fastBatch.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/openfast/glue-codes/fast/5MW_Land_DLL_WTurb' # Path to fst directory files - fastBatch.FAST_runDirectory = iec.run_dir - - fastBatch.case_list = case_list - fastBatch.case_name_list = case_name_list - fastBatch.debug_level = 2 - - # fastBatch.run_serial() - fastBatch.run_multi(4) - - -def example_runFAST_pywrapper(): - """ - Example of reading, writing, and running FAST 7, 8 and OpenFAST. - """ - - FAST_ver = 'OpenFAST' - fast = runFAST_pywrapper(FAST_ver=FAST_ver, debug_level=2) - - if FAST_ver.lower() == 'fast7': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/FAST_v7.02.00d-bjj/FAST.exe' # Path to executable - fast.FAST_InputFile = 'Test12.fst' # FAST input file (ext=.fst) - fast.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/FAST_v7.02.00d-bjj/CertTest/' # Path to fst directory files - fast.FAST_runDirectory = 'temp/FAST7' - fast.FAST_namingOut = 'test' - - elif FAST_ver.lower() == 'fast8': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/FAST_v8.16.00a-bjj/bin/FAST_Win32.exe' # Path to executable - fast.FAST_InputFile = 'NREL5MW_onshore.fst' # FAST input file (ext=.fst) - fast.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/FAST_v8.16.00a-bjj/ref/5mw_onshore/' # Path to fst directory files - fast.FAST_runDirectory = 'temp/FAST8' - fast.FAST_namingOut = 'test' - - # elif FAST_ver.lower() == 'openfast': - # fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/openfast/build/glue-codes/fast/openfast.exe' # Path to executable - # fast.FAST_InputFile = '5MW_Land_DLL_WTurb.fst' # FAST input file (ext=.fst) - # fast.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/openfast/glue-codes/fast/5MW_Land_DLL_WTurb' # Path to fst directory files - # fast.FAST_runDirectory = 'temp/OpenFAST' - # fast.FAST_namingOut = 'test' - - # fast.read_yaml = False - # fast.FAST_yamlfile_in = 'temp/OpenFAST/test.yaml' - - # fast.write_yaml = False - # fast.FAST_yamlfile_out = 'temp/OpenFAST/test.yaml' - elif FAST_ver.lower() == 'openfast': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/openfast-dev/build/glue-codes/openfast/openfast.exe' # Path to executable - # fast.FAST_InputFile = '5MW_Land_DLL_WTurb.fst' # FAST input file (ext=.fst) - # fast.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/openfast-dev/r-test/glue-codes/openfast/5MW_Land_DLL_WTurb' # Path to fst directory files - fast.FAST_InputFile = '5MW_OC3Spar_DLL_WTurb_WavesIrr.fst' # FAST input file (ext=.fst) - fast.FAST_directory = 'C:/Users/egaertne/WT_Codes/models/openfast-dev/r-test/glue-codes/openfast/5MW_OC3Spar_DLL_WTurb_WavesIrr' # Path to fst directory files - fast.FAST_runDirectory = 'temp/OpenFAST' - fast.FAST_namingOut = 'test_run_spar' - - fast.read_yaml = False - fast.FAST_yamlfile_in = 'temp/OpenFAST/test.yaml' - - fast.write_yaml = False - fast.FAST_yamlfile_out = 'temp/OpenFAST/test.yaml' - - fast.execute() - - -if __name__=="__main__": - - # example_runFAST_pywrapper() - example_runFAST_pywrapper_batch() - # example_runFAST_CaseGenIEC() \ No newline at end of file + return evaluate(indict) diff --git a/ROSCO_toolbox/ofTools/case_gen/run_FAST.py b/ROSCO_toolbox/ofTools/case_gen/run_FAST.py index 26cc8a22..a0c31925 100644 --- a/ROSCO_toolbox/ofTools/case_gen/run_FAST.py +++ b/ROSCO_toolbox/ofTools/case_gen/run_FAST.py @@ -7,7 +7,7 @@ from ROSCO_toolbox.ofTools.case_gen.runFAST_pywrapper import runFAST_pywrapper, runFAST_pywrapper_batch from ROSCO_toolbox.ofTools.case_gen.CaseGen_IEC import CaseGen_IEC from ROSCO_toolbox.ofTools.case_gen.CaseGen_General import CaseGen_General -from ROSCO_toolbox.ofTools.case_gen.CaseLibrary import power_curve, set_channels, find_max_group, sweep_rated_torque, load_tuning_yaml, simp_step +from ROSCO_toolbox.ofTools.case_gen import CaseLibrary as cl from wisdem.commonse.mpi_tools import MPI import sys, os, platform import numpy as np @@ -20,174 +20,263 @@ # Globals this_dir = os.path.dirname(os.path.abspath(__file__)) tune_case_dir = os.path.realpath(os.path.join(this_dir,'../../../Tune_Cases')) +rosco_dir = os.path.realpath(os.path.join(this_dir,'../../..')) + +class run_FAST_ROSCO(): + + def __init__(self): + + # Set default parameters + self.tuning_yaml = os.path.join(tune_case_dir,'IEA15MW.yaml') + self.wind_case_fcn = cl.power_curve + self.wind_case_opts = {} + self.control_sweep_opts = {} + self.control_sweep_fcn = None + self.case_inputs = {} + self.rosco_dll = '' + self.save_dir = os.path.join(rosco_dir,'outputs') + self.n_cores = 1 + self.base_name = '' + self.controller_params = {} + + def run_FAST(self): + # set up run directory + if self.control_sweep_fcn: + sweep_name = self.control_sweep_fcn.__name__ + else: + sweep_name = 'base' + # Base name and run directory + if not self.base_name: + self.base_name = os.path.split(self.tuning_yaml)[-1].split('.')[0] + + run_dir = os.path.join(self.save_dir,self.base_name,self.wind_case_fcn.__name__,sweep_name) -def run_FAST(tuning_yaml,wind_case_fcn,control_sweep_fcn,save_dir,n_cores=1): - # set up run directory - if control_sweep_fcn: - sweep_name = control_sweep_fcn.__name__ - else: - sweep_name = 'base' + + # Start with tuning yaml definition of controller + if not os.path.isabs(self.tuning_yaml): + self.tuning_yaml = os.path.join(tune_case_dir,self.tuning_yaml) + + # Load yaml file + inps = load_rosco_yaml(self.tuning_yaml) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Update user-defined controller_params + controller_params.update(self.controller_params) + + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) + + # Load turbine data from OpenFAST and rotor performance text file + tune_yaml_dir = os.path.split(self.tuning_yaml)[0] + cp_filename = os.path.join( + tune_yaml_dir, + path_params['FAST_directory'], + path_params['rotor_performance_filename'] + ) + turbine.load_from_fast(path_params['FAST_InputFile'], \ + os.path.join(tune_yaml_dir,path_params['FAST_directory']), \ + dev_branch=True,rot_source='txt',\ + txt_filename=cp_filename) + + # tune base controller defined by the yaml + controller.tune_controller(turbine) + + # Apply all discon variables as case inputs + discon_vt = ROSCO_utilities.DISCON_dict(turbine, controller, txt_filename=cp_filename) + control_base_case = {} + for discon_input in discon_vt: + control_base_case[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} + + # Set up wind case + self.wind_case_opts['run_dir'] = run_dir + case_inputs = self.wind_case_fcn(**self.wind_case_opts) + case_inputs.update(control_base_case) + + # Set up rosco_dll + if not self.rosco_dll: + rosco_dir = os.path.realpath(os.path.join(os.path.dirname(__file__),'../../..')) + if platform.system() == 'Windows': + rosco_dll = os.path.join(rosco_dir, 'ROSCO/build/libdiscon.dll') + elif platform.system() == 'Darwin': + rosco_dll = os.path.join(rosco_dir, 'ROSCO/build/libdiscon.dylib') + else: + rosco_dll = os.path.join(rosco_dir, 'ROSCO/build/libdiscon.so') - turbine_name = os.path.split(tuning_yaml)[-1].split('.')[0] - run_dir = os.path.join(save_dir,turbine_name,wind_case_fcn.__name__,sweep_name) + case_inputs[('ServoDyn','DLL_FileName')] = {'vals': [rosco_dll], 'group': 0} - - # Start with tuning yaml definition of controller - if not os.path.isabs(tuning_yaml): - tuning_yaml = os.path.join(tune_case_dir,tuning_yaml) - - - # Load yaml file - inps = load_rosco_yaml(tuning_yaml) - path_params = inps['path_params'] - turbine_params = inps['turbine_params'] - controller_params = inps['controller_params'] - - # Instantiate turbine, controller, and file processing classes - turbine = ROSCO_turbine.Turbine(turbine_params) - controller = ROSCO_controller.Controller(controller_params) - - # Load turbine data from OpenFAST and rotor performance text file - cp_filename = os.path.join(tune_case_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) - turbine.load_from_fast(path_params['FAST_InputFile'], \ - os.path.join(tune_case_dir,path_params['FAST_directory']), \ - dev_branch=True,rot_source='txt',\ - txt_filename=cp_filename) - - # tune base controller defined by the yaml - controller.tune_controller(turbine) - - # Apply all discon variables as case inputs - discon_vt = ROSCO_utilities.DISCON_dict(turbine, controller, txt_filename=cp_filename) - control_base_case = {} - for discon_input in discon_vt: - control_base_case[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} - - # Set up wind case - case_inputs = wind_case_fcn(run_dir) - case_inputs.update(control_base_case) - - # Specify rosco controller dylib - rosco_dll = '/Users/dzalkind/Tools/ROSCO/ROSCO/build/libdiscon.dylib' #'/Users/dzalkind/Tools/ROSCO_toolbox/ROSCO/build/libdiscon.dylib' - - if not rosco_dll: # use WEIS ROSCO - run_dir1 = os.path.dirname( os.path.dirname( os.path.dirname( os.path.realpath(__file__) ) ) ) + os.sep - if platform.system() == 'Windows': - rosco_dll = os.path.join(run_dir1, 'local/lib/libdiscon.dll') - elif platform.system() == 'Darwin': - rosco_dll = os.path.join(run_dir1, 'local/lib/libdiscon.dylib') + # Sweep control parameter + if self.control_sweep_fcn: + self.control_sweep_opts['tuning_yaml'] = self.tuning_yaml + case_inputs_control = self.control_sweep_fcn(cl.find_max_group(case_inputs)+1, **self.control_sweep_opts) + sweep_name = self.control_sweep_fcn.__name__ + case_inputs.update(case_inputs_control) else: - rosco_dll = os.path.join(run_dir1, 'local/lib/libdiscon.so') + sweep_name = 'base' - case_inputs[('ServoDyn','DLL_FileName')] = {'vals': [rosco_dll], 'group': 0} - - # Sweep control parameter - if control_sweep_fcn: - case_inputs_control = control_sweep_fcn(tuning_yaml,find_max_group(case_inputs)+1) - sweep_name = control_sweep_fcn.__name__ - case_inputs.update(case_inputs_control) - else: - sweep_name = 'base' - - - - # Generate cases - case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=run_dir, namebase=turbine_name) - channels = set_channels() - - # Management of parallelization, leave in for now - if MPI: - from wisdem.commonse.mpi_tools import map_comm_heirarchical, subprocessor_loop, subprocessor_stop - n_OF_runs = len(case_list) - - available_cores = MPI.COMM_WORLD.Get_size() - n_parallel_OFruns = np.min([available_cores - 1, n_OF_runs]) - comm_map_down, comm_map_up, color_map = map_comm_heirarchical(1, n_parallel_OFruns) - sys.stdout.flush() + # Add external user-defined case inputs + case_inputs.update(self.case_inputs) + + # Generate cases + case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=run_dir, namebase=self.base_name) + channels = cl.set_channels() + # Management of parallelization, leave in for now + if MPI: + from wisdem.commonse.mpi_tools import map_comm_heirarchical, subprocessor_loop, subprocessor_stop + n_OF_runs = len(case_list) - # Parallel file generation with MPI - if MPI: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - else: - rank = 0 - if rank == 0: + available_cores = MPI.COMM_WORLD.Get_size() + n_parallel_OFruns = np.min([available_cores - 1, n_OF_runs]) + comm_map_down, comm_map_up, color_map = map_comm_heirarchical(1, n_parallel_OFruns) + sys.stdout.flush() - # Run FAST cases - fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST',dev_branch = True) - - # Select Turbine Model - model_dir = os.path.join(os.path.dirname( os.path.dirname( os.path.realpath(__file__) ) ), '01_aeroelasticse/OpenFAST_models') - - # FAST_directory (relative to Tune_Dir/) - fastBatch.FAST_directory = os.path.realpath(os.path.join(tune_case_dir,path_params['FAST_directory'])) - fastBatch.FAST_InputFile = path_params['FAST_InputFile'] - fastBatch.channels = channels - fastBatch.FAST_runDirectory = run_dir - fastBatch.case_list = case_list - fastBatch.case_name_list = case_name_list - fastBatch.debug_level = 2 - fastBatch.FAST_exe = 'openfast' + # Parallel file generation with MPI if MPI: - fastBatch.run_mpi(comm_map_down) + comm = MPI.COMM_WORLD + rank = comm.Get_rank() else: - if n_cores == 1: - fastBatch.run_serial() + rank = 0 + if rank == 0: + + # Run FAST cases + fastBatch = runFAST_pywrapper_batch() + + # FAST_directory (relative to Tune_Dir/) + fastBatch.FAST_directory = os.path.realpath(os.path.join(tune_yaml_dir,path_params['FAST_directory'])) + fastBatch.FAST_InputFile = path_params['FAST_InputFile'] + fastBatch.channels = channels + fastBatch.FAST_runDirectory = run_dir + fastBatch.case_list = case_list + fastBatch.case_name_list = case_name_list + fastBatch.debug_level = 2 + fastBatch.FAST_exe = 'openfast' + + if MPI: + fastBatch.run_mpi(comm_map_down) else: - fastBatch.run_multi(cores=n_cores) + if self.n_cores == 1: + fastBatch.run_serial() + else: + fastBatch.run_multi(cores=self.n_cores) - if MPI: - sys.stdout.flush() - if rank in comm_map_up.keys(): - subprocessor_loop(comm_map_up) + if MPI: + sys.stdout.flush() + if rank in comm_map_up.keys(): + subprocessor_loop(comm_map_up) + sys.stdout.flush() + + # Close signal to subprocessors + if rank == 0 and MPI: + subprocessor_stop(comm_map_down) sys.stdout.flush() - - # Close signal to subprocessors - if rank == 0 and MPI: - subprocessor_stop(comm_map_down) - sys.stdout.flush() if __name__ == "__main__": # Simulation config - sim_config = 6 - n_cores = 8 + sim_config = 11 + + r = run_FAST_ROSCO() + + wind_case_opts = {} if sim_config == 1: # FOCAL single wind speed testing - tuning_yaml = '/Users/dzalkind/Tools/ROSCO/Tune_Cases/IEA15MW_FOCAL.yaml' - wind_case = simp_step - sweep_mode = None - save_dir = '/Users/dzalkind/Projects/FOCAL/torque_274' + r.tuning_yaml = os.path.join(tune_case_dir,'IEA15MW.yaml') + r.wind_case_fcn = cl.simp_step + r.sweep_mode = None + r.save_dir = '/Users/dzalkind/Tools/ROSCO/outputs' elif sim_config == 6: # FOCAL rated wind speed tuning - tuning_yaml = '/Users/dzalkind/Tools/ROSCO/Tune_Cases/IEA15MW_FOCAL.yaml' - wind_case = power_curve - sweep_mode = sweep_rated_torque - save_dir = '/Users/dzalkind/Projects/FOCAL/drop_torque' - - else: - raise Exception('This simulation configuration is not supported.') + r.tuning_yaml = os.path.join(tune_case_dir,'IEA15MW_FOCAL.yaml') + r.wind_case_fcn = power_curve + r.sweep_mode = cl.sweep_rated_torque + r.save_dir = '/Users/dzalkind/Projects/FOCAL/drop_torque' + elif sim_config == 7: + # FOCAL rated wind speed tuning + r.tuning_yaml = os.path.join(tune_case_dir,'IEA15MW.yaml') + r.wind_case_fcn = cl.steps + r.wind_case_opts = { + 'tt': [100,200], + 'U': [16,18], + 'U_0': 13 + } + r.sweep_mode = None + r.save_dir = '/Users/dzalkind/Tools/ROSCO/outputs' + r.control_sweep_fcn = cl.sweep_pitch_act + r.control_sweep_opts = { + 'act_bw': np.array([0.25,0.5,1,10]) * np.pi * 2 + } + + r.n_cores = 4 + + elif sim_config == 8: + + # RAAW IPC set up + r.tuning_yaml = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/ROSCO/RAAW_rosco_BD.yaml' + r.wind_case_fcn = cl.power_curve + r.wind_case_opts = { + 'U': [16], + } + r.save_dir = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/outputs/IPC_play' + r.control_sweep_fcn = cl.sweep_ipc_gains + + elif sim_config == 9: + + # RAAW FAD set up + r.tuning_yaml = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/ROSCO/RAAW_rosco_BD.yaml' + r.wind_case_fcn = cl.simp_step + r.wind_case_opts = { + 'U_start': [13], + 'U_end': [15], + 'wind_dir': '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/outputs/FAD_play' + } + r.save_dir = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/outputs/FAD_play' + r.control_sweep_fcn = cl.sweep_fad_gains + r.n_cores = 8 + + elif sim_config == 10: + + # RAAW FAD set up + r.tuning_yaml = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/ROSCO/RAAW_rosco_BD.yaml' + r.wind_case_fcn = cl.turb_bts + r.wind_case_opts = { + 'TMax': 720, + 'wind_filenames': ['/Users/dzalkind/Tools/WEIS-2/outputs/02_RAAW_IPC/wind/RAAW_NTM_U12.000000_Seed1693606511.0.bts'] + } + r.save_dir = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/outputs/PS_BD' + r.control_sweep_fcn = cl.sweep_ps_percent + r.n_cores = 8 + + elif sim_config == 11: + + # RAAW FAD set up + r.tuning_yaml = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/ROSCO/RAAW_rosco_BD.yaml' + r.wind_case_fcn = cl.user_hh + r.wind_case_opts = { + 'TMax': 1000., + 'wind_filenames': ['/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/Performance/GE_P3_WindStep.asc'] + } + r.save_dir = '/Users/dzalkind/Projects/RAAW/RAAW_OpenFAST/outputs/PS_steps' + r.control_sweep_fcn = cl.sweep_ps_percent + r.n_cores = 4 + else: + raise Exception('This simulation configuration is not supported.') - run_FAST(tuning_yaml,wind_case,sweep_mode,save_dir,n_cores=n_cores) - # # Options: simp, pwr_curve - # test_type = 'pwr_curve' + r.run_FAST() - # save_dir_list = [os.path.join(res_dir,tm,os.path.basename(dl).split('.')[0],test_type) \ - # for tm, dl in zip(turbine_mods,discon_list)] - # for tm, co, sd in zip(turbine_mods,discon_list,save_dir_list): - # run_Simp(tm,co,sd,n_cores=8) diff --git a/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py b/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py index 0a462a49..f4fb2f59 100644 --- a/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py +++ b/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py @@ -1630,7 +1630,15 @@ def read_HydroDyn(self): # PLATFORM ADDITIONAL STIFFNESS AND DAMPING f.readline() - self.fst_vt['HydroDyn']['AddF0'] = np.array([[float(idx) for idx in f.readline().strip().split()[0]] for i in range(6)]) + # Get number of F0 terms [If NBodyMod=1, one size 6*NBody x 1 vector; if NBodyMod>1, NBody size 6 x 1 vectors] + NBody = self.fst_vt['HydroDyn']['NBody'] + if self.fst_vt['HydroDyn']['NBodyMod'] == 1: + self.fst_vt['HydroDyn']['AddF0'] = [float(f.readline().strip().split()[0]) for i in range(6*NBody)] + elif self.fst_vt['HydroDyn']['NBodyMod'] > 1: + self.fst_vt['HydroDyn']['AddF0'] = [[float(idx) for idx in f.readline().strip().split()[:NBody]] for i in range(6)] + else: + raise Exception("Invalid value for fst_vt['HydroDyn']['NBodyMod']") + self.fst_vt['HydroDyn']['AddCLin'] = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)]) self.fst_vt['HydroDyn']['AddBLin'] = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)]) self.fst_vt['HydroDyn']['AddBQuad'] = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)]) @@ -1920,7 +1928,7 @@ def read_SubDyn(self): self.fst_vt['SubDyn']['Nmodes'] = int_read(f.readline().split()[0]) self.fst_vt['SubDyn']['JDampings'] = int_read(f.readline().split()[0]) self.fst_vt['SubDyn']['GuyanDampMod'] = int_read(f.readline().split()[0]) - self.fst_vt['SubDyn']['RayleighDamp'] = [float(m.replace(',','')) for m in f.readline().split()[:2]] + self.fst_vt['SubDyn']['RayleighDamp'] = [float(m) for m in f.readline().strip().replace(',','').split()[:2]] self.fst_vt['SubDyn']['GuyanDampSize'] = int_read(f.readline().split()[0]) self.fst_vt['SubDyn']['GuyanDamp'] = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(self.fst_vt['SubDyn']['GuyanDampSize'])]) f.readline() diff --git a/ROSCO_toolbox/ofTools/fast_io/FAST_wrapper.py b/ROSCO_toolbox/ofTools/fast_io/FAST_wrapper.py index 3f963829..66e73ff6 100644 --- a/ROSCO_toolbox/ofTools/fast_io/FAST_wrapper.py +++ b/ROSCO_toolbox/ofTools/fast_io/FAST_wrapper.py @@ -1,17 +1,16 @@ import os +# from re import sub import subprocess import platform import time -class FastWrapper(object): +class FAST_wrapper(object): def __init__(self, **kwargs): - self.FAST_ver = 'OPENFAST' #(FAST7, FAST8, OPENFAST) self.FAST_exe = None # Path to executable self.FAST_InputFile = None # FAST input file (ext=.fst) self.FAST_directory = None # Path to fst directory files - self.debug_level = 0 #(0:quiet, 1:output task description, 2:full FAST stdout) # Optional population class attributes from key word arguments for k, w in kwargs.items(): @@ -20,7 +19,7 @@ def __init__(self, **kwargs): except: pass - super(FastWrapper, self).__init__() + super(FAST_wrapper, self).__init__() def execute(self): @@ -39,46 +38,30 @@ def execute(self): olddir = os.getcwd() os.chdir(self.FAST_directory) - if self.debug_level > 0: - print ("EXECUTING", self.FAST_ver) - print ("Executable: \t", self.FAST_exe) - print ("Run directory: \t", self.FAST_directory) - print ("Input file: \t", self.FAST_InputFile) - print ("Exec string: \t", exec_str) - + run_idx = 0 start = time.time() - if self.debug_level > 1: - subprocess.call(exec_str) - else: - FNULL = open(os.devnull, 'w') - subprocess.call(exec_str, stdout=FNULL, stderr=subprocess.STDOUT) + while run_idx < 2: + try: + subprocess.run(exec_str, check=True) + failed = False + run_idx = 2 + except subprocess.CalledProcessError as e: + if e.returncode > 1 and run_idx < 1: # This probably failed because of a temporary library access issue, retry + print('Error loading OpenFAST libraries, retrying.') + failed = False + run_idx += 1 + else: # Bad OpenFAST inputs, or we've already retried + print('OpenFAST Failed: {}'.format(e)) + failed = True + run_idx = 2 + except: + print('OpenFAST Failed: {}'.format(e)) + failed = True + run_idx = 2 + runtime = time.time() - start print('Runtime: \t{} = {:<6.2f}s'.format(self.FAST_InputFile, runtime)) os.chdir(olddir) -if __name__=="__main__": - - - fast = FastWrapper(debug_level=2) - - fast.FAST_ver = 'OPENFAST' - - if fast.FAST_ver == 'FAST7': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/FAST_v7.02.00d-bjj/FAST.exe' # Path to executable - fast.FAST_InputFile = 'test.fst' # FAST input file (ext=.fst) - fast.FAST_directory = 'C:/Users/egaertne/WISDEM/AeroelasticSE/src/AeroelasticSE/FAST_mdao/temp/FAST7' # Path to fst directory files - - elif fast.FAST_ver == 'FAST8': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/FAST_v8.16.00a-bjj/bin/FAST_Win32.exe' # Path to executable - fast.FAST_InputFile = 'test.fst' # FAST input file (ext=.fst) - fast.FAST_directory = 'C:/Users/egaertne/WISDEM/AeroelasticSE/src/AeroelasticSE/FAST_mdao/temp/FAST8' # Path to fst directory files - - elif fast.FAST_ver == 'OPENFAST': - fast.FAST_exe = 'C:/Users/egaertne/WT_Codes/openfast-dev/build/glue-codes/openfast/openfast.exe' # Path to executable - # fast.FAST_InputFile = 'test.fst' # FAST input file (ext=.fst) - # fast.FAST_directory = 'C:/Users/egaertne/WISDEM/AeroelasticSE/src/AeroelasticSE/FAST_mdao/temp/OpenFAST' # Path to fst directory files - fast.FAST_InputFile = 'RotorSE_FAST_5MW_0.fst' # FAST input file (ext=.fst) - fast.FAST_directory = "C:/Users/egaertne/WISDEM/RotorSE_yaml/RotorSE/src/rotorse/temp/RotorSE_FAST_5MW" # Path to fst directory files - - fast.execute() + return failed diff --git a/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py b/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py index fe93afbd..c3f45edf 100644 --- a/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py +++ b/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py @@ -875,7 +875,7 @@ def write_AeroDyn15(self): self.write_AeroDyn15Polar() # Generate AeroDyn v15 airfoil coordinates - if self.fst_vt['AeroDyn15']['af_data'][1][0]['NumCoords'] != 0: + if self.fst_vt['AeroDyn15']['af_data'][1][0]['NumCoords'] != '0': self.write_AeroDyn15Coord() if self.fst_vt['AeroDyn15']['WakeMod'] == 3: @@ -1465,7 +1465,10 @@ def write_HydroDyn(self): f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['SumQTF'], 'SumQTF', "- Full summation -frequency 2nd-order forces computed with full QTF {0: None; [10, 11, or 12]: WAMIT file to use}\n")) f.write('---------------------- PLATFORM ADDITIONAL STIFFNESS AND DAMPING --------------\n') for j in range(6): - ln = '{:14} '.format(self.fst_vt['HydroDyn']['AddF0'][j][0]) + if type(self.fst_vt['HydroDyn']['AddF0'][j]) == float: + ln = '{:14} '.format(self.fst_vt['HydroDyn']['AddF0'][j]) + elif type(self.fst_vt['HydroDyn']['AddF0'][j]) == list: + ln = '{:14} '.format(' '.join([f'{val}' for val in self.fst_vt['HydroDyn']['AddF0'][j]])) if j == 0: ln = ln + 'AddF0 - Additional preload (N, N-m) [If NBodyMod=1, one size 6*NBody x 1 vector; if NBodyMod>1, NBody size 6 x 1 vectors]\n' else: diff --git a/ROSCO_toolbox/ofTools/fast_io/update_discons.py b/ROSCO_toolbox/ofTools/fast_io/update_discons.py new file mode 100644 index 00000000..901a953a --- /dev/null +++ b/ROSCO_toolbox/ofTools/fast_io/update_discons.py @@ -0,0 +1,46 @@ +import os +# ROSCO toolbox modules +from ROSCO_toolbox import controller as ROSCO_controller +from ROSCO_toolbox import turbine as ROSCO_turbine +from ROSCO_toolbox.utilities import write_DISCON +from ROSCO_toolbox.inputs.validation import load_rosco_yaml + +def update_discons(tune_to_test_map): + # Update a set of discon files + # Input is a dict: each key is the tuning yaml and each value is the discon or list of discons + for tuning_yaml in tune_to_test_map: + + # Load yaml file + inps = load_rosco_yaml(tuning_yaml) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) + + # Load turbine data from OpenFAST and rotor performance text file + yaml_dir = os.path.dirname(tuning_yaml) # files relative to tuning yaml + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(yaml_dir,path_params['FAST_directory']), + dev_branch=True, + rot_source='txt', + txt_filename=os.path.join(yaml_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) + ) + + # Tune controller + controller.tune_controller(turbine) + + # Write parameter input file + if not isinstance(tune_to_test_map[tuning_yaml],list): + tune_to_test_map[tuning_yaml] = [tune_to_test_map[tuning_yaml]] + + discon_in_files = [f for f in tune_to_test_map[tuning_yaml]] + for discon in discon_in_files: + write_DISCON( + turbine,controller, + param_file=discon, + txt_filename=path_params['rotor_performance_filename'] + ) diff --git a/ROSCO_toolbox/sim.py b/ROSCO_toolbox/sim.py index 78ce4496..54e3df62 100644 --- a/ROSCO_toolbox/sim.py +++ b/ROSCO_toolbox/sim.py @@ -19,6 +19,7 @@ rad2deg = np.rad2deg(1) rpm2RadSec = 2.0*(np.pi)/60.0 + class Sim(): """ Simple controller simulation interface for a wind turbine. @@ -48,18 +49,23 @@ def __init__(self, turbine, controller_int): self.turbine = turbine self.controller_int = controller_int - - def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0, make_plots=True): + def sim_ws_series(self, t_array, ws_array, rotor_rpm_init=10, init_pitch=0.0, + wd_array=None, yaw_init=0.0, + make_plots=True): ''' Simulate simplified turbine model using a complied controller (.dll or similar). - currently a 1DOF rotor model Parameters: ----------- - t_array: float + t_array: list-like Array of time steps, (s) - ws_array: float - Array of wind speeds, (s) + ws_array: list-like + Array of wind speeds, (m/s) + wd_array: list-like + Array of wind directions, (rad) + wd_array: float + Initial "north", (or constant) yaw angle, (rad) rotor_rpm_init: float, optional initial rotor speed, (rpm) init_pitch: float, optional @@ -68,44 +74,240 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0, make_p True: generate plots, False: don't. ''' - print('Running simulation for %s wind turbine.' % self.turbine.TurbineName) + # Store turbine data for convenience + dt = t_array[1] - t_array[0] + R = self.turbine.rotor_radius + GBRatio = self.turbine.Ng + + # Declare output arrays + bld_pitch = np.ones_like(t_array) * init_pitch * deg2rad + rot_speed = np.ones_like(t_array) * rotor_rpm_init * \ + rpm2RadSec # represent rot speed in rad / s + gen_speed = np.ones_like(t_array) * rotor_rpm_init * GBRatio * \ + rpm2RadSec # represent gen speed in rad/s + aero_torque = np.ones_like(t_array) * 1000.0 + gen_torque = np.ones_like(t_array) + gen_power = np.ones_like(t_array) * 0.0 + nac_yaw = np.ones_like(t_array) * yaw_init + nac_yawerr = np.ones_like(t_array) * 0.0 + nac_yawrate = np.ones_like(t_array) * 0.0 + + # check for wind direction array + if isinstance(wd_array, (list, np.ndarray)): + if len(ws_array) != len(wd_array): + raise ValueError('ws_array and wd_array must be the same length') + else: + wd_array = np.ones_like(ws_array) * 0.0 + + # Loop through time + for i, t in enumerate(t_array): + if i == 0: + continue # Skip the first run + ws = ws_array[i] + wd = wd_array[i] + + # Load current Cq data + tsr = rot_speed[i-1] * self.turbine.rotor_radius / ws + cq = self.turbine.Cq.interp_surface(bld_pitch[i-1], tsr) + cp = self.turbine.Cp.interp_surface(bld_pitch[i-1], tsr) + # Update the turbine state + # -- 1DOF model: rotor speed and generator speed (scaled by Ng) + aero_torque[i] = 0.5 * self.turbine.rho * (np.pi * R**3) * (cp/tsr) * ws**2 + rot_speed[i] = rot_speed[i-1] + (dt/self.turbine.J)*(aero_torque[i] + * self.turbine.GenEff/100 - self.turbine.Ng * gen_torque[i-1]) + gen_speed[i] = rot_speed[i] * self.turbine.Ng + # -- Simple nacelle model + nac_yawerr[i] = wd - nac_yaw[i-1] + + # populate turbine state dictionary + turbine_state = {} + if i < len(t_array): + turbine_state['iStatus'] = 1 + else: + turbine_state['iStatus'] = -1 + turbine_state['t'] = t + turbine_state['dt'] = dt + turbine_state['ws'] = ws + turbine_state['bld_pitch'] = bld_pitch[i-1] + turbine_state['gen_torque'] = gen_torque[i-1] + turbine_state['gen_speed'] = gen_speed[i] + turbine_state['gen_eff'] = self.turbine.GenEff/100 + turbine_state['rot_speed'] = rot_speed[i] + turbine_state['Yaw_fromNorth'] = nac_yaw[i] + turbine_state['Y_MeasErr'] = nac_yawerr[i-1] + + # Define outputs + gen_torque[i], bld_pitch[i], nac_yawrate[i] = self.controller_int.call_controller(turbine_state) + + # Calculate the power + gen_power[i] = gen_speed[i] * gen_torque[i] + + # Calculate the nacelle position + nac_yaw[i] = nac_yaw[i-1] + nac_yawrate[i] * dt - # Store turbine data for conveniente + self.controller_int.kill_discon() + + # Save these values + self.bld_pitch = bld_pitch + self.rot_speed = rot_speed + self.gen_speed = gen_speed + self.aero_torque = aero_torque + self.gen_torque = gen_torque + self.gen_power = gen_power + self.t_array = t_array + self.ws_array = ws_array + self.wd_array = wd_array + self.nac_yaw = nac_yaw + + if make_plots: + # if sum(nac_yaw) > 0: + if True: + fig, axarr = plt.subplots(5, 1, sharex=True, figsize=(6, 10)) + + ax = axarr[0] + ax.plot(self.t_array, self.ws_array) + ax.set_ylabel('Wind Speed (m/s)') + ax.grid() + ax = axarr[1] + ax.plot(self.t_array, self.wd_array * 180/np.pi, label='WindDirection') + ax.plot(self.t_array, self.nac_yaw * 180/np.pi, label='NacelleAngle') + ax.legend(loc='best') + ax.set_ylabel('Angle(deg)') + ax.set_xlabel('Time (s)') + ax.grid() + ax = axarr[2] + ax.plot(self.t_array, self.rot_speed) + ax.set_ylabel('Rot Speed (rad/s)') + ax.grid() + ax = axarr[3] + ax.plot(self.t_array, self.gen_power/1000) + ax.set_ylabel('Gen Power (W)') + ax.grid() + ax = axarr[4] + ax.plot(self.t_array, self.bld_pitch*rad2deg) + ax.set_ylabel('Bld Pitch (deg)') + ax.set_xlabel('Time (s)') + ax.grid() + + else: + fig, axarr = plt.subplots(4, 1, sharex=True, figsize=(6, 10)) + + ax = axarr[0] + ax.plot(self.t_array, self.ws_array) + ax.set_ylabel('Wind Speed (m/s)') + ax.grid() + ax = axarr[1] + ax.plot(self.t_array, self.rot_speed) + ax.set_ylabel('Rot Speed (rad/s)') + ax.grid() + ax = axarr[2] + ax.plot(self.t_array, self.gen_torque) + ax.set_ylabel('Gen Torque (N)') + ax.grid() + ax = axarr[3] + ax.plot(self.t_array, self.bld_pitch*rad2deg) + ax.set_ylabel('Bld Pitch (deg)') + ax.set_xlabel('Time (s)') + ax.grid() + + def sim_ws_wd_series(self, t_array, ws_array, wd_array, + rotor_rpm_init=10, + init_pitch=0.0, + init_yaw=None, + make_plots=True): + ''' + Simulate simplified turbine model using a complied controller (.dll or similar). + - currently a 1DOF rotor model + + Parameters: + ----------- + t_array: float + Array of time steps, (s) + ws_array: float + Array of wind speeds, (s) + wd_array: float + Array of wind directions, (deg) + rotor_rpm_init: float, optional + Initial rotor speed, (rpm) + init_pitch: float, optional + Initial blade pitch angle, (deg) + init_yaw: float, optional + Initial yaw angle, if None then start with no misalignment, + i.e., the yaw angle is set to the initial wind direction (deg) + make_plots: bool, optional + True: generate plots, False: don't. + ''' + + # Store turbine data for convenience dt = t_array[1] - t_array[0] R = self.turbine.rotor_radius GBRatio = self.turbine.Ng # Declare output arrays - bld_pitch = np.ones_like(t_array) * init_pitch - rot_speed = np.ones_like(t_array) * rotor_rpm_init * rpm2RadSec # represent rot speed in rad / s - gen_speed = np.ones_like(t_array) * rotor_rpm_init * GBRatio * rpm2RadSec # represent gen speed in rad/s + bld_pitch = np.ones_like(t_array) * init_pitch + rot_speed = np.ones_like(t_array) * rotor_rpm_init * \ + rpm2RadSec # represent rot speed in rad / s + gen_speed = np.ones_like(t_array) * rotor_rpm_init * GBRatio * \ + rpm2RadSec # represent gen speed in rad/s aero_torque = np.ones_like(t_array) * 1000.0 - gen_torque = np.ones_like(t_array) # * trq_cont(turbine_dict, gen_speed[0]) + gen_torque = np.ones_like(t_array) # * trq_cont(turbine_dict, gen_speed[0]) gen_power = np.ones_like(t_array) * 0.0 + nac_yawerr = np.ones_like(t_array) * 0.0 + if init_yaw is None: + init_yaw = wd_array[0] + else: + nac_yawerr[0] = init_yaw - wd_array[0] + nac_yaw = np.ones_like(t_array) * init_yaw + nac_yawrate = np.ones_like(t_array) * 0.0 - # Loop through time for i, t in enumerate(t_array): if i == 0: - continue # Skip the first run + continue # Skip the first run ws = ws_array[i] + wd = wd_array[i] + nac_yawerr[i] = (wd - nac_yaw[i-1])*deg2rad # Load current Cq data tsr = rot_speed[i-1] * self.turbine.rotor_radius / ws - cq = self.turbine.Cq.interp_surface([bld_pitch[i-1]],tsr) - + cq = self.turbine.Cq.interp_surface([bld_pitch[i-1]], tsr) + # Update the turbine state # -- 1DOF model: rotor speed and generator speed (scaled by Ng) aero_torque[i] = 0.5 * self.turbine.rho * (np.pi * R**2) * cq * R * ws**2 - rot_speed[i] = rot_speed[i-1] + (dt/self.turbine.J)*(aero_torque[i] * self.turbine.GenEff/100 - self.turbine.Ng * gen_torque[i-1]) + rot_speed[i] = rot_speed[i-1] + (dt/self.turbine.J)*(aero_torque[i] + * self.turbine.GenEff/100 - self.turbine.Ng * gen_torque[i-1]) gen_speed[i] = rot_speed[i] * self.turbine.Ng + # populate turbine state dictionary + turbine_state = {} + # populate turbine state dictionary + turbine_state = {} + if i < len(t_array)-1: + turbine_state['iStatus'] = 1 + else: + turbine_state['iStatus'] = -1 + turbine_state['t'] = t + turbine_state['dt'] = dt + turbine_state['ws'] = ws + turbine_state['bld_pitch'] = bld_pitch[i-1] + turbine_state['gen_torque'] = gen_torque[i-1] + turbine_state['gen_speed'] = gen_speed[i] + turbine_state['gen_eff'] = self.turbine.GenEff/100 + turbine_state['rot_speed'] = rot_speed[i] + turbine_state['Yaw_fromNorth'] = nac_yaw[i] + turbine_state['Y_MeasErr'] = nac_yawerr[i-1] + # Call the controller - gen_torque[i], bld_pitch[i] = self.controller_int.call_controller(t,dt,bld_pitch[i-1],gen_torque[i-1],gen_speed[i],self.turbine.GenEff/100,rot_speed[i],ws) + + gen_torque[i], bld_pitch[i], nac_yawrate[i] = self.controller_int.call_controller(turbine_state) # Calculate the power gen_power[i] = gen_speed[i] * gen_torque[i] + # Update the nacelle position + nac_yaw[i] = nac_yaw[i-1] + nac_yawrate[i]*rad2deg*dt + # Save these values self.bld_pitch = bld_pitch self.rot_speed = rot_speed @@ -115,29 +317,39 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0, make_p self.gen_power = gen_power self.t_array = t_array self.ws_array = ws_array - - # Close shared library when finished - self.controller_int.kill_discon() + self.wd_array = wd_array + self.nac_yaw = nac_yaw + self.nac_yawrate = nac_yawrate if make_plots: - fig, axarr = plt.subplots(4,1,sharex=True,figsize=(6,10)) + fig, axarr = plt.subplots(nrows=6, sharex=True, figsize=(8, 14)) ax = axarr[0] - ax.plot(self.t_array,self.ws_array) + ax.plot(self.t_array, self.ws_array) ax.set_ylabel('Wind Speed (m/s)') - ax.grid() + ax = axarr[1] - ax.plot(self.t_array,self.rot_speed) - ax.set_ylabel('Rot Speed (rad/s)') - ax.grid() + ax.plot(self.t_array, self.wd_array, label='wind direction') + ax.plot(self.t_array, self.nac_yaw, label='yaw position') + ax.set_ylabel('Wind Direction (deg)') + ax.legend(loc='best') + ax = axarr[2] - ax.plot(self.t_array,self.gen_torque) - ax.set_ylabel('Gen Torque (N)') - ax.grid() + ax.plot(self.t_array, self.nac_yaw*rad2deg) + ax.set_ylabel('Nacelle yaw error (deg)') + ax = axarr[3] - ax.plot(self.t_array,self.bld_pitch*rad2deg) + ax.plot(self.t_array, self.rot_speed) + ax.set_ylabel('Rot Speed (rad/s)') + + ax = axarr[4] + ax.plot(self.t_array, self.gen_torque) + ax.set_ylabel('Gen Torque (N)') + + ax = axarr[5] + ax.plot(self.t_array, self.bld_pitch*rad2deg) ax.set_ylabel('Bld Pitch (deg)') - ax.set_xlabel('Time (s)') - ax.grid() - + ax.set_xlabel('Time (s)') + for ax in axarr: + ax.grid() diff --git a/ROSCO_toolbox/turbine.py b/ROSCO_toolbox/turbine.py index 09082e73..e97d7203 100644 --- a/ROSCO_toolbox/turbine.py +++ b/ROSCO_toolbox/turbine.py @@ -151,11 +151,13 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ txt_filename: str, optional filename for *.txt, only used if rot_source='txt' """ + # Use weis if it exists if use_weis: from weis.aeroelasticse.FAST_reader import InputReader_OpenFAST else: from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST + # Load OpenFAST model using the FAST_reader print('Loading FAST model: %s ' % FAST_InputFile) self.TurbineName = FAST_InputFile.strip('.fst') fast = self.fast = InputReader_OpenFAST() @@ -163,6 +165,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ fast.FAST_directory = FAST_directory fast.execute() + # Use Performance tables if defined, otherwise use defaults if txt_filename: self.rotor_performance_filename = txt_filename else: @@ -175,7 +178,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ self.hubHt = fast.fst_vt['ElastoDyn']['TowerHt'] + fast.fst_vt['ElastoDyn']['Twr2Shft'] self.NumBl = fast.fst_vt['ElastoDyn']['NumBl'] self.TowerHt = fast.fst_vt['ElastoDyn']['TowerHt'] - self.shearExp = 0.2 #HARD CODED FOR NOW + self.shearExp = 0.2 #NOTE: HARD CODED if 'default' in str(fast.fst_vt['AeroDyn15']['AirDens']): fast.fst_vt['AeroDyn15']['AirDens'] = 1.225 self.rho = fast.fst_vt['AeroDyn15']['AirDens'] @@ -195,9 +198,7 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_ self.yaw = 0.0 self.J = self.rotor_inertia + self.generator_inertia * self.Ng**2 self.rated_torque = self.rated_power/(self.GenEff/100*self.rated_rotor_speed*self.Ng) - self.max_torque = self.rated_torque * 1.1 self.rotor_radius = self.TipRad - # self.omega_dt = np.sqrt(self.DTTorSpr/self.J) # Load blade information self.load_blade_info() @@ -268,18 +269,14 @@ def load_from_ccblade(self): pitch_flat = pitch_mesh.flatten() omega_mesh, _ = np.meshgrid(omega_array, pitch_initial) omega_flat = omega_mesh.flatten() - # tsr_flat = (omega_flat * rpm2RadSec * self.rotor_radius) / ws_flat # Get values from cc-blade print('Running CCBlade aerodynamic analysis, this may take a minute...') - try: # wisde/master as of Nov 9, 2020 - _, _, _, _, CP, CT, CQ, CM = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True) - except(ValueError): # wisdem/dev as of Nov 9, 2020 - outputs, derivs = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True) - CP = outputs['CP'] - CT = outputs['CT'] - CQ = outputs['CQ'] + outputs, derivs = self.cc_rotor.evaluate(ws_flat, omega_flat, pitch_flat, coefficients=True) + CP = outputs['CP'] + CT = outputs['CT'] + CQ = outputs['CQ'] print('CCBlade aerodynamic analysis run successfully.') # Reshape Cp, Ct and Cq @@ -322,8 +319,7 @@ def generate_rotperf_fast(self, openfast_path, FAST_runDirectory=None, run_BeamD from ROSCO_toolbox.ofTools.case_gen import runFAST_pywrapper, CaseGen_General from ROSCO_toolbox.ofTools.util import FileTools # Load pCrunch tools - from pCrunch import pdTools, Processing - + from pCrunch import Processing # setup values for surface v0 = self.v_rated + 2 @@ -392,7 +388,7 @@ def generate_rotperf_fast(self, openfast_path, FAST_runDirectory=None, run_BeamD # FAST details - fastBatch = runFAST_pywrapper.runFAST_pywrapper_batch(FAST_ver='OpenFAST', dev_branch=True) + fastBatch = runFAST_pywrapper.runFAST_pywrapper_batch() fastBatch.FAST_exe = openfast_path # Path to executable fastBatch.FAST_InputFile = self.fast.FAST_InputFile fastBatch.FAST_directory = self.fast.FAST_directory @@ -596,14 +592,15 @@ def __init__(self,performance_table, pitch_initial_rad, TSR_initial): if len(self.max_ind[1]) > 1: print('ROSCO_toolbox Warning: repeated maximum values in a performance table and the last one @ pitch = {} rad. was taken...'.format(self.pitch_opt[-1])) - TSR_ind = np.arange(0,len(TSR_initial)) - TSR_fine_ind = np.linspace(TSR_initial[0],TSR_initial[-1],int(TSR_initial[-1] - TSR_initial[0])*100) - f_TSR = interpolate.interp1d(TSR_initial,TSR_initial,bounds_error='False',kind='quadratic') # interpolate function for Cp(tsr) values - TSR_fine = f_TSR(TSR_fine_ind) - f_performance = interpolate.interp1d(TSR_initial,performance_beta_max,bounds_error='False',kind='quadratic') # interpolate function for Cp(tsr) values - performance_fine = f_performance(TSR_fine_ind) - performance_max_ind = np.where(performance_fine == np.max(performance_fine)) - self.TSR_opt = float(TSR_fine[performance_max_ind[0]]) + # Find TSR that maximizes Cx at fine pitch + # - TSR to satisfy: max( Cx(TSR, \beta_fine) ) = TSR_opt + TSR_fine_ind = np.linspace(TSR_initial[0],TSR_initial[-1],int(TSR_initial[-1] - TSR_initial[0])*100) # Range of TSRs to interpolate accross + f_TSR = interpolate.interp1d(TSR_initial,TSR_initial,bounds_error='False',kind='quadratic') # interpolate function for Cp(tsr) values + TSR_fine = f_TSR(TSR_fine_ind) # TSRs at fine pitch + f_performance = interpolate.interp1d(TSR_initial,performance_beta_max,bounds_error='False',kind='quadratic') # interpolate function for Cx(tsr) values + performance_fine = f_performance(TSR_fine_ind) # Cx values at fine pitch + performance_max_ind = np.where(performance_fine == np.max(performance_fine)) # Find max performance at fine pitch + self.TSR_opt = float(TSR_fine[performance_max_ind[0]]) # TSR to maximize Cx at fine pitch def interp_surface(self,pitch,TSR): ''' @@ -660,10 +657,12 @@ def plot_performance(self): max_beta_id = self.pitch_initial_rad[max_ind[1]] max_tsr_id = self.TSR_initial[max_ind[0]] - P = plt.contourf(self.pitch_initial_rad * rad2deg, self.TSR_initial, self.performance_table, - levels=20) + cbarticks = np.linspace(0.0,self.performance_table.max(),20) + + P = plt.contourf(self.pitch_initial_rad * rad2deg, self.TSR_initial, self.performance_table, cbarticks) + # levels=20,vmin=0) plt.colorbar(format='%1.3f') - plt.title('Power Coefficient', fontsize=14, fontweight='bold') + # plt.title('Power Coefficient', fontsize=14, fontweight='bold') plt.xlabel('Pitch Angle [deg]', fontsize=14, fontweight='bold') plt.ylabel('TSR [-]', fontsize=14, fontweight='bold') plt.scatter(max_beta_id * rad2deg, max_tsr_id, color='red') diff --git a/ROSCO_toolbox/utilities.py b/ROSCO_toolbox/utilities.py index 24dc6b0e..b68dd3a9 100644 --- a/ROSCO_toolbox/utilities.py +++ b/ROSCO_toolbox/utilities.py @@ -63,7 +63,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('! - File written using ROSCO version {} controller tuning logic on {}\n'.format(ROSCO_toolbox.__version__, now.strftime('%m/%d/%y'))) file.write('\n') file.write('!------- DEBUG ------------------------------------------------------------\n') - file.write('{0:<12d} ! LoggingLevel - {{0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file}}\n'.format(int(rosco_vt['LoggingLevel']))) + file.write('{0:<12d} ! LoggingLevel - {{0: write no debug files, 1: write standard output .dbg-file, 2: LoggingLevel 1 + ROSCO LocalVars (.dbg2) 3: LoggingLevel 2 + complete avrSWAP-array (.dbg3)}}\n'.format(int(rosco_vt['LoggingLevel']))) file.write('\n') file.write('!------- CONTROLLER FLAGS -------------------------------------------------\n') file.write('{0:<12d} ! F_LPFType - {{1: first-order low-pass filter, 2: second-order low-pass filter}}, [rad/s] (currently filters generator speed and pitch control signals\n'.format(int(rosco_vt['F_LPFType']))) @@ -77,19 +77,25 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('{0:<12d} ! PS_Mode - Pitch saturation mode {{0: no pitch saturation, 1: implement pitch saturation}}\n'.format(int(rosco_vt['PS_Mode']))) file.write('{0:<12d} ! SD_Mode - Shutdown mode {{0: no shutdown procedure, 1: pitch to max pitch at shutdown}}\n'.format(int(rosco_vt['SD_Mode']))) file.write('{0:<12d} ! Fl_Mode - Floating specific feedback mode {{0: no nacelle velocity feedback, 1: feed back translational velocity, 2: feed back rotational veloicty}}\n'.format(int(rosco_vt['Fl_Mode']))) + file.write('{0:<12d} ! TD_Mode - Tower damper mode {{0: no tower damper, 1: feed back translational nacelle accelleration to pitch angle}}\n'.format(int(rosco_vt['TD_Mode']))) file.write('{0:<12d} ! Flp_Mode - Flap control mode {{0: no flap control, 1: steady state flap angle, 2: Proportional flap control, 2: Cyclic (1P) flap control}}\n'.format(int(rosco_vt['Flp_Mode']))) - file.write('{0:<12d} ! OL_Mode - Open loop control mode {{0: no open loop control, 1: open loop control vs. time, 2: open loop control vs. wind speed}}\n'.format(int(rosco_vt['OL_Mode']))) + file.write('{0:<12d} ! OL_Mode - Open loop control mode {{0: no open loop control, 1: open loop control vs. time}}\n'.format(int(rosco_vt['OL_Mode']))) + file.write('{0:<12d} ! PA_Mode - Pitch actuator mode {{0 - not used, 1 - first order filter, 2 - second order filter}}\n'.format(int(rosco_vt['PA_Mode']))) + file.write('{0:<12d} ! Ext_Mode - External control mode {{0 - not used, 1 - call external dynamic library}}\n'.format(int(rosco_vt['Ext_Mode']))) + file.write('{0:<12d} ! ZMQ_Mode - Fuse ZeroMQ interaface {{0: unused, 1: Yaw Control}}\n'.format(int(rosco_vt['ZMQ_Mode']))) + file.write('\n') file.write('!------- FILTERS ----------------------------------------------------------\n') file.write('{:<13.5f} ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s]\n'.format(rosco_vt['F_LPFCornerFreq'])) file.write('{:<13.5f} ! F_LPFDamping - Damping coefficient {{used only when F_FilterType = 2}} [-]\n'.format(rosco_vt['F_LPFDamping'])) file.write('{:<13.5f} ! F_NotchCornerFreq - Natural frequency of the notch filter, [rad/s]\n'.format(rosco_vt['F_NotchCornerFreq'])) - file.write('{} ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-]\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_NotchBetaNumDen'][i]) for i in range(len(rosco_vt['F_NotchBetaNumDen']))))) + file.write('{}! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-]\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_NotchBetaNumDen'][i]) for i in range(len(rosco_vt['F_NotchBetaNumDen']))))) file.write('{:<13.5f} ! F_SSCornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, [rad/s].\n'.format(rosco_vt['F_SSCornerFreq'])) file.write('{:<13.5f} ! F_WECornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s].\n'.format(rosco_vt['F_WECornerFreq'])) - file.write('{} ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -].\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_FlCornerFreq'][i]) for i in range(len(rosco_vt['F_FlCornerFreq']))))) + file.write('{:<13.5f} ! F_YawErr - Low pass filter corner frequency for yaw controller [rad/s].\n'.format(rosco_vt['F_YawErr'])) + file.write('{}! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -].\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_FlCornerFreq'][i]) for i in range(len(rosco_vt['F_FlCornerFreq']))))) file.write('{:<13.5f} ! F_FlHighPassFreq - Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s].\n'.format(rosco_vt['F_FlHighPassFreq'])) - file.write('{} ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -].\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_FlpCornerFreq'][i]) for i in range(len(rosco_vt['F_FlpCornerFreq']))))) + file.write('{}! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -].\n'.format(''.join('{:<4.6f} '.format(rosco_vt['F_FlpCornerFreq'][i]) for i in range(len(rosco_vt['F_FlpCornerFreq']))))) file.write('\n') file.write('!------- BLADE PITCH CONTROL ----------------------------------------------\n') @@ -108,10 +114,11 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('{:<014.5f} ! PC_Switch - Angle above lowest minimum pitch angle for switch, [rad]\n'.format(rosco_vt['PC_Switch'])) file.write('\n') file.write('!------- INDIVIDUAL PITCH CONTROL -----------------------------------------\n') + file.write('{}! IPC_Vramp - Start and end wind speeds for cut-in ramp function. First entry: IPC inactive, second entry: IPC fully active. [m/s]\n'.format(''.join('{:<4.6f} '.format(rosco_vt['IPC_Vramp'][i]) for i in range(len(rosco_vt['IPC_Vramp']))))) file.write('{:<13.1f} ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad]\n'.format(rosco_vt['IPC_IntSat'])) # Hardcode to 5 degrees - file.write('{} ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-]\n'.format(''.join('{:<4.3e} '.format(rosco_vt['IPC_KP'][i]) for i in range(len(rosco_vt['IPC_KP']))))) - file.write('{} ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-]\n'.format(''.join('{:<4.3e} '.format(rosco_vt['IPC_KI'][i]) for i in range(len(rosco_vt['IPC_KI']))))) - file.write('{} ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. \n'.format(''.join('{:<4.6f} '.format(rosco_vt['IPC_aziOffset'][i]) for i in range(len(rosco_vt['IPC_aziOffset']))))) + file.write('{}! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-]\n'.format(''.join('{:<4.3e} '.format(rosco_vt['IPC_KP'][i]) for i in range(len(rosco_vt['IPC_KP']))))) + file.write('{}! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-]\n'.format(''.join('{:<4.3e} '.format(rosco_vt['IPC_KI'][i]) for i in range(len(rosco_vt['IPC_KI']))))) + file.write('{}! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. \n'.format(''.join('{:<4.6f} '.format(rosco_vt['IPC_aziOffset'][i]) for i in range(len(rosco_vt['IPC_aziOffset']))))) file.write('{:<13.1f} ! IPC_CornerFreqAct - Corner frequency of the first-order actuators model, to induce a phase lag in the IPC signal {{0: Disable}}, [rad/s]\n'.format(rosco_vt['IPC_CornerFreqAct'])) file.write('\n') file.write('!------- VS TORQUE CONTROL ------------------------------------------------\n') @@ -137,7 +144,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('!------- WIND SPEED ESTIMATOR ---------------------------------------------\n') file.write('{:<13.3f} ! WE_BladeRadius - Blade length (distance from hub center to blade tip), [m]\n'.format(rosco_vt['WE_BladeRadius'])) file.write('{:<11d} ! WE_CP_n - Amount of parameters in the Cp array\n'.format(int(rosco_vt['WE_CP_n']))) - file.write('{} ! WE_CP - Parameters that define the parameterized CP(lambda) function\n'.format(rosco_vt['WE_CP'])) + file.write('{:<13.1f} ! WE_CP - Parameters that define the parameterized CP(lambda) function\n'.format(rosco_vt['WE_CP'])) file.write('{:<13.1f} ! WE_Gamma - Adaption gain of the wind speed estimator algorithm [m/rad]\n'.format(rosco_vt['WE_Gamma'])) file.write('{:<13.1f} ! WE_GearboxRatio - Gearbox ratio [>=1], [-]\n'.format(rosco_vt['WE_GearboxRatio'])) file.write('{:<14.5f} ! WE_Jtot - Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2]\n'.format(rosco_vt['WE_Jtot'])) @@ -149,20 +156,16 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('{} ! WE_FOPoles - First order system poles [1/s]\n'.format(''.join('{:<10.8f} '.format(rosco_vt['WE_FOPoles'][i]) for i in range(len(rosco_vt['WE_FOPoles']))))) file.write('\n') file.write('!------- YAW CONTROL ------------------------------------------------------\n') - file.write('{:<13.5f} ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. [rad^2 s]\n'.format(rosco_vt['Y_ErrThresh'])) + file.write('{:<13.5f} ! Y_uSwitch - Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s]\n'.format(rosco_vt['Y_uSwitch'])) + file.write('{}! Y_ErrThresh - Yaw error threshold/deadband. Turbine begins to yaw when it passes this. If Y_uSwitch is zero, only the first value is used. [deg].\n'.format(''.join('{:<4.6f} '.format(rosco_vt['Y_ErrThresh'][i]) for i in range(len(rosco_vt['F_FlCornerFreq']))))) + file.write('{:<13.5f} ! Y_Rate - Yaw rate [rad/s]\n'.format(rosco_vt['Y_Rate'])) + file.write('{:<13.5f} ! Y_MErrSet - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad]\n'.format(rosco_vt['Y_MErrSet'])) file.write('{:<13.5f} ! Y_IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad]\n'.format(rosco_vt['Y_IPC_IntSat'])) - file.write('{:<11d} ! Y_IPC_n - Number of controller gains (yaw-by-IPC)\n'.format(int(rosco_vt['Y_IPC_n']))) file.write('{:<13.5f} ! Y_IPC_KP - Yaw-by-IPC proportional controller gain Kp\n'.format(rosco_vt['Y_IPC_KP'])) file.write('{:<13.5f} ! Y_IPC_KI - Yaw-by-IPC integral controller gain Ki\n'.format(rosco_vt['Y_IPC_KI'])) - file.write('{:<13.5f} ! Y_IPC_omegaLP - Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s].\n'.format(rosco_vt['Y_IPC_omegaLP'])) - file.write('{:<13.5f} ! Y_IPC_zetaLP - Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-].\n'.format(rosco_vt['Y_IPC_zetaLP'])) - file.write('{:<13.5f} ! Y_MErrSet - Yaw alignment error, set point [rad]\n'.format(rosco_vt['Y_MErrSet'])) - file.write('{:<13.5f} ! Y_omegaLPFast - Corner frequency fast low pass filter, 1.0 [rad/s]\n'.format(rosco_vt['Y_omegaLPFast'])) - file.write('{:<13.5f} ! Y_omegaLPSlow - Corner frequency slow low pass filter, 1/60 [rad/s]\n'.format(rosco_vt['Y_omegaLPSlow'])) - file.write('{:<13.5f} ! Y_Rate - Yaw rate [rad/s]\n'.format(rosco_vt['Y_Rate'])) file.write('\n') file.write('!------- TOWER FORE-AFT DAMPING -------------------------------------------\n') - file.write('{:<11d} ! FA_KI - Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on [rad s/m] - !NJA - Make this a flag\n'.format(int(rosco_vt['FA_KI'] ))) + file.write('{:<13.5f} ! FA_KI - Integral gain for the fore-aft tower damper controller [rad s/m]\n'.format(rosco_vt['FA_KI'] )) file.write('{:<13.1f} ! FA_HPFCornerFreq - Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s]\n'.format(rosco_vt['FA_HPFCornerFreq'] )) file.write('{:<13.1f} ! FA_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from FA damper), [rad]\n'.format(rosco_vt['FA_IntSat'] )) file.write('\n') @@ -194,8 +197,21 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C file.write('{0:<12d} ! Ind_BldPitch - The column in OL_Filename that contains the blade pitch input in rad\n'.format(int(rosco_vt['Ind_BldPitch']))) file.write('{0:<12d} ! Ind_GenTq - The column in OL_Filename that contains the generator torque in Nm\n'.format(int(rosco_vt['Ind_GenTq']))) file.write('{0:<12d} ! Ind_YawRate - The column in OL_Filename that contains the generator torque in Nm\n'.format(int(rosco_vt['Ind_YawRate']))) + file.write('\n') + file.write('!------- Pitch Actuator Model -----------------------------------------------------\n') + file.write('{:<014.5f} ! PA_CornerFreq - Pitch actuator bandwidth/cut-off frequency [rad/s]\n'.format(rosco_vt['PA_CornerFreq'])) + file.write('{:<014.5f} ! PA_Damping - Pitch actuator damping ratio [-, unused if PA_Mode = 1]\n'.format(rosco_vt['PA_Damping'])) + file.write('\n') + file.write('!------- External Controller Interface -----------------------------------------------------\n') + file.write('"{}" ! DLL_FileName - Name/location of the dynamic library in the Bladed-DLL format\n'.format(rosco_vt['DLL_FileName'])) + file.write('"{}" ! DLL_InFile - Name of input file sent to the DLL (-)\n'.format(rosco_vt['DLL_InFile'])) + file.write('"{}" ! DLL_ProcName - Name of procedure in DLL to be called (-) \n'.format(rosco_vt['DLL_ProcName'])) + file.write('\n') + file.write('!------- ZeroMQ Interface ---------------------------------------------------------\n') + file.write('"{}" ! ZMQ_CommAddress - Communication address for ZMQ server, (e.g. "tcp://localhost:5555") \n'.format(rosco_vt['ZMQ_CommAddress'])) + file.write('{:<11d} ! ZMQ_UpdatePeriod - Call ZeroMQ every [x] seconds, [s]\n'.format(int(rosco_vt['ZMQ_UpdatePeriod']))) file.close() - + # Write Open loop input if rosco_vt['OL_Mode'] and hasattr(controller, 'OpenLoop'): write_ol_control(controller) @@ -374,8 +390,12 @@ def DISCON_dict(turbine, controller, txt_filename=None): DISCON_dict['PS_Mode'] = int(controller.PS_Mode > 0) DISCON_dict['SD_Mode'] = int(controller.SD_Mode) DISCON_dict['Fl_Mode'] = int(controller.Fl_Mode) + DISCON_dict['TD_Mode'] = int(controller.TD_Mode) DISCON_dict['Flp_Mode'] = int(controller.Flp_Mode) DISCON_dict['OL_Mode'] = int(controller.OL_Mode) + DISCON_dict['PA_Mode'] = int(controller.PA_Mode) + DISCON_dict['Ext_Mode'] = int(controller.Ext_Mode) + DISCON_dict['ZMQ_Mode'] = int(controller.ZMQ_Mode) # ------- FILTERS ------- DISCON_dict['F_LPFCornerFreq'] = turbine.bld_edgewise_freq * 1/4 DISCON_dict['F_LPFDamping'] = controller.F_LPFDamping @@ -392,6 +412,7 @@ def DISCON_dict(turbine, controller, txt_filename=None): DISCON_dict['F_FlpCornerFreq'] = [turbine.bld_flapwise_freq*3, 1.0] DISCON_dict['F_WECornerFreq'] = controller.f_we_cornerfreq DISCON_dict['F_SSCornerFreq'] = controller.f_ss_cornerfreq + DISCON_dict['F_YawErr'] = controller.f_yawerr DISCON_dict['F_FlHighPassFreq'] = controller.f_fl_highpassfreq DISCON_dict['F_FlCornerFreq'] = [controller.ptfm_freq, 1.0] # ------- BLADE PITCH CONTROL ------- @@ -409,7 +430,8 @@ def DISCON_dict(turbine, controller, txt_filename=None): DISCON_dict['PC_FinePit'] = controller.min_pitch DISCON_dict['PC_Switch'] = 1 * deg2rad # ------- INDIVIDUAL PITCH CONTROL ------- - DISCON_dict['IPC_IntSat'] = 0.087266 + DISCON_dict['IPC_Vramp'] = controller.IPC_Vramp + DISCON_dict['IPC_IntSat'] = 0.2618 DISCON_dict['IPC_KP'] = [controller.Kp_ipc1p, controller.Kp_ipc2p] DISCON_dict['IPC_KI'] = [controller.Ki_ipc1p, controller.Ki_ipc2p] DISCON_dict['IPC_aziOffset'] = [0.0, 0.0] @@ -446,17 +468,13 @@ def DISCON_dict(turbine, controller, txt_filename=None): DISCON_dict['WE_FOPoles_v'] = controller.v DISCON_dict['WE_FOPoles'] = controller.A # ------- YAW CONTROL ------- - DISCON_dict['Y_ErrThresh'] = 0.1396 - DISCON_dict['Y_IPC_IntSat'] = 0.0 - DISCON_dict['Y_IPC_n'] = 1 - DISCON_dict['Y_IPC_KP'] = 0.0 - DISCON_dict['Y_IPC_KI'] = 0.0 - DISCON_dict['Y_IPC_omegaLP'] = 0.2094 - DISCON_dict['Y_IPC_zetaLP'] = 1 - DISCON_dict['Y_MErrSet'] = 0.0 - DISCON_dict['Y_omegaLPFast'] = 0.2094 - DISCON_dict['Y_omegaLPSlow'] = 0.1047 - DISCON_dict['Y_Rate'] = 0.0052 + DISCON_dict['Y_uSwitch'] = 0.0 + DISCON_dict['Y_ErrThresh'] = [4.0, 8.0] # NJA: hard coding these params right now b/c we can just use the DISCON pass-through if needed + DISCON_dict['Y_Rate'] = 0.0087 #0.5 deg/s + DISCON_dict['Y_MErrSet'] = 0.0 + DISCON_dict['Y_IPC_IntSat'] = 0.0 + DISCON_dict['Y_IPC_KP'] = 0.0 + DISCON_dict['Y_IPC_KI'] = 0.0 # ------- TOWER FORE-AFT DAMPING ------- DISCON_dict['FA_KI'] = -1 DISCON_dict['FA_HPFCornerFreq'] = 0.0 @@ -481,6 +499,17 @@ def DISCON_dict(turbine, controller, txt_filename=None): DISCON_dict['Ind_BldPitch'] = controller.OL_Ind_BldPitch DISCON_dict['Ind_GenTq'] = controller.OL_Ind_GenTq DISCON_dict['Ind_YawRate'] = controller.OL_Ind_YawRate + # ------- Pitch Actuator ------- + DISCON_dict['PA_Mode'] = controller.PA_Mode + DISCON_dict['PA_CornerFreq'] = controller.PA_CornerFreq + DISCON_dict['PA_Damping'] = controller.PA_Damping + # ------- Zero-MQ ------- + DISCON_dict['ZMQ_CommAddress'] = "tcp://localhost:5555" + DISCON_dict['ZMQ_UpdatePeriod'] = 2 + # Add pass through here + for param, value in controller.controller_params['DISCON'].items(): + DISCON_dict[param] = value + return DISCON_dict diff --git a/Test_Cases/BAR_10/BAR_10_DISCON.IN b/Test_Cases/BAR_10/BAR_10_DISCON.IN index 6f4472c5..a2d09fa1 100644 --- a/Test_Cases/BAR_10/BAR_10_DISCON.IN +++ b/Test_Cases/BAR_10/BAR_10_DISCON.IN @@ -1,8 +1,8 @@ ! Controller parameter input file for the BAR_10 wind turbine -! - File written using ROSCO version 2.5.0 controller tuning logic on 03/21/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 07/22/22 !------- DEBUG ------------------------------------------------------------ -1 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file} +1 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: LoggingLevel 1 + ROSCO LocalVars (.dbg2) 3: LoggingLevel 2 + complete avrSWAP-array (.dbg3)} !------- CONTROLLER FLAGS ------------------------------------------------- 2 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals @@ -16,19 +16,24 @@ 1 ! PS_Mode - Pitch saturation mode {0: no pitch saturation, 1: implement pitch saturation} 0 ! SD_Mode - Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} 0 ! Fl_Mode - Floating specific feedback mode {0: no nacelle velocity feedback, 1: feed back translational velocity, 2: feed back rotational veloicty} +0 ! TD_Mode - Tower damper mode {0: no tower damper, 1: feed back translational nacelle accelleration to pitch angle} 2 ! Flp_Mode - Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control, 2: Cyclic (1P) flap control} -0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time, 2: open loop control vs. wind speed} +0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time} +0 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} +0 ! Ext_Mode - External control mode {0 - not used, 1 - call external dynamic library} +0 ! ZMQ_Mode - Fuse ZeroMQ interaface {0: unused, 1: Yaw Control} !------- FILTERS ---------------------------------------------------------- 0.81771 ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s] 0.70000 ! F_LPFDamping - Damping coefficient {used only when F_FilterType = 2} [-] 2.61601 ! F_NotchCornerFreq - Natural frequency of the notch filter, [rad/s] -0.000000 0.500000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] +0.000000 0.500000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] 0.62830 ! F_SSCornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, [rad/s]. 0.20944 ! F_WECornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s]. -0.000000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. +0.17952 ! F_YawErr - Low pass filter corner frequency for yaw controller [rad/s]. +0.000000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. 0.01042 ! F_FlHighPassFreq - Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s]. -7.848030 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. +7.848030 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. !------- BLADE PITCH CONTROL ---------------------------------------------- 30 ! PC_GS_n - Amount of gain-scheduling table entries @@ -46,10 +51,11 @@ 0.017450000000 ! PC_Switch - Angle above lowest minimum pitch angle for switch, [rad] !------- INDIVIDUAL PITCH CONTROL ----------------------------------------- -0.1 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] -2.050e-08 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -1.450e-09 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. +6.618848 8.273560 ! IPC_Vramp - Start and end wind speeds for cut-in ramp function. First entry: IPC inactive, second entry: IPC fully active. [m/s] +0.3 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] +2.050e-08 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +1.450e-09 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. 0.0 ! IPC_CornerFreqAct - Corner frequency of the first-order actuators model, to induce a phase lag in the IPC signal {0: Disable}, [rad/s] !------- VS TORQUE CONTROL ------------------------------------------------ @@ -75,7 +81,7 @@ !------- WIND SPEED ESTIMATOR --------------------------------------------- 102.996 ! WE_BladeRadius - Blade length (distance from hub center to blade tip), [m] 1 ! WE_CP_n - Amount of parameters in the Cp array -0 ! WE_CP - Parameters that define the parameterized CP(lambda) function +0.0 ! WE_CP - Parameters that define the parameterized CP(lambda) function 0.0 ! WE_Gamma - Adaption gain of the wind speed estimator algorithm [m/rad] 96.8 ! WE_GearboxRatio - Gearbox ratio [>=1], [-] 311169343.31448 ! WE_Jtot - Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2] @@ -87,20 +93,16 @@ -0.00972164 -0.01031092 -0.01090020 -0.01148948 -0.01207877 -0.01266805 -0.01325733 -0.01384662 -0.01443590 -0.01502518 -0.01561447 -0.01620375 -0.01679303 -0.01738232 -0.01797160 -0.01856088 -0.01915016 -0.01973945 -0.02032873 -0.02091801 -0.02150730 -0.02209658 -0.02268586 -0.02327515 -0.02386443 -0.02445371 -0.02504300 -0.02563228 -0.02622156 -0.02378670 -0.02062296 -0.02541485 -0.03159105 -0.03849962 -0.04595997 -0.05389417 -0.06225884 -0.07101431 -0.08016995 -0.08970426 -0.09955610 -0.10976762 -0.12028100 -0.13120419 -0.14238799 -0.15383228 -0.16572504 -0.17783697 -0.19011487 -0.20289651 -0.21593424 -0.22919262 -0.24255457 -0.25635133 -0.27049546 -0.28482651 -0.29923376 -0.31380076 -0.32862514 -0.34372726 ! WE_FOPoles - First order system poles [1/s] !------- YAW CONTROL ------------------------------------------------------ -0.13960 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. [rad^2 s] +0.00000 ! Y_uSwitch - Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] +4.000000 8.000000 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. If Y_uSwitch is zero, only the first value is used. [deg]. +0.00870 ! Y_Rate - Yaw rate [rad/s] +0.00000 ! Y_MErrSet - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] 0.00000 ! Y_IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] -1 ! Y_IPC_n - Number of controller gains (yaw-by-IPC) 0.00000 ! Y_IPC_KP - Yaw-by-IPC proportional controller gain Kp 0.00000 ! Y_IPC_KI - Yaw-by-IPC integral controller gain Ki -0.20940 ! Y_IPC_omegaLP - Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. -1.00000 ! Y_IPC_zetaLP - Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. -0.00000 ! Y_MErrSet - Yaw alignment error, set point [rad] -0.20940 ! Y_omegaLPFast - Corner frequency fast low pass filter, 1.0 [rad/s] -0.10470 ! Y_omegaLPSlow - Corner frequency slow low pass filter, 1/60 [rad/s] -0.00520 ! Y_Rate - Yaw rate [rad/s] !------- TOWER FORE-AFT DAMPING ------------------------------------------- --1 ! FA_KI - Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on [rad s/m] - !NJA - Make this a flag +-1.00000 ! FA_KI - Integral gain for the fore-aft tower damper controller [rad s/m] 0.0 ! FA_HPFCornerFreq - Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] 0.0 ! FA_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from FA damper), [rad] @@ -128,3 +130,16 @@ 0 ! Ind_BldPitch - The column in OL_Filename that contains the blade pitch input in rad 0 ! Ind_GenTq - The column in OL_Filename that contains the generator torque in Nm 0 ! Ind_YawRate - The column in OL_Filename that contains the generator torque in Nm + +!------- Pitch Actuator Model ----------------------------------------------------- +3.140000000000 ! PA_CornerFreq - Pitch actuator bandwidth/cut-off frequency [rad/s] +0.707000000000 ! PA_Damping - Pitch actuator damping ratio [-, unused if PA_Mode = 1] + +!------- External Controller Interface ----------------------------------------------------- +"unused" ! DLL_FileName - Name/location of the dynamic library in the Bladed-DLL format +"unused" ! DLL_InFile - Name of input file sent to the DLL (-) +"DISCON" ! DLL_ProcName - Name of procedure in DLL to be called (-) + +!------- ZeroMQ Interface --------------------------------------------------------- +"tcp://localhost:5555" ! ZMQ_CommAddress - Communication address for ZMQ server, (e.g. "tcp://localhost:5555") +2 ! ZMQ_UpdatePeriod - Call ZeroMQ every [x] seconds, [s] diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN b/Test_Cases/IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN index 0889cb8a..3745fee9 100644 --- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN +++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN @@ -1,8 +1,8 @@ ! Controller parameter input file for the IEA-15-240-RWT-UMaineSemi wind turbine -! - File written using ROSCO version 2.5.0 controller tuning logic on 03/21/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 07/22/22 !------- DEBUG ------------------------------------------------------------ -1 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file} +2 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: LoggingLevel 1 + ROSCO LocalVars (.dbg2) 3: LoggingLevel 2 + complete avrSWAP-array (.dbg3)} !------- CONTROLLER FLAGS ------------------------------------------------- 2 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals @@ -16,19 +16,24 @@ 1 ! PS_Mode - Pitch saturation mode {0: no pitch saturation, 1: implement pitch saturation} 0 ! SD_Mode - Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} 2 ! Fl_Mode - Floating specific feedback mode {0: no nacelle velocity feedback, 1: feed back translational velocity, 2: feed back rotational veloicty} +0 ! TD_Mode - Tower damper mode {0: no tower damper, 1: feed back translational nacelle accelleration to pitch angle} 0 ! Flp_Mode - Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control, 2: Cyclic (1P) flap control} -0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time, 2: open loop control vs. wind speed} +0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time} +2 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} +0 ! Ext_Mode - External control mode {0 - not used, 1 - call external dynamic library} +0 ! ZMQ_Mode - Fuse ZeroMQ interaface {0: unused, 1: Yaw Control} !------- FILTERS ---------------------------------------------------------- 1.00810 ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s] 0.70000 ! F_LPFDamping - Damping coefficient {used only when F_FilterType = 2} [-] 3.35500 ! F_NotchCornerFreq - Natural frequency of the notch filter, [rad/s] -0.000000 0.250000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] +0.000000 0.250000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] 0.62830 ! F_SSCornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, [rad/s]. 0.20944 ! F_WECornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s]. -0.213000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. +0.17952 ! F_YawErr - Low pass filter corner frequency for yaw controller [rad/s]. +0.213000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. 0.01042 ! F_FlHighPassFreq - Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s]. -10.461600 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. +10.461600 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. !------- BLADE PITCH CONTROL ---------------------------------------------- 30 ! PC_GS_n - Amount of gain-scheduling table entries @@ -46,10 +51,11 @@ 0.017450000000 ! PC_Switch - Angle above lowest minimum pitch angle for switch, [rad] !------- INDIVIDUAL PITCH CONTROL ----------------------------------------- -0.1 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] -0.000e+00 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -0.000e+00 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. +8.592000 10.740000 ! IPC_Vramp - Start and end wind speeds for cut-in ramp function. First entry: IPC inactive, second entry: IPC fully active. [m/s] +0.3 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] +0.000e+00 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +0.000e+00 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. 0.0 ! IPC_CornerFreqAct - Corner frequency of the first-order actuators model, to induce a phase lag in the IPC signal {0: Disable}, [rad/s] !------- VS TORQUE CONTROL ------------------------------------------------ @@ -75,7 +81,7 @@ !------- WIND SPEED ESTIMATOR --------------------------------------------- 120.000 ! WE_BladeRadius - Blade length (distance from hub center to blade tip), [m] 1 ! WE_CP_n - Amount of parameters in the Cp array -0 ! WE_CP - Parameters that define the parameterized CP(lambda) function +0.0 ! WE_CP - Parameters that define the parameterized CP(lambda) function 0.0 ! WE_Gamma - Adaption gain of the wind speed estimator algorithm [m/rad] 1.0 ! WE_GearboxRatio - Gearbox ratio [>=1], [-] 318628138.00000 ! WE_Jtot - Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2] @@ -87,20 +93,16 @@ -0.02438353 -0.02655283 -0.02872212 -0.03089141 -0.03306071 -0.03523000 -0.03739930 -0.03956859 -0.04173788 -0.04390718 -0.04607647 -0.04824576 -0.05041506 -0.05258435 -0.05475365 -0.05692294 -0.05909223 -0.06126153 -0.06343082 -0.06560011 -0.06776941 -0.06993870 -0.07210800 -0.07427729 -0.07644658 -0.07861588 -0.08078517 -0.08295446 -0.08512376 -0.08490640 -0.05739531 -0.05967534 -0.06643664 -0.07537721 -0.08537869 -0.09664144 -0.10851650 -0.12137925 -0.13453168 -0.14834459 -0.16280188 -0.17753158 -0.19283401 -0.20862160 -0.22461456 -0.24120058 -0.25817445 -0.27538476 -0.29299882 -0.31103587 -0.32941546 -0.34807902 -0.36693717 -0.38625237 -0.40583167 -0.42579305 -0.44596365 -0.46626113 -0.48675074 -0.50756940 ! WE_FOPoles - First order system poles [1/s] !------- YAW CONTROL ------------------------------------------------------ -0.13960 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. [rad^2 s] +0.00000 ! Y_uSwitch - Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] +4.000000 8.000000 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. If Y_uSwitch is zero, only the first value is used. [deg]. +0.00870 ! Y_Rate - Yaw rate [rad/s] +0.00000 ! Y_MErrSet - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] 0.00000 ! Y_IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] -1 ! Y_IPC_n - Number of controller gains (yaw-by-IPC) 0.00000 ! Y_IPC_KP - Yaw-by-IPC proportional controller gain Kp 0.00000 ! Y_IPC_KI - Yaw-by-IPC integral controller gain Ki -0.20940 ! Y_IPC_omegaLP - Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. -1.00000 ! Y_IPC_zetaLP - Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. -0.00000 ! Y_MErrSet - Yaw alignment error, set point [rad] -0.20940 ! Y_omegaLPFast - Corner frequency fast low pass filter, 1.0 [rad/s] -0.10470 ! Y_omegaLPSlow - Corner frequency slow low pass filter, 1/60 [rad/s] -0.00520 ! Y_Rate - Yaw rate [rad/s] !------- TOWER FORE-AFT DAMPING ------------------------------------------- --1 ! FA_KI - Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on [rad s/m] - !NJA - Make this a flag +-1.00000 ! FA_KI - Integral gain for the fore-aft tower damper controller [rad s/m] 0.0 ! FA_HPFCornerFreq - Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] 0.0 ! FA_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from FA damper), [rad] @@ -128,3 +130,16 @@ 0 ! Ind_BldPitch - The column in OL_Filename that contains the blade pitch input in rad 0 ! Ind_GenTq - The column in OL_Filename that contains the generator torque in Nm 0 ! Ind_YawRate - The column in OL_Filename that contains the generator torque in Nm + +!------- Pitch Actuator Model ----------------------------------------------------- +1.570800000000 ! PA_CornerFreq - Pitch actuator bandwidth/cut-off frequency [rad/s] +0.707000000000 ! PA_Damping - Pitch actuator damping ratio [-, unused if PA_Mode = 1] + +!------- External Controller Interface ----------------------------------------------------- +"unused" ! DLL_FileName - Name/location of the dynamic library in the Bladed-DLL format +"unused" ! DLL_InFile - Name of input file sent to the DLL (-) +"DISCON" ! DLL_ProcName - Name of procedure in DLL to be called (-) + +!------- ZeroMQ Interface --------------------------------------------------------- +"tcp://localhost:5555" ! ZMQ_CommAddress - Communication address for ZMQ server, (e.g. "tcp://localhost:5555") +2 ! ZMQ_UpdatePeriod - Call ZeroMQ every [x] seconds, [s] diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat index 8d74a1f8..07ca94c0 100644 --- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat +++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat @@ -183,5 +183,9 @@ True TabDelim - Use tab delimiters in text tabular output file? (fla "YawBrMzp" "TwrBsFzt" "NacYaw" +"NcIMURAYs" +"RootMyc1" +"RootMyc2" +"RootMyc3" END of input file (the word "END" must appear in the first 3 columns of this last OutList line) --------------------------------------------------------------------------------------- diff --git a/Test_Cases/NREL-5MW/DISCON.IN b/Test_Cases/NREL-5MW/DISCON.IN index 1ef3dcf9..b5a353ae 100644 --- a/Test_Cases/NREL-5MW/DISCON.IN +++ b/Test_Cases/NREL-5MW/DISCON.IN @@ -1,8 +1,8 @@ ! Controller parameter input file for the NREL-5MW wind turbine -! - File written using ROSCO version 2.5.0 controller tuning logic on 03/21/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 07/22/22 !------- DEBUG ------------------------------------------------------------ -1 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file} +1 ! LoggingLevel - {0: write no debug files, 1: write standard output .dbg-file, 2: LoggingLevel 1 + ROSCO LocalVars (.dbg2) 3: LoggingLevel 2 + complete avrSWAP-array (.dbg3)} !------- CONTROLLER FLAGS ------------------------------------------------- 1 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals @@ -16,19 +16,24 @@ 1 ! PS_Mode - Pitch saturation mode {0: no pitch saturation, 1: implement pitch saturation} 0 ! SD_Mode - Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} 0 ! Fl_Mode - Floating specific feedback mode {0: no nacelle velocity feedback, 1: feed back translational velocity, 2: feed back rotational veloicty} +0 ! TD_Mode - Tower damper mode {0: no tower damper, 1: feed back translational nacelle accelleration to pitch angle} 0 ! Flp_Mode - Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control, 2: Cyclic (1P) flap control} -0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time, 2: open loop control vs. wind speed} +0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time} +0 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} +0 ! Ext_Mode - External control mode {0 - not used, 1 - call external dynamic library} +0 ! ZMQ_Mode - Fuse ZeroMQ interaface {0: unused, 1: Yaw Control} !------- FILTERS ---------------------------------------------------------- 1.57080 ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s] 0.00000 ! F_LPFDamping - Damping coefficient {used only when F_FilterType = 2} [-] 0.00000 ! F_NotchCornerFreq - Natural frequency of the notch filter, [rad/s] -0.000000 0.250000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] +0.000000 0.250000 ! F_NotchBetaNumDen - Two notch damping values (numerator and denominator, resp) - determines the width and depth of the notch, [-] 0.62830 ! F_SSCornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the setpoint smoother, [rad/s]. 0.20944 ! F_WECornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s]. -0.000000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. +0.17952 ! F_YawErr - Low pass filter corner frequency for yaw controller [rad/s]. +0.000000 1.000000 ! F_FlCornerFreq - Natural frequency and damping in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s, -]. 0.01042 ! F_FlHighPassFreq - Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s]. -0.000000 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. +0.000000 1.000000 ! F_FlpCornerFreq - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -]. !------- BLADE PITCH CONTROL ---------------------------------------------- 30 ! PC_GS_n - Amount of gain-scheduling table entries @@ -46,10 +51,11 @@ 0.017450000000 ! PC_Switch - Angle above lowest minimum pitch angle for switch, [rad] !------- INDIVIDUAL PITCH CONTROL ----------------------------------------- -0.1 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] -0.000e+00 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -0.000e+00 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] -0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. +9.120000 11.400000 ! IPC_Vramp - Start and end wind speeds for cut-in ramp function. First entry: IPC inactive, second entry: IPC fully active. [m/s] +0.3 ! IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from IPC), [rad] +0.000e+00 0.000e+00 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +0.000e+00 0.000e+00 ! IPC_KI - Integral gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] +0.000000 0.000000 ! IPC_aziOffset - Phase offset added to the azimuth angle for the individual pitch controller, [rad]. 0.0 ! IPC_CornerFreqAct - Corner frequency of the first-order actuators model, to induce a phase lag in the IPC signal {0: Disable}, [rad/s] !------- VS TORQUE CONTROL ------------------------------------------------ @@ -75,7 +81,7 @@ !------- WIND SPEED ESTIMATOR --------------------------------------------- 63.000 ! WE_BladeRadius - Blade length (distance from hub center to blade tip), [m] 1 ! WE_CP_n - Amount of parameters in the Cp array -0 ! WE_CP - Parameters that define the parameterized CP(lambda) function +0.0 ! WE_CP - Parameters that define the parameterized CP(lambda) function 0.0 ! WE_Gamma - Adaption gain of the wind speed estimator algorithm [m/rad] 97.0 ! WE_GearboxRatio - Gearbox ratio [>=1], [-] 43702538.05700 ! WE_Jtot - Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2] @@ -87,20 +93,16 @@ -0.01638154 -0.01796321 -0.01954487 -0.02112654 -0.02270820 -0.02428987 -0.02587154 -0.02745320 -0.02903487 -0.03061653 -0.03219820 -0.03377987 -0.03536153 -0.03694320 -0.03852486 -0.04010653 -0.04168820 -0.04326986 -0.04485153 -0.04643319 -0.04801486 -0.04959652 -0.05117819 -0.05275986 -0.05434152 -0.05592319 -0.05758373 -0.05882656 -0.06845507 -0.05992890 0.02094683 0.01327182 0.00285485 -0.00935731 -0.02210773 -0.03573037 -0.04990222 -0.06404904 -0.07899629 -0.09463190 -0.10954192 -0.12525205 -0.14168652 -0.15843395 -0.17415061 -0.19052486 -0.20780146 -0.22581018 -0.24373777 -0.26010871 -0.27706767 -0.29551708 -0.31430599 -0.33428552 -0.35420853 -0.37183729 -0.38936451 -0.40828911 -0.42758878 -0.44818175 ! WE_FOPoles - First order system poles [1/s] !------- YAW CONTROL ------------------------------------------------------ -0.13960 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. [rad^2 s] +0.00000 ! Y_uSwitch - Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] +4.000000 8.000000 ! Y_ErrThresh - Yaw error threshold. Turbine begins to yaw when it passes this. If Y_uSwitch is zero, only the first value is used. [deg]. +0.00870 ! Y_Rate - Yaw rate [rad/s] +0.00000 ! Y_MErrSet - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] 0.00000 ! Y_IPC_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] -1 ! Y_IPC_n - Number of controller gains (yaw-by-IPC) 0.00000 ! Y_IPC_KP - Yaw-by-IPC proportional controller gain Kp 0.00000 ! Y_IPC_KI - Yaw-by-IPC integral controller gain Ki -0.20940 ! Y_IPC_omegaLP - Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. -1.00000 ! Y_IPC_zetaLP - Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. -0.00000 ! Y_MErrSet - Yaw alignment error, set point [rad] -0.20940 ! Y_omegaLPFast - Corner frequency fast low pass filter, 1.0 [rad/s] -0.10470 ! Y_omegaLPSlow - Corner frequency slow low pass filter, 1/60 [rad/s] -0.00520 ! Y_Rate - Yaw rate [rad/s] !------- TOWER FORE-AFT DAMPING ------------------------------------------- --1 ! FA_KI - Integral gain for the fore-aft tower damper controller, -1 = off / >0 = on [rad s/m] - !NJA - Make this a flag +-1.00000 ! FA_KI - Integral gain for the fore-aft tower damper controller [rad s/m] 0.0 ! FA_HPFCornerFreq - Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] 0.0 ! FA_IntSat - Integrator saturation (maximum signal amplitude contribution to pitch from FA damper), [rad] @@ -128,3 +130,16 @@ 0 ! Ind_BldPitch - The column in OL_Filename that contains the blade pitch input in rad 0 ! Ind_GenTq - The column in OL_Filename that contains the generator torque in Nm 0 ! Ind_YawRate - The column in OL_Filename that contains the generator torque in Nm + +!------- Pitch Actuator Model ----------------------------------------------------- +3.140000000000 ! PA_CornerFreq - Pitch actuator bandwidth/cut-off frequency [rad/s] +0.707000000000 ! PA_Damping - Pitch actuator damping ratio [-, unused if PA_Mode = 1] + +!------- External Controller Interface ----------------------------------------------------- +"unused" ! DLL_FileName - Name/location of the dynamic library in the Bladed-DLL format +"unused" ! DLL_InFile - Name of input file sent to the DLL (-) +"DISCON" ! DLL_ProcName - Name of procedure in DLL to be called (-) + +!------- ZeroMQ Interface --------------------------------------------------------- +"tcp://localhost:5555" ! ZMQ_CommAddress - Communication address for ZMQ server, (e.g. "tcp://localhost:5555") +2 ! ZMQ_UpdatePeriod - Call ZeroMQ every [x] seconds, [s] diff --git a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat index a7f094dd..86351f2d 100644 --- a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat +++ b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat @@ -81,7 +81,7 @@ True GenTiStp - Method to stop the generator {T: timed using TimGen false DLL_Ramp - Whether a linear ramp should be used between DLL_DT time steps [introduces time shift when true] (flag) [used only with Bladed Interface] 9999.9 BPCutoff - Cutoff frequency for low-pass filter on blade pitch from DLL (Hz) [used only with Bladed Interface] 0 NacYaw_North - Reference yaw angle of the nacelle when the upwind end points due North (deg) [used only with Bladed Interface] - 0 Ptch_Cntrl - Record 28: Use individual pitch control {0: collective pitch; 1: individual pitch control} (switch) [used only with Bladed Interface] + 1 Ptch_Cntrl - Record 28: Use individual pitch control {0: collective pitch; 1: individual pitch control} (switch) [used only with Bladed Interface] 0 Ptch_SetPnt - Record 5: Below-rated pitch angle set-point (deg) [used only with Bladed Interface] 0 Ptch_Min - Record 6: Minimum pitch angle (deg) [used only with Bladed Interface] 0 Ptch_Max - Record 7: Maximum pitch angle (deg) [used only with Bladed Interface] diff --git a/Test_Cases/Wind/NoShr_3-15_50s.wnd b/Test_Cases/Wind/NoShr_3-15_50s.wnd new file mode 100644 index 00000000..42b8a2d2 --- /dev/null +++ b/Test_Cases/Wind/NoShr_3-15_50s.wnd @@ -0,0 +1,17 @@ +!Wind file for input to OpenFAST. +!Time Wind Wind Vert. Horiz. Vert. LinV Gust +! Speed Dir Speed Shear Shear Shear Speed +0.00 5.00 0.00 0.00 0.00 0.00 0.00 0.00 +50.0 5.00 0.00 0.00 0.00 0.00 0.00 0.00 +50.1 6.00 0.00 0.00 0.00 0.00 0.00 0.00 +100.0 6.00 0.00 0.00 0.00 0.00 0.00 0.00 +100.1 7.00 0.00 0.00 0.00 0.00 0.00 0.00 +150.0 7.00 0.00 0.00 0.00 0.00 0.00 0.00 +150.1 8.00 0.00 0.00 0.00 0.00 0.00 0.00 +200.0 8.00 0.00 0.00 0.00 0.00 0.00 0.00 +200.1 9.00 0.00 0.00 0.00 0.00 0.00 0.00 +250.0 9.00 0.00 0.00 0.00 0.00 0.00 0.00 +250.1 10.00 0.00 0.00 0.00 0.00 0.00 0.00 +300.0 10.00 0.00 0.00 0.00 0.00 0.00 0.00 +300.1 11.00 0.00 0.00 0.00 0.00 0.00 0.00 + diff --git a/Test_Cases/update_discons.py b/Test_Cases/update_discons.py deleted file mode 100644 index 076387ee..00000000 --- a/Test_Cases/update_discons.py +++ /dev/null @@ -1,54 +0,0 @@ -''' -Update the DISCON.IN examples in the ROSCO repository using the Tune_Case/ .yaml files - -''' -import os - -# ROSCO toolbox modules -from ROSCO_toolbox import controller as ROSCO_controller -from ROSCO_toolbox import turbine as ROSCO_turbine -from ROSCO_toolbox.utilities import write_DISCON -from ROSCO_toolbox.inputs.validation import load_rosco_yaml - -test_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.realpath(os.path.join(test_dir,'../Tune_Cases')) - - -# Paths are relative to Tune_Case/ and Test_Case/ -tune_to_test_map = { - 'NREL5MW.yaml': 'NREL-5MW/DISCON.IN', - 'IEA15MW.yaml': 'IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN', - 'BAR.yaml': 'BAR_10/BAR_10_DISCON.IN' -} - -for tuning_yaml in tune_to_test_map: - - # Load yaml file - inps = load_rosco_yaml(os.path.join(tune_dir,tuning_yaml)) - path_params = inps['path_params'] - turbine_params = inps['turbine_params'] - controller_params = inps['controller_params'] - - # Instantiate turbine, controller, and file processing classes - turbine = ROSCO_turbine.Turbine(turbine_params) - controller = ROSCO_controller.Controller(controller_params) - - # Load turbine data from OpenFAST and rotor performance text file - turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - dev_branch=True, - rot_source='txt', - txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) - ) - - # Tune controller - controller.tune_controller(turbine) - - # Write parameter input file - discon_in_file = os.path.join(test_dir,tune_to_test_map[tuning_yaml]) - write_DISCON( - turbine,controller, - param_file=discon_in_file, - txt_filename=path_params['rotor_performance_filename'].split('/')[-1] - ) diff --git a/Test_Cases/update_rosco_discons.py b/Test_Cases/update_rosco_discons.py new file mode 100644 index 00000000..ca08ea31 --- /dev/null +++ b/Test_Cases/update_rosco_discons.py @@ -0,0 +1,29 @@ +''' +Update the DISCON.IN examples in the ROSCO repository using the Tune_Case/ .yaml files + +''' +import os +from ROSCO_toolbox.ofTools.fast_io.update_discons import update_discons + + +if __name__=="__main__": + + # paths relative to Tune_Case/ and Test_Case/ + map_rel = { + 'NREL5MW.yaml': 'NREL-5MW/DISCON.IN', + 'IEA15MW.yaml': 'IEA-15-240-RWT-UMaineSemi/DISCON-UMaineSemi.IN', + 'BAR.yaml': 'BAR_10/BAR_10_DISCON.IN' + } + + # Directories + test_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.realpath(os.path.join(test_dir,'../Tune_Cases')) + + # Make paths absolute + map_abs = {} + for tune, test in map_rel.items(): + tune = os.path.join(tune_dir,tune) + map_abs[tune] = os.path.join(test_dir,test) + + # Make discons + update_discons(map_abs) diff --git a/Tune_Cases/BAR.yaml b/Tune_Cases/BAR.yaml index bb21281d..e1d7c463 100644 --- a/Tune_Cases/BAR.yaml +++ b/Tune_Cases/BAR.yaml @@ -6,7 +6,7 @@ path_params: FAST_InputFile: 'BAR_10.fst' # Name of *.fst file FAST_directory: '../Test_Cases/BAR_10' # Main OpenFAST model directory, where the *.fst lives # Optional (but suggested...) - rotor_performance_filename: '../Test_Cases/BAR_10/Cp_Ct_Cq.BAR_10.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) + rotor_performance_filename: 'Cp_Ct_Cq.BAR_10.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) # -------------------------------- TURBINE PARAMETERS ----------------------------------- turbine_params: diff --git a/Tune_Cases/IEA15MW.yaml b/Tune_Cases/IEA15MW.yaml index 673716eb..fa2aeb57 100644 --- a/Tune_Cases/IEA15MW.yaml +++ b/Tune_Cases/IEA15MW.yaml @@ -6,7 +6,7 @@ path_params: FAST_InputFile: 'IEA-15-240-RWT-UMaineSemi.fst' # Name of *.fst file FAST_directory: '../Test_Cases/IEA-15-240-RWT-UMaineSemi' # Main OpenFAST model directory, where the *.fst lives # Optional (but suggested...) - rotor_performance_filename: '../Test_Cases/IEA-15-240-RWT-UMaineSemi/Cp_Ct_Cq.IEA15MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) + rotor_performance_filename: 'Cp_Ct_Cq.IEA15MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) # -------------------------------- TURBINE PARAMETERS ----------------------------------- turbine_params: @@ -25,7 +25,7 @@ turbine_params: #------------------------------- CONTROLLER PARAMETERS ---------------------------------- controller_params: # Controller flags - LoggingLevel: 1 # {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file + LoggingLevel: 2 # {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file F_LPFType: 2 # {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals) F_NotchType: 0 # Notch on the measured generator speed {0: disable, 1: enable} IPC_ControlMode: 0 # Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions} @@ -38,6 +38,7 @@ controller_params: SD_Mode: 0 # Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} Fl_Mode: 2 # Floating specific feedback mode {0: no nacelle velocity feedback, 1: nacelle velocity feedback} Flp_Mode: 0 # Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control} + PA_Mode: 2 # Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} # Controller parameters # U_pc: [14] zeta_pc: 1.0 # Pitch controller desired damping ratio [-] @@ -50,3 +51,6 @@ controller_params: min_pitch: 0.0 # Minimum pitch angle [rad], {default = 0 degrees} vs_minspd: 0.523598775 # Minimum rotor speed [rad/s], {default = 0 rad/s} ps_percent: 0.8 # Percent peak shaving [%, <= 1 ], {default = 80%} + PA_CornerFreq: 1.5708 # Pitch actuator natural frequency [rad/s] + PA_Damping: 0.707 # Pitch actuator natural frequency [rad/s] + diff --git a/Tune_Cases/IEA15MW_ExtInterface.yaml b/Tune_Cases/IEA15MW_ExtInterface.yaml new file mode 100644 index 00000000..7abe10da --- /dev/null +++ b/Tune_Cases/IEA15MW_ExtInterface.yaml @@ -0,0 +1,59 @@ +# --------------------- ROSCO controller tuning input file ------------------- + # Written for use with ROSCO_Toolbox tuning procedures + # Turbine: IEA 15MW Reference Wind Turbine +# ------------------------------ OpenFAST PATH DEFINITIONS ------------------------------ +path_params: + FAST_InputFile: 'IEA-15-240-RWT-UMaineSemi.fst' # Name of *.fst file + FAST_directory: '../Test_Cases/IEA-15-240-RWT-UMaineSemi' # Main OpenFAST model directory, where the *.fst lives + # Optional (but suggested...) + rotor_performance_filename: 'Cp_Ct_Cq.IEA15MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) + +# -------------------------------- TURBINE PARAMETERS ----------------------------------- +turbine_params: + rotor_inertia: 310619488. # Rotor inertia [kg m^2], {Available in Elastodyn .sum file} + rated_rotor_speed: 0.7916813478 # Rated rotor speed [rad/s] + v_min: 3. # Cut-in wind speed [m/s] + v_rated: 10.74 # Rated wind speed [m/s] + v_max: 25.0 # Cut-out wind speed [m/s], -- Does not need to be exact (JUST ASSUME FOR NOW) + max_pitch_rate: 0.0349 # Maximum blade pitch rate [rad/s] + max_torque_rate: 4500000. # Maximum torque rate [Nm/s], {~1/4 VS_RtTq/s} + rated_power: 15000000. # Rated Power [W] + bld_edgewise_freq: 4.0324 # Blade edgewise first natural frequency [rad/s] + bld_flapwise_freq: 3.4872 # Blade flapwise first natural frequency [rad/s] + TSR_operational: 9.0 + +#------------------------------- CONTROLLER PARAMETERS ---------------------------------- +controller_params: + # Controller flags + LoggingLevel: 2 # {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file + F_LPFType: 2 # {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals) + F_NotchType: 0 # Notch on the measured generator speed {0: disable, 1: enable} + IPC_ControlMode: 0 # Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions} + VS_ControlMode: 2 # Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control} + PC_ControlMode: 1 # Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control} + Y_ControlMode: 0 # Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC} + SS_Mode: 1 # Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing} + WE_Mode: 2 # Wind speed estimator mode {0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.)} + PS_Mode: 3 # Pitch saturation mode {0: no pitch saturation, 1: peak shaving, 2: Cp-maximizing pitch saturation, 3: peak shaving and Cp-maximizing pitch saturation} + SD_Mode: 0 # Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} + Fl_Mode: 1 # Floating specific feedback mode {0: no nacelle velocity feedback, 1: nacelle velocity feedback} + Flp_Mode: 0 # Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control} + Ext_Mode: 1 # Use external controller + # Controller parameters + U_pc: [14,20] + zeta_pc: [2.0,1.0] # Pitch controller desired damping ratio [-] + omega_pc: [0.15,0.3] # Pitch controller desired natural frequency [rad/s] + interp_type: sigma + zeta_vs: 0.85 # Torque controller desired damping ratio [-] + omega_vs: 0.12 # Torque controller desired natural frequency [rad/s] + twr_freq: 3.355 # for semi only! + ptfm_freq: 0.213 # for semi only! + # Optional - these can be defined, but do not need to be + min_pitch: 0.0 # Minimum pitch angle [rad], {default = 0 degrees} + vs_minspd: 0.523598775 # Minimum rotor speed [rad/s], {default = 0 rad/s} + ps_percent: 0.8 # Percent peak shaving [%, <= 1 ], {default = 80%} + + DISCON: + DLL_FileName: /Users/dzalkind/Tools/ROSCO/ROSCO/build/libdiscon_copy.dylib + DLL_InFile: /Users/dzalkind/Tools/ROSCO/Test_Cases/NREL-5MW/DISCON.IN + DLL_ProcName: DISCON \ No newline at end of file diff --git a/Tune_Cases/IEA15MW_FOCAL.yaml b/Tune_Cases/IEA15MW_FOCAL.yaml index 09a00dc9..06a36cf8 100644 --- a/Tune_Cases/IEA15MW_FOCAL.yaml +++ b/Tune_Cases/IEA15MW_FOCAL.yaml @@ -19,7 +19,8 @@ turbine_params: max_torque_rate: 4500000. # Maximum torque rate [Nm/s], {~1/4 VS_RtTq/s} rated_power: 1.4780e+07 # Rated Power [W] TSR_operational: 7.49 - + bld_edgewise_freq: 4.0324 # Blade edgewise first natural frequency [rad/s] + bld_flapwise_freq: 3.4872 # Blade flapwise first natural frequency [rad/s] #------------------------------- CONTROLLER PARAMETERS ---------------------------------- controller_params: # Controller flags diff --git a/Tune_Cases/NREL5MW.yaml b/Tune_Cases/NREL5MW.yaml index 21f5acab..9ad252dc 100644 --- a/Tune_Cases/NREL5MW.yaml +++ b/Tune_Cases/NREL5MW.yaml @@ -6,7 +6,7 @@ path_params: FAST_InputFile: 'NREL-5MW.fst' # Name of *.fst file FAST_directory: '../Test_Cases/NREL-5MW' # Main OpenFAST model directory, where the *.fst lives # Optional - rotor_performance_filename: '../Test_Cases/NREL-5MW/Cp_Ct_Cq.NREL5MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) + rotor_performance_filename: 'Cp_Ct_Cq.NREL5MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) # -------------------------------- TURBINE PARAMETERS ----------------------------------- turbine_params: diff --git a/Tune_Cases/NREL5MW_PassThrough.yaml b/Tune_Cases/NREL5MW_PassThrough.yaml new file mode 100644 index 00000000..2ad3be80 --- /dev/null +++ b/Tune_Cases/NREL5MW_PassThrough.yaml @@ -0,0 +1,64 @@ +--- # ---------------------NREL Generic controller tuning input file ------------------- + # Written for use with ROSCO_Toolbox tuning procedures + # Turbine: NREL 5MW Reference Wind Turbine +# ------------------------------ OpenFAST PATH DEFINITIONS ------------------------------ +path_params: + FAST_InputFile: 'NREL-5MW.fst' # Name of *.fst file + FAST_directory: '../Test_Cases/NREL-5MW' # Main OpenFAST model directory, where the *.fst lives + # Optional + rotor_performance_filename: 'Cp_Ct_Cq.NREL5MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already) + +# -------------------------------- TURBINE PARAMETERS ----------------------------------- +turbine_params: + rotor_inertia: 38677040.613 # Rotor inertia [kg m^2], {Available in Elastodyn .sum file} + rated_rotor_speed: 1.26711 # Rated rotor speed [rad/s] + v_min: 3.0 # Cut-in wind speed [m/s] + v_rated: 11.4 # Rated wind speed [m/s] + v_max: 25.0 # Cut-out wind speed [m/s], -- Does not need to be exact (JUST ASSUME FOR NOW) + max_pitch_rate: 0.1745 # Maximum blade pitch rate [rad/s] + max_torque_rate: 1500000. # Maximum torque rate [Nm/s], {~1/4 VS_RtTq/s} + rated_power: 5000000. # Rated Power [W] + bld_edgewise_freq: 6.2831853 # Blade edgewise first natural frequency [rad/s] + bld_flapwise_freq: 0.0 # Blade flapwise first natural frequency [rad/s] + # Optional + # TSR_operational: # None # Desired below-rated operational tip speed ratio (Cp-maximizing TSR is used if not defined) +#------------------------------- CONTROLLER PARAMETERS ---------------------------------- +controller_params: + # Controller flags + LoggingLevel: 2 # {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file + F_LPFType: 1 # {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals) + F_NotchType: 0 # Notch filter on generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion} + IPC_ControlMode: 0 # Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions} + VS_ControlMode: 3 # Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control} + PC_ControlMode: 1 # Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control} + Y_ControlMode: 0 # Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC} + SS_Mode: 1 # Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing} + WE_Mode: 2 # Wind speed estimator mode {0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.)} + PS_Mode: 1 # Pitch saturation mode {0: no pitch saturation, 1: peak shaving, 2: Cp-maximizing pitch saturation, 3: peak shaving and Cp-maximizing pitch saturation} + SD_Mode: 0 # Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown} + Fl_Mode: 0 # Floating specific feedback mode {0: no nacelle velocity feedback, 1: nacelle velocity feedback} + Flp_Mode: 0 # Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control} + # Controller parameters + zeta_pc: 0.7 # Pitch controller desired damping ratio [-] + omega_pc: 0.6 # Pitch controller desired natural frequency [rad/s] + zeta_vs: 0.7 # Torque controller desired damping ratio [-] + omega_vs: 0.15 # Torque controller desired natural frequency [rad/s] + twr_freq: 0.4499 # Tower natural frequency [rad/s] + ptfm_freq: 0.2325 # Platform natural frequency [rad/s] (OC4Hywind Parameters, here) + # Optional + ps_percent: 0.80 # Percent peak shaving [%, <= 1 ], {default = 80%} + sd_maxpit: 0.4363 # Maximum blade pitch angle to initiate shutdown [rad], {default = bld pitch at v_max} + + DISCON: + IPC_ControlMode: 2 + IPC_Vramp: [10,14] + IPC_IntSat: 0.2 + IPC_KI: [1.e-8, 0] + IPC_aziOffset: [0.5236,0] + VS_MaxTq: 44170.325 + PC_GS_n: 2 + PC_GS_angles: [0, 0.4] + PC_GS_KP: [-.02, -0.003] + PC_GS_KI: [-.008, -.002] + PC_GS_KD: [0, 0] + PC_GS_TF: [0, 0] \ No newline at end of file diff --git a/docs/source/api_change.rst b/docs/source/api_change.rst index 7865d377..9efdd8db 100644 --- a/docs/source/api_change.rst +++ b/docs/source/api_change.rst @@ -9,29 +9,89 @@ The changes are tabulated according to the line number, and flag name. The line number corresponds to the resulting line number after all changes are implemented. Thus, be sure to implement each in order so that subsequent line numbers are correct. +2.5.0 to 2.6.0 +------------------------------- +IPC +- A wind speed based soft cut-in using a sigma interpolation is added for the IPC controller + +Pitch Actuator +- A first or second order filter can be used to model a pitch actuator + +External Control Interface +- Call another control library from ROSCO + +ZeroMQ Interface +- Communicate with an external routine via ZeroMQ. Only yaw control currently supported + +Updated yaw control +- Filter wind direction with deadband, and yaw until direction error changes signs (https://iopscience.iop.org/article/10.1088/1742-6596/1037/3/032011) + +====== ================= ====================================================================================================================================================================================================== +New in ROSCO develop +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Line Input Name Example Value +====== ================= ====================================================================================================================================================================================================== +19 TD_Mode 0 ! TD_Mode - Tower damper mode {0: no tower damper, 1: feed back translational nacelle accelleration to pitch angle} +22 PA_Mode 0 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} +23 Ext_Mode 0 ! Ext_Mode - External control mode {0 - not used, 1 - call external dynamic library} +24 ZMQ_Mode 0 ! ZMQ_Mode - Fuse ZeroMQ interaface {0: unused, 1: Yaw Control} +33 F_YawErr 0.17952 ! F_YawErr - Low pass filter corner frequency for yaw controller [rad/s]. +54 IPC_Vramp 9.120000 11.400000 ! IPC_Vramp - Start and end wind speeds for cut-in ramp function. First entry: IPC inactive, second entry: IPC fully active. [m/s] +96 Y_uSwitch 0.00000 ! Y_uSwitch - Wind speed to switch between Y_ErrThresh. If zero, only the first value of Y_ErrThresh is used [m/s] +133 Empty Line N/A +134 PitchActSec !------- Pitch Actuator Model ----------------------------------------------------- +135 PA_CornerFreq 3.140000000000 ! PA_CornerFreq - Pitch actuator bandwidth/cut-off frequency [rad/s] +136 PA_Damping 0.707000000000 ! PA_Damping - Pitch actuator damping ratio [-, unused if PA_Mode = 1] +137 Empty Line +138 ExtConSec !------- External Controller Interface ----------------------------------------------------- +139 DLL_FileName "unused" ! DLL_FileName - Name/location of the dynamic library in the Bladed-DLL format +140 DLL_InFile "unused" ! DLL_InFile - Name of input file sent to the DLL (-) +141 DLL_ProcName "DISCON" ! DLL_ProcName - Name of procedure in DLL to be called (-) +142 Empty Line +143 ZeroMQSec !------- ZeroMQ Interface --------------------------------------------------------- +144 ZMQ_CommAddress "tcp://localhost:5555" ! ZMQ_CommAddress - Communication address for ZMQ server, (e.g. "tcp://localhost:5555") +145 ZMQ_UpdatePeriod 2 ! ZMQ_UpdatePeriod - Call ZeroMQ every [x] seconds, [s] +====== ================= ====================================================================================================================================================================================================== + +====== ================= ====================================================================================================================================================================================================== +Modified in ROSCO develop +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Line Input Name Example Value +====== ================= ====================================================================================================================================================================================================== +97 Y_ErrThresh 4.000000 8.000000 ! Y_ErrThresh - Yaw error threshold/deadband. Turbine begins to yaw when it passes this. If Y_uSwitch is zero, only the first value is used. [deg]. +98 Y_Rate 0.00870 ! Y_Rate - Yaw rate [rad/s] +99 Y_MErrSet 0.00000 ! Y_MErrSet - Integrator saturation (maximum signal amplitude contribution to pitch from yaw-by-IPC), [rad] +====== ================= ====================================================================================================================================================================================================== + +====== ================= ====================================================================================================================================================================================================== +Removed in ROSCO develop +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Line Input Name Example Value +====== ================= ====================================================================================================================================================================================================== +96 Y_IPn 1 ! Y_IPC_n - Number of controller gains (yaw-by-IPC) +99 Y_IPC_omegaLP 0.20940 ! Y_IPC_omegaLP - Low-pass filter corner frequency for the Yaw-by-IPC controller to filtering the yaw alignment error, [rad/s]. +100 Y_IPC_zetaLP 1.00000 ! Y_IPC_zetaLP - Low-pass filter damping factor for the Yaw-by-IPC controller to filtering the yaw alignment error, [-]. +102 Y_omegaLPFast 0.20940 ! Y_omegaLPFast - Corner frequency fast low pass filter, 1.0 [rad/s] +103 Y_omegaLPSlow 0.10470 ! Y_omegaLPSlow - Corner frequency slow low pass filter, 1/60 [rad/s] +====== ================= ====================================================================================================================================================================================================== ROSCO v2.4.1 to ROSCO v2.5.0 ------------------------------- Two filter parameters were added to - - change the high pass filter in the floating feedback module - - change the low pass filter of the wind speed estimator signal that is used in torque control Open loop control inputs, users must specify: - - The open loop input filename, an example can be found in Examples/Example_OL_Input.dat - - Indices (columns) of values specified in OL_Filename IPC - Proportional Control capabilities were added, 1P and 2P gains should be specified ====== ================= ====================================================================================================================================================================================================== -Added in ROSCO develop ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Line Flag Name Example Value ====== ================= ====================================================================================================================================================================================================== +20 OL_Mode 0 ! OL_Mode - Open loop control mode {0: no open loop control, 1: open loop control vs. time, 2: open loop control vs. wind speed} 27 F_WECornerFreq 0.20944 ! F_WECornerFreq - Corner frequency (-3dB point) in the first order low pass filter for the wind speed estimate [rad/s]. 29 F_FlHighPassFreq 0.01000 ! F_FlHighPassFreq - Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s]. 50 IPC_KP 0.000000 0.000000 ! IPC_KP - Proportional gain for the individual pitch controller: first parameter for 1P reductions, second for 2P reductions, [-] diff --git a/docs/source/install.rst b/docs/source/install.rst index 3a506b27..29a71620 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -22,7 +22,7 @@ Depending on what is needed, a user can choose to use just the ROSCO controller * - :ref:`full_rosco` - Best for users who wish to both use the controller and leverage the tools in the ROSCO toolbox * - :ref:`cmake_compile` - - Best for users who need to re-compile the source code often, plan to use non-released versions of ROSCO (including modified source code), or who simply want to compile the controller themselves so they have the full code available locally. + - Best for users who need to re-compile the source code often, plan to use non-released versions of ROSCO (including modified source code), or who simply want to compile the controller themselves so they have the full code available locally. This is necessary for users who wish to use the :ref:`zmq_build`. .. _roscotoolbox_table: .. list-table:: Methods for Installing the ROSCO Toolbox @@ -122,6 +122,18 @@ Once the CMake and the required compilers are downloaded, the following code can This will generate a file called :code:`libdiscon.so` (Linux), :code:`libdiscon.dylib` (Mac), or :code:`libdisscon.dll` (Windows) in the :code:`/ROSCO/install/lib` directory. +.. _zmq_build: + +ZeroMQ Interface +..................... +There is an option to interface ROSCO with external inputs using `ZeroMQ `_. Currently, only externally commanded yaw offset inputs are supported, though this could easily be expanded if the need arises. + +To use the ZeroMQ interface, the software must be downloaded following the `ZeroMQ download instructions `_. Using CMake, ROSCO can then be compiled to enable this interface by using the :code:`ZMQ_CLIENT:ON` flag with the :code:`cmake` command in :ref:`cmake_compile`: + +.. code-block:: bash + + cmake -DZMQ_CLIENT:ON .. + .. _rosco_toolbox_install: Installing the ROSCO toolbox diff --git a/docs/source/toolbox_input.rst b/docs/source/toolbox_input.rst index d041f0e3..a3b47729 100644 --- a/docs/source/toolbox_input.rst +++ b/docs/source/toolbox_input.rst @@ -9,7 +9,7 @@ ROSCO_Toolbox tuning .yaml Definition of inputs for ROSCO tuning procedure -/Users/dzalkind/Tools/ROSCO/ROSCO_toolbox/inputs/toolbox_schema. +toolbox_schema. @@ -201,6 +201,15 @@ controller_params *Minimum* = 0 *Maximum* = 1 +:code:`TD_Mode` : Float + Tower damper mode (0- no tower damper, 1- feed back translational + nacelle accelleration to pitch angle + + *Default* = 0 + + *Minimum* = 0 *Maximum* = 1 + + :code:`Fl_Mode` : Float Floating specific feedback mode (0- no nacelle velocity feedback, 1 - nacelle velocity feedback, 2 - nacelle pitching acceleration @@ -230,6 +239,32 @@ controller_params *Minimum* = 0 *Maximum* = 2 +:code:`ZMQ_Mode` : Float + ZMQ Mode (0 - ZMQ Inteface, 1 - ZMQ for yaw control) + + *Default* = 0 + + *Minimum* = 0 *Maximum* = 1 + + +:code:`PA_Mode` : Float + Pitch actuator mode {0 - not used, 1 - first order filter, 2 - + second order filter} + + *Default* = 0 + + *Minimum* = 0 *Maximum* = 2 + + +:code:`Ext_Mode` : Float + External control mode {{0 - not used, 1 - call external dynamic + library}} + + *Default* = 0 + + *Minimum* = 0 *Maximum* = 1 + + :code:`U_pc` : Array of Floats List of wind speeds to schedule pitch control zeta and omega @@ -363,6 +398,13 @@ controller_params *Minimum* = 0 +:code:`max_torque_factor` : Float + Maximum torque = rated torque * max_torque_factor + + *Default* = 1.1 + + *Minimum* = 0 + :code:`IPC_Kp1p` : Float, s Proportional gain for IPC, 1P [s] @@ -391,6 +433,13 @@ controller_params *Minimum* = 0 +:code:`IPC_Vramp` : Array of Floats + wind speeds for IPC cut-in sigma function [m/s] + + *Default* = [0.0, 0.0] + + *Minimum* = 0.0 + filter_params @@ -433,6 +482,13 @@ filter_params *Minimum* = 0 +:code:`f_yawerr` : Float, rad/s + Low pass filter corner frequency for yaw controller [rad/ + + *Default* = 0.17952 + + *Minimum* = 0 + :code:`f_sd_cornerfreq` : Float, rad Cutoff Frequency for first order low-pass filter for blade pitch angle [rad/s], {default = 0.41888 ~ time constant of 15s} @@ -475,6 +531,384 @@ open_loop *Default* = 0 +:code:`PA_CornerFreq` : Float, rad/s + Pitch actuator natural frequency [rad/s] + + *Default* = 3.14 + + *Minimum* = 0 + +:code:`PA_Damping` : Float + Pitch actuator damping ratio [-] + + *Default* = 0.707 + + *Minimum* = 0 + + + +DISCON +######################################## + + +These are pass-through parameters for the DISCON.IN file. Use with caution. + +:code:`LoggingLevel` : Float + (0- write no debug files, 1- write standard output .dbg-file, 2- + write standard output .dbg-file and complete avrSWAP-array + .dbg2-file) + +:code:`F_LPFType` : Float + 1- first-order low-pass filter, 2- second-order low-pass filter + (currently filters generator speed and pitch control signals + +:code:`F_NotchType` : Float + Notch on the measured generator speed and/or tower fore-aft motion + (for floating) (0- disable, 1- generator speed, 2- tower-top fore- + aft motion, 3- generator speed and tower-top fore-aft motion) + +:code:`IPC_ControlMode` : Float + Turn Individual Pitch Control (IPC) for fatigue load reductions + (pitch contribution) (0- off, 1- 1P reductions, 2- 1P+2P + reductions) + +:code:`VS_ControlMode` : Float + Generator torque control mode in above rated conditions (0- + constant torque, 1- constant power, 2- TSR tracking PI control + with constant torque, 3- TSR tracking PI control with constant + power) + +:code:`PC_ControlMode` : Float + Blade pitch control mode (0- No pitch, fix to fine pitch, 1- + active PI blade pitch control) + +:code:`Y_ControlMode` : Float + Yaw control mode (0- no yaw control, 1- yaw rate control, 2- yaw- + by-IPC) + +:code:`SS_Mode` : Float + Setpoint Smoother mode (0- no setpoint smoothing, 1- introduce + setpoint smoothing) + +:code:`WE_Mode` : Float + Wind speed estimator mode (0- One-second low pass filtered hub + height wind speed, 1- Immersion and Invariance Estimator, 2- + Extended Kalman Filter) + +:code:`PS_Mode` : Float + Pitch saturation mode (0- no pitch saturation, 1- implement pitch + saturation) + +:code:`SD_Mode` : Float + Shutdown mode (0- no shutdown procedure, 1- pitch to max pitch at + shutdown) + +:code:`Fl_Mode` : Float + Floating specific feedback mode (0- no nacelle velocity feedback, + 1- feed back translational velocity, 2- feed back rotational + veloicty) + +:code:`Flp_Mode` : Float + Flap control mode (0- no flap control, 1- steady state flap angle, + 2- Proportional flap control) + +:code:`F_LPFCornerFreq` : Float, rad/s + Corner frequency (-3dB point) in the low-pass filters, + +:code:`F_LPFDamping` : Float + Damping coefficient (used only when F_FilterType = 2 [-] + +:code:`F_NotchCornerFreq` : Float, rad/s + Natural frequency of the notch filter, + +:code:`F_NotchBetaNumDen` : Array of Floats + Two notch damping values (numerator and denominator, resp) - + determines the width and depth of the notch, [-] + +:code:`F_SSCornerFreq` : Float, rad/s. + Corner frequency (-3dB point) in the first order low pass filter + for the setpoint smoother, + +:code:`F_WECornerFreq` : Float, rad/s. + Corner frequency (-3dB point) in the first order low pass filter + for the wind speed estimate + +:code:`F_FlCornerFreq` : Array of Floats + Natural frequency and damping in the second order low pass filter + of the tower-top fore-aft motion for floating feedback control + +:code:`F_FlHighPassFreq` : Float, rad/s + Natural frequency of first-order high-pass filter for nacelle + fore-aft motion + +:code:`F_FlpCornerFreq` : Array of Floats + Corner frequency and damping in the second order low pass filter + of the blade root bending moment for flap control + +:code:`PC_GS_n` : Float + Amount of gain-scheduling table entries + +:code:`PC_GS_angles` : Array of Floats + Gain-schedule table- pitch angles + +:code:`PC_GS_KP` : Array of Floats + Gain-schedule table- pitch controller kp gains + +:code:`PC_GS_KI` : Array of Floats + Gain-schedule table- pitch controller ki gains + +:code:`PC_GS_KD` : Array of Floats + Gain-schedule table- pitch controller kd gains + +:code:`PC_GS_TF` : Array of Floats + Gain-schedule table- pitch controller tf gains (derivative filter) + +:code:`PC_MaxPit` : Float, rad + Maximum physical pitch limit, + +:code:`PC_MinPit` : Float, rad + Minimum physical pitch limit, + +:code:`PC_MaxRat` : Float, rad/s. + Maximum pitch rate (in absolute value) in pitch controller + +:code:`PC_MinRat` : Float, rad/s. + Minimum pitch rate (in absolute value) in pitch controller + +:code:`PC_RefSpd` : Float, rad/s. + Desired (reference) HSS speed for pitch controller + +:code:`PC_FinePit` : Float, rad + Record 5- Below-rated pitch angle set-point + +:code:`PC_Switch` : Float, rad + Angle above lowest minimum pitch angle for switch + +:code:`IPC_IntSat` : Float, rad + Integrator saturation (maximum signal amplitude contribution to + pitch from IPC) + +:code:`IPC_KP` : Array of Floats + Proportional gain for the individual pitch controller- first + parameter for 1P reductions, second for 2P reductions, [-] + +:code:`IPC_KI` : Array of Floats + Integral gain for the individual pitch controller- first parameter + for 1P reductions, second for 2P reductions, [-] + +:code:`IPC_aziOffset` : Array of Floats + Phase offset added to the azimuth angle for the individual pitch + controller + +:code:`IPC_CornerFreqAct` : Float, rad/s + Corner frequency of the first-order actuators model, to induce a + phase lag in the IPC signal (0- Disable) + +:code:`VS_GenEff` : Float, percent + Generator efficiency mechanical power -> electrical power, should + match the efficiency defined in the generator properties + +:code:`VS_ArSatTq` : Float, Nm + Above rated generator torque PI control saturation + +:code:`VS_MaxRat` : Float, Nm/s + Maximum torque rate (in absolute value) in torque controller + +:code:`VS_MaxTq` : Float, Nm + Maximum generator torque in Region 3 (HSS side) + +:code:`VS_MinTq` : Float, Nm + Minimum generator torque (HSS side) + +:code:`VS_MinOMSpd` : Float, rad/s + Minimum generator speed + +:code:`VS_Rgn2K` : Float, Nm/(rad/s)^2 + Generator torque constant in Region 2 (HSS side) + +:code:`VS_RtPwr` : Float, W + Wind turbine rated power + +:code:`VS_RtTq` : Float, Nm + Rated torque + +:code:`VS_RefSpd` : Float, rad/s + Rated generator speed + +:code:`VS_n` : Float + Number of generator PI torque controller gains + +:code:`VS_KP` : Float + Proportional gain for generator PI torque controller. (Only used + in the transitional 2.5 region if VS_ControlMode =/ 2) + +:code:`VS_KI` : Float, s + Integral gain for generator PI torque controller (Only used in + the transitional 2.5 region if VS_ControlMode =/ 2) + +:code:`VS_TSRopt` : Float, rad + Power-maximizing region 2 tip-speed-ratio + +:code:`SS_VSGain` : Float + Variable speed torque controller setpoint smoother gain + +:code:`SS_PCGain` : Float + Collective pitch controller setpoint smoother gain + +:code:`WE_BladeRadius` : Float, m + Blade length (distance from hub center to blade tip) + +:code:`WE_CP_n` : Float + Amount of parameters in the Cp array + +:code:`WE_CP` : Array of Floats + Parameters that define the parameterized CP(lambda) function + +:code:`WE_Gamma` : Float, m/rad + Adaption gain of the wind speed estimator algorithm + +:code:`WE_GearboxRatio` : Float + Gearbox ratio, >=1 + +:code:`WE_Jtot` : Float, kg m^2 + Total drivetrain inertia, including blades, hub and casted + generator inertia to LSS + +:code:`WE_RhoAir` : Float, kg m^-3 + Air density + +:code:`PerfFileName` : String + File containing rotor performance tables (Cp,Ct,Cq) (absolute path + or relative to this file) + +:code:`PerfTableSize` : Float + Size of rotor performance tables, first number refers to number of + blade pitch angles, second number referse to number of tip-speed + ratios + +:code:`WE_FOPoles_N` : Float + Number of first-order system poles used in EKF + +:code:`WE_FOPoles_v` : Array of Floats + Wind speeds corresponding to first-order system poles + +:code:`WE_FOPoles` : Array of Floats + First order system poles + +:code:`Y_ErrThresh` : Float, rad^2 s + Yaw error threshold. Turbine begins to yaw when it passes this + +:code:`Y_IPC_IntSat` : Float, rad + Integrator saturation (maximum signal amplitude contribution to + pitch from yaw-by-IPC) + +:code:`Y_IPC_n` : Float + Number of controller gains (yaw-by-IPC) + +:code:`Y_IPC_KP` : Float + Yaw-by-IPC proportional controller gain Kp + +:code:`Y_IPC_KI` : Float + Yaw-by-IPC integral controller gain Ki + +:code:`Y_IPC_omegaLP` : Float, rad/s. + Low-pass filter corner frequency for the Yaw-by-IPC controller to + filtering the yaw alignment error + +:code:`Y_IPC_zetaLP` : Float + Low-pass filter damping factor for the Yaw-by-IPC controller to + filtering the yaw alignment error. + +:code:`Y_MErrSet` : Float, rad + Yaw alignment error, set point + +:code:`Y_omegaLPFast` : Float, rad/s + Corner frequency fast low pass filter, 1.0 + +:code:`Y_omegaLPSlow` : Float, rad/s + Corner frequency slow low pass filter, 1/60 + +:code:`Y_Rate` : Float, rad/s + Yaw rate + +:code:`FA_KI` : Float, rad s/m + Integral gain for the fore-aft tower damper controller, -1 = off / + >0 = on + +:code:`FA_HPFCornerFreq` : Float, rad/s + Corner frequency (-3dB point) in the high-pass filter on the fore- + aft acceleration signal + +:code:`FA_IntSat` : Float, rad + Integrator saturation (maximum signal amplitude contribution to + pitch from FA damper) + +:code:`PS_BldPitchMin_N` : Float + Number of values in minimum blade pitch lookup table (should equal + number of values in PS_WindSpeeds and PS_BldPitchMin) + +:code:`PS_WindSpeeds` : Array of Floats + Wind speeds corresponding to minimum blade pitch angles + +:code:`PS_BldPitchMin` : Array of Floats + Minimum blade pitch angles + +:code:`SD_MaxPit` : Float, rad + Maximum blade pitch angle to initiate shutdown + +:code:`SD_CornerFreq` : Float, rad/s + Cutoff Frequency for first order low-pass filter for blade pitch + angle + +:code:`Fl_Kp` : Float, s + Nacelle velocity proportional feedback gain + +:code:`Flp_Angle` : Float, rad + Initial or steady state flap angle + +:code:`Flp_Kp` : Float, s + Blade root bending moment proportional gain for flap control + +:code:`Flp_Ki` : Float + Flap displacement integral gain for flap control + +:code:`Flp_MaxPit` : Float, rad + Maximum (and minimum) flap pitch angle + +:code:`OL_Filename` : String + Input file with open loop timeseries (absolute path or relative to + this file) + +:code:`Ind_Breakpoint` : Float + The column in OL_Filename that contains the breakpoint (time if + OL_Mode = 1) + +:code:`Ind_BldPitch` : Float + The column in OL_Filename that contains the blade pitch input in + rad + +:code:`Ind_GenTq` : Float + The column in OL_Filename that contains the generator torque in Nm + +:code:`Ind_YawRate` : Float + The column in OL_Filename that contains the generator torque in Nm + +:code:`DLL_FileName` : String + Name/location of the dynamic library {.dll [Windows] or .so + [Linux]} in the Bladed-DLL format + + *Default* = unused + +:code:`DLL_InFile` : String + Name of input file sent to the DLL + + *Default* = unused + +:code:`DLL_ProcName` : String + Name of procedure in DLL to be called + + *Default* = DISCON + linmodel_tuning diff --git a/environment.yml b/environment.yml index 4195d0e3..d90d5165 100644 --- a/environment.yml +++ b/environment.yml @@ -13,3 +13,6 @@ dependencies: - cmake - pyparsing==2.4.7 - control + - pyzmq + - mpi4py + - treon diff --git a/setup.py b/setup.py index aa4a94c3..ee681647 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ EMAIL = 'nikhar.abbas@nrel.gov' AUTHOR = 'NREL, National Wind Technology Center' REQUIRES_PYTHON = '>=3.4' -VERSION = '2.5.0' +VERSION = '2.6.0' # These packages are required for all of the code to be executed. # - Maybe you can get away with older versions...