From 5cdb0b5fbb0fcf2266ad087072b5f1fc1ef8e880 Mon Sep 17 00:00:00 2001 From: dzalkind <65573423+dzalkind@users.noreply.github.com> Date: Fri, 6 May 2022 14:16:09 -0600 Subject: [PATCH] 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> --- .gitignore | 3 + ROSCO/rosco_registry/rosco_types.yaml | 20 +- ROSCO/rosco_registry/write_registry.py | 29 +- ROSCO/src/Controllers.f90 | 38 +- ROSCO/src/Filters.f90 | 2 +- ROSCO/src/ROSCO_IO.f90 | 104 ++-- ROSCO/src/ROSCO_Types.f90 | 5 + ROSCO/src/ReadSetParameters.f90 | 24 + ROSCO_toolbox/controller.py | 6 + ROSCO_toolbox/inputs/toolbox_schema.yaml | 23 + ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py | 445 ++++++++---------- ROSCO_toolbox/ofTools/case_gen/run_FAST.py | 293 ++++++------ ROSCO_toolbox/utilities.py | 15 +- Test_Cases/BAR_10/BAR_10_DISCON.IN | 22 +- .../DISCON-UMaineSemi.IN | 12 +- Test_Cases/NREL-5MW/DISCON.IN | 12 +- Tune_Cases/IEA15MW.yaml | 6 +- Tune_Cases/IEA15MW_FOCAL.yaml | 3 +- docs/source/api_change.rst | 6 + 19 files changed, 600 insertions(+), 468 deletions(-) diff --git a/.gitignore b/.gitignore index 44e74d47..1b4b24f0 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,6 @@ ROSCO_testing/results/ *.autosave *.mat +# OpenFAST outputs +outputs/ + diff --git a/ROSCO/rosco_registry/rosco_types.yaml b/ROSCO/rosco_registry/rosco_types.yaml index 522337ae..88730c31 100644 --- a/ROSCO/rosco_registry/rosco_types.yaml +++ b/ROSCO/rosco_registry/rosco_types.yaml @@ -97,6 +97,9 @@ ControlParameters: 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] @@ -422,6 +425,17 @@ 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] # Calculated PC_RtTq99: @@ -769,6 +783,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]. @@ -1011,4 +1029,4 @@ 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. diff --git a/ROSCO/rosco_registry/write_registry.py b/ROSCO/rosco_registry/write_registry.py index d1957748..8f8deb91 100644 --- a/ROSCO/rosco_registry/write_registry.py +++ b/ROSCO/rosco_registry/write_registry.py @@ -278,25 +278,24 @@ def write_roscoio(yfile): 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") diff --git a/ROSCO/src/Controllers.f90 b/ROSCO/src/Controllers.f90 index 15bf1188..aa92893d 100644 --- a/ROSCO/src/Controllers.f90 +++ b/ROSCO/src/Controllers.f90 @@ -67,7 +67,7 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) 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,13 +99,12 @@ 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) = saturate(LocalVar%PitCom(K), CntrPar%PC_MinPit, CntrPar%PC_MaxPit) ! Saturate the command using the absolute pitch angle limits + 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 diff --git a/ROSCO/src/Filters.f90 b/ROSCO/src/Filters.f90 index 4fb4239b..5123b74c 100644 --- a/ROSCO/src/Filters.f90 +++ b/ROSCO/src/Filters.f90 @@ -310,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/ROSCO_IO.f90 b/ROSCO/src/ROSCO_IO.f90 index 1744a385..79c867a7 100644 --- a/ROSCO/src/ROSCO_IO.f90 +++ b/ROSCO/src/ROSCO_IO.f90 @@ -94,6 +94,9 @@ SUBROUTINE WriteRestartFile(LocalVar, CntrPar, objInst, RootName, size_avcOUTNAM 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 @@ -272,6 +275,9 @@ SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootNa 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 @@ -424,7 +430,7 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME '[m/s]', '[m/s]', '[rad]', '[rad]', '[rad/s]', & '[rad/s]', '[rad/s]', '[m/s]', '[rad]', '[rad]', & '', '', '', ''] - nLocalVars = 72 + nLocalVars = 73 Allocate(LocalVarOutData(nLocalVars)) Allocate(LocalVarOutStrings(nLocalVars)) LocalVarOutData(1) = LocalVar%iStatus @@ -472,33 +478,34 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME LocalVarOutData(43) = LocalVar%IPC_KP(1) LocalVarOutData(44) = LocalVar%PC_State LocalVarOutData(45) = LocalVar%PitCom(1) - LocalVarOutData(46) = LocalVar%SS_DelOmegaF - LocalVarOutData(47) = LocalVar%TestType - LocalVarOutData(48) = LocalVar%VS_MaxTq - LocalVarOutData(49) = LocalVar%VS_LastGenTrq - LocalVarOutData(50) = LocalVar%VS_LastGenPwr - LocalVarOutData(51) = LocalVar%VS_MechGenPwr - LocalVarOutData(52) = LocalVar%VS_SpdErrAr - LocalVarOutData(53) = LocalVar%VS_SpdErrBr - LocalVarOutData(54) = LocalVar%VS_SpdErr - LocalVarOutData(55) = LocalVar%VS_State - LocalVarOutData(56) = LocalVar%VS_Rgn3Pitch - LocalVarOutData(57) = LocalVar%WE_Vw - LocalVarOutData(58) = LocalVar%WE_Vw_F - LocalVarOutData(59) = LocalVar%WE_VwI - LocalVarOutData(60) = LocalVar%WE_VwIdot - LocalVarOutData(61) = LocalVar%VS_LastGenTrqF - LocalVarOutData(62) = LocalVar%Y_AccErr - LocalVarOutData(63) = LocalVar%Y_ErrLPFFast - LocalVarOutData(64) = LocalVar%Y_ErrLPFSlow - LocalVarOutData(65) = LocalVar%Y_MErr - LocalVarOutData(66) = LocalVar%Y_YawEndT - LocalVarOutData(67) = LocalVar%Fl_PitCom - LocalVarOutData(68) = LocalVar%NACIMU_FA_AccF - LocalVarOutData(69) = LocalVar%FA_AccF - LocalVarOutData(70) = LocalVar%Flp_Angle(1) - LocalVarOutData(71) = LocalVar%RootMyb_Last(1) - LocalVarOutData(72) = LocalVar%ACC_INFILE_SIZE + LocalVarOutData(46) = LocalVar%PitComAct(1) + LocalVarOutData(47) = LocalVar%SS_DelOmegaF + LocalVarOutData(48) = LocalVar%TestType + LocalVarOutData(49) = LocalVar%VS_MaxTq + LocalVarOutData(50) = LocalVar%VS_LastGenTrq + LocalVarOutData(51) = LocalVar%VS_LastGenPwr + LocalVarOutData(52) = LocalVar%VS_MechGenPwr + LocalVarOutData(53) = LocalVar%VS_SpdErrAr + LocalVarOutData(54) = LocalVar%VS_SpdErrBr + LocalVarOutData(55) = LocalVar%VS_SpdErr + LocalVarOutData(56) = LocalVar%VS_State + LocalVarOutData(57) = LocalVar%VS_Rgn3Pitch + LocalVarOutData(58) = LocalVar%WE_Vw + LocalVarOutData(59) = LocalVar%WE_Vw_F + LocalVarOutData(60) = LocalVar%WE_VwI + LocalVarOutData(61) = LocalVar%WE_VwIdot + LocalVarOutData(62) = LocalVar%VS_LastGenTrqF + LocalVarOutData(63) = LocalVar%Y_AccErr + LocalVarOutData(64) = LocalVar%Y_ErrLPFFast + LocalVarOutData(65) = LocalVar%Y_ErrLPFSlow + LocalVarOutData(66) = LocalVar%Y_MErr + LocalVarOutData(67) = LocalVar%Y_YawEndT + LocalVarOutData(68) = LocalVar%Fl_PitCom + LocalVarOutData(69) = LocalVar%NACIMU_FA_AccF + LocalVarOutData(70) = LocalVar%FA_AccF + LocalVarOutData(71) = LocalVar%Flp_Angle(1) + LocalVarOutData(72) = LocalVar%RootMyb_Last(1) + LocalVarOutData(73) = 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', & @@ -508,12 +515,12 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME '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', & - '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'] + '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', 'Y_AccErr', 'Y_ErrLPFFast', 'Y_ErrLPFSlow', & + 'Y_MErr', 'Y_YawEndT', '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 @@ -536,25 +543,24 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME 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 ef2fa0b1..c5333f21 100644 --- a/ROSCO/src/ROSCO_Types.f90 +++ b/ROSCO/src/ROSCO_Types.f90 @@ -20,6 +20,7 @@ MODULE ROSCO_Types 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), 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] @@ -112,6 +113,9 @@ 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] 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] @@ -219,6 +223,7 @@ MODULE ROSCO_Types 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]. diff --git a/ROSCO/src/ReadSetParameters.f90 b/ROSCO/src/ReadSetParameters.f90 index d084e6fe..70a6a963 100644 --- a/ROSCO/src/ReadSetParameters.f90 +++ b/ROSCO/src/ReadSetParameters.f90 @@ -251,8 +251,10 @@ 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 ReadEmptyLine(UnControllerParameters,CurLine) @@ -392,6 +394,12 @@ 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) ! Fix Paths (add relative paths if called from another dir) IF (PathIsRelative(CntrPar%PerfFileName)) CntrPar%PerfFileName = TRIM(PriPath)//TRIM(CntrPar%PerfFileName) @@ -972,6 +980,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 diff --git a/ROSCO_toolbox/controller.py b/ROSCO_toolbox/controller.py index b4e70bb1..9795e2ce 100644 --- a/ROSCO_toolbox/controller.py +++ b/ROSCO_toolbox/controller.py @@ -60,6 +60,7 @@ 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'] # Necessary parameters @@ -147,6 +148,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 diff --git a/ROSCO_toolbox/inputs/toolbox_schema.yaml b/ROSCO_toolbox/inputs/toolbox_schema.yaml index cd631643..c9a7d25d 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,12 @@ 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) + 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} U_pc: type: array description: List of wind speeds to schedule pitch control zeta and omega @@ -407,6 +419,17 @@ 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 diff --git a/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py b/ROSCO_toolbox/ofTools/case_gen/CaseLibrary.py index 04746f82..9f1dc8f0 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 @@ -66,21 +66,12 @@ 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 if False: case_inputs[("ElastoDyn","YawDOF")] = {'vals':['True'], 'group':0} @@ -100,9 +91,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} @@ -130,24 +118,31 @@ 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) - # channels = set_channels() - return case_list, case_name_list, channels + case_inputs = base_op_case() + # simulation settings + case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} + + # wind inflow + case_inputs[("InflowWind","WindType")] = {'vals':[1], 'group':0} + case_inputs[("InflowWind","HWindSpeed")] = {'vals':U, 'group':1} + + 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 @@ -176,126 +171,19 @@ 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 +219,85 @@ 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} +def steps(**wind_case_opts): + # Muliple steps in same simulation at time, wind breakpoints, this function adds zero-order hold, 100 seconds to end - # 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} - + 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') - # 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} - - # 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() + + hh_wind.write() + case_inputs = base_op_case() + + case_inputs[("Fst","TMax")] = {'vals':[T_max], 'group':0} - # 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} + + # wind inflow + case_inputs[("InflowWind","WindType")] = {'vals':[2], 'group':0} + case_inputs[("InflowWind","Filename_Uni")] = {'vals':[hh_wind.filename], 'group':0} return case_inputs -# Control sweep functions -# function(controller,turbine,start_group) +############################################################################################## +# +# Control sweep cases +# +############################################################################################## -def sweep_rated_torque(tuning_yaml,start_group): +def sweep_rated_torque(start_group, **control_sweep_opts): # Sweep multiplier of original rated torque multipliers = np.linspace(1,1.5,5) @@ -500,7 +344,124 @@ 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_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 == '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} diff --git a/ROSCO_toolbox/ofTools/case_gen/run_FAST.py b/ROSCO_toolbox/ofTools/case_gen/run_FAST.py index 26cc8a22..4de3ed2c 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,201 @@ # 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 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' + def __init__(self): - 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) + # 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.save_dir = os.path.join(rosco_dir,'outputs') + self.n_cores = 1 - - # 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') + def run_FAST(self): + # set up run directory + if self.control_sweep_fcn: + sweep_name = self.control_sweep_fcn.__name__ 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' + turbine_name = os.path.split(self.tuning_yaml)[-1].split('.')[0] + run_dir = os.path.join(self.save_dir,turbine_name,self.wind_case_fcn.__name__,sweep_name) - - # 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() + # 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'] + + # 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['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 + self.wind_case_opts['run_dir'] = run_dir + case_inputs = self.wind_case_fcn(**self.wind_case_opts) + 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') + else: + rosco_dll = os.path.join(run_dir1, 'local/lib/libdiscon.so') + case_inputs[('ServoDyn','DLL_FileName')] = {'vals': [rosco_dll], 'group': 0} - # Parallel file generation with MPI - if MPI: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - else: - rank = 0 - if rank == 0: + # 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: + sweep_name = 'base' - # 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' + + # Generate cases + case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=run_dir, namebase=turbine_name) + channels = cl.set_channels() + # Management of parallelization, leave in for now if MPI: - fastBatch.run_mpi(comm_map_down) + 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() + + + # Parallel file generation with MPI + if MPI: + 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_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' + + 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 = 7 + + 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 + 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/utilities.py b/ROSCO_toolbox/utilities.py index 4d289147..61e2b1cf 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,8 +77,10 @@ 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} ! 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('\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'])) @@ -163,7 +165,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C 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') @@ -195,6 +197,10 @@ 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.close() # Write Open loop input @@ -375,6 +381,7 @@ 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) # ------- FILTERS ------- @@ -483,6 +490,10 @@ 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 return DISCON_dict diff --git a/Test_Cases/BAR_10/BAR_10_DISCON.IN b/Test_Cases/BAR_10/BAR_10_DISCON.IN index 64db4dc3..0a854e6b 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 04/14/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 04/29/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,14 +16,16 @@ 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 ! Flp_Mode - Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control, 2: Cyclic (1P) flap control} +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 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} !------- 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} [-] -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, [-] +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.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, -]. @@ -101,7 +103,7 @@ 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] @@ -119,8 +121,8 @@ !------- FLAP ACTUATION ----------------------------------------------------- 0.000000000000 ! Flp_Angle - Initial or steady state flap angle [rad] -0.00000000e+00 ! Flp_Kp - Blade root bending moment proportional gain for flap control [s] -0.00000000e+00 ! Flp_Ki - Flap displacement integral gain for flap control [-] +1.46445122e-08 ! Flp_Kp - Blade root bending moment proportional gain for flap control [s] +1.46445122e-09 ! Flp_Ki - Flap displacement integral gain for flap control [-] 0.174500000000 ! Flp_MaxPit - Maximum (and minimum) flap pitch angle [rad] !------- Open Loop Control ----------------------------------------------------- @@ -129,3 +131,7 @@ 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] 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 c96242be..5f30d82d 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 04/14/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 04/29/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,8 +16,10 @@ 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} +2 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} !------- FILTERS ---------------------------------------------------------- 1.00810 ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s] @@ -101,7 +103,7 @@ 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] @@ -129,3 +131,7 @@ 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] diff --git a/Test_Cases/NREL-5MW/DISCON.IN b/Test_Cases/NREL-5MW/DISCON.IN index a344bb32..b469e830 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 04/14/22 +! - File written using ROSCO version 2.5.0 controller tuning logic on 04/29/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,8 +16,10 @@ 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 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} !------- FILTERS ---------------------------------------------------------- 1.57080 ! F_LPFCornerFreq - Corner frequency (-3dB point) in the low-pass filters, [rad/s] @@ -101,7 +103,7 @@ 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] @@ -129,3 +131,7 @@ 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] diff --git a/Tune_Cases/IEA15MW.yaml b/Tune_Cases/IEA15MW.yaml index 673716eb..7eaf1d8d 100644 --- a/Tune_Cases/IEA15MW.yaml +++ b/Tune_Cases/IEA15MW.yaml @@ -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_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/docs/source/api_change.rst b/docs/source/api_change.rst index db482001..cf915d38 100644 --- a/docs/source/api_change.rst +++ b/docs/source/api_change.rst @@ -17,6 +17,8 @@ IPC ====== ================= ====================================================================================================================================================================================================== Line Flag 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} +21 PA_Mode 0 ! PA_Mode - Pitch actuator mode {0 - not used, 1 - first order filter, 2 - second order filter} 49 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] ====== ================= ====================================================================================================================================================================================================== @@ -24,11 +26,15 @@ Line Flag Name Example Value 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