Repository: NREL/ROSCO_toolbox (language: python)

Issue:
Update docs
# Update docs
This adds a user survey to the documentation and updates a few typos


Discussion / hints:

Fix patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..c57c562d
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,19 @@
+## Description and Purpose
+
+## Type of change
+What types of change is it?
+_Select the appropriate type(s) that describe this PR_
+
+- [ ] Bugfix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (non-backwards-compatible fix or feature)
+- [ ] Code style update (formatting, renaming)
+- [ ] Refactoring (no functional changes, no API changes)
+- [ ] Documentation update
+- [ ] Maintenance update
+- [ ] Other (please describe)
+
+## Github issues addressed, if one exists
+
+## Examples/Testing, if applicable
+
diff --git a/.github/workflows/CI_rosco-toolbox.yml b/.github/workflows/CI_rosco-toolbox.yml
new file mode 100644
index 00000000..d4a8780a
--- /dev/null
+++ b/.github/workflows/CI_rosco-toolbox.yml
@@ -0,0 +1,157 @@
+name: CI_rosco-toolbox
+
+# We run CI on push commits on all branches
+on: [push, pull_request]
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  build:
+    name: Build (${{ matrix.os }})
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: ["ubuntu-latest", "macOS-latest", "windows-latest"]
+        python-version: ["3.8"]
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - name: Checkout repository and submodules
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+      
+      - name: Setup environment
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          miniconda-version: "latest"
+          channels: conda-forge, general
+          auto-update-conda: true
+          python-version: 3.8
+          environment-file: environment.yml
+
+
+      # Install dependencies of ROSCO toolbox
+      - name: Add dependencies ubuntu specific
+        if: false == contains( matrix.os, 'windows')
+        run: |
+          conda install -y compilers
+          conda install -y wisdem
+      - name: Add dependencies windows specific
+        if: true == contains( matrix.os, 'windows')
+        run: |
+          conda install -y m2w64-toolchain libpython 
+          conda install -y wisdem
+        
+
+      # Install ROSCO toolbox
+      - name: Install ROSCO toolbox
+        run: |
+          python setup.py develop --compile-rosco
+          
+
+  run_examples:
+    name: Run Examples
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: ["ubuntu-latest"] #, "macOS-latest"]
+        python-version: ["3.8"]
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - name: Checkout repository and submodules
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+      
+      - name: Setup environment
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          miniconda-version: "latest"
+          channels: conda-forge, general
+          auto-update-conda: true
+          python-version: 3.8
+          environment-file: environment.yml
+
+
+      # Install dependencies of ROSCO toolbox
+      - name: Add dependencies ubuntu specific
+        if: false == contains( matrix.os, 'windows')
+        run: |
+          conda install -y compilers
+          conda install -y wisdem
+        
+
+      # Install ROSCO toolbox
+      - name: Install ROSCO toolbox
+        run: |
+          python setup.py develop --compile-rosco
+          
+      # Install OpenFAST
+      - name: Install OpenFAST
+        run: |
+          conda install openfast==3.0.0
+          
+      # Run examples
+      - name: Run examples
+        run: |
+          cd Examples
+          python run_examples.py
+
+  run_testing:
+    name: Run Testing
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: ["ubuntu-latest"] #, "macOS-latest"]
+        python-version: ["3.8"]
+    defaults:
+      run:
+        shell: bash -l {0}
+
+    steps:
+      - name: Checkout repository and submodules
+        uses: actions/checkout@v2
+        with:
+          submodules: recursive
+      
+      - name: Setup environment
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          miniconda-version: "latest"
+          channels: conda-forge, general
+          auto-update-conda: true
+          python-version: 3.8
+          environment-file: environment.yml
+
+
+      # Install dependencies of ROSCO toolbox
+      - name: Add dependencies ubuntu specific
+        if: false == contains( matrix.os, 'windows')
+        run: |
+          conda install -y compilers
+          conda install -y wisdem
+        
+
+      # Install ROSCO toolbox
+      - name: Install ROSCO toolbox
+        run: |
+          python setup.py develop --compile-rosco
+          
+      # Install OpenFAST
+      - name: Install OpenFAST
+        run: |
+          conda install openfast==3.0.0
+
+      # Run Testing
+      - name: Run testing
+        run: |
+          cd ROSCO_testing
+          python ROSCO_testing.py
\ No newline at end of file
diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml
new file mode 100644
index 00000000..096085a1
--- /dev/null
+++ b/.github/workflows/publish_to_pypi.yml
@@ -0,0 +1,27 @@
+name: Upload to PyPi
+
+on:
+  release:
+    types: [released]
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install setuptools wheel twine
+    - name: Build and publish
+      if: startsWith(github.ref, 'refs/tags')
+      env:
+        TWINE_USERNAME: __token__
+        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
+      run: |
+        python setup.py sdist bdist_wheel
+        twine upload dist/*
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index d9da4963..0a63e2c3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
 [submodule "ROSCO"]
 	path = ROSCO
 	url = https://github.com/NREL/ROSCO
-	branch = main
\ No newline at end of file
+	branch = main
diff --git a/Examples/example_07.py b/Examples/example_07.py
index 786c7609..fe8b71d5 100644
--- a/Examples/example_07.py
+++ b/Examples/example_07.py
@@ -45,11 +45,12 @@
 controller.tune_controller(turbine)
 
 # Plot minimum pitch schedule
-plt.plot(controller.v, controller.pitch_op,label='Steady State Operation')
-plt.plot(controller.v, controller.ps_min_bld_pitch, label='Minimum Pitch Schedule')
-plt.legend()
-plt.xlabel('Wind speed (m/s)')
-plt.ylabel('Blade pitch (rad)')
+fig, ax = plt.subplots(1,1)
+ax.plot(controller.v, controller.pitch_op,label='Steady State Operation')
+ax.plot(controller.v, controller.ps_min_bld_pitch, label='Minimum Pitch Schedule')
+ax.legend()
+ax.set_xlabel('Wind speed (m/s)')
+ax.set_ylabel('Blade pitch (rad)')
 
 if False:
   plt.show()
diff --git a/Examples/example_09.py b/Examples/example_09.py
index 2e909ebc..3bc58a8f 100644
--- a/Examples/example_09.py
+++ b/Examples/example_09.py
@@ -20,5 +20,7 @@
 wind_directory = os.path.join(this_dir,'../Test_Cases/Wind/')
 turbsim_infile = '90m_12mps_twr.inp'
 
-run_openfast(wind_directory, fastcall='turbsim', fastfile=turbsim_infile, chdir=False)
+run_openfast(wind_directory, fastcall='turbsim',
+             fastfile=os.path.join(wind_directory, turbsim_infile), chdir=False)
 
+print('test')
diff --git a/Examples/run_examples.py b/Examples/run_examples.py
index ede4b959..7e561568 100644
--- a/Examples/run_examples.py
+++ b/Examples/run_examples.py
@@ -1,5 +1,6 @@
 import os
 import unittest
+import sys
 from time import time
 import importlib
 
@@ -100,4 +101,5 @@ def suite():
 
 
 if __name__ == "__main__":
-    unittest.TextTestRunner().run(suite())
\ No newline at end of file
+    result = unittest.TextTestRunner().run(suite())
+    sys.exit(not result.wasSuccessful())
diff --git a/README.md b/README.md
index c754e562..1783ed47 100644
--- a/README.md
+++ b/README.md
@@ -19,18 +19,22 @@ The NREL Reference OpenSource Controller (ROSCO) provides an open, modular and f
 ## Documentation
 All relevant documentation about the ROSCO toolbox and ROSCO controller can be found at through [ROSCO's readthedocs webpage](https://rosco-toolbox.readthedocs.io/en/latest/). Here, users can find the information on [installing the ROSCO toolbox](https://rosco-toolbox.readthedocs.io/en/latest/source/install.html#installing-the-rosco-toolbox) and [compiling ROSCO](https://rosco-toolbox.readthedocs.io/en/latest/source/install.html#compiling-rosco) for control purposes. Additionally, there is information on the standard workflow and uses cases for the ROSCO toolchain, and more. 
 
+## Survey
+Please help us better understand the ROSCO user-base and how we can improve rosco through this brief survey:
+[ROSCO toolchain survey](https://forms.office.com/Pages/ResponsePage.aspx?id=fp3yoM0oVE-EQniFrufAgGWnC45k8q5Kl90RBkHijqBUN0JTNzBJT1QwMjIzNDhCWDlDTUZPWDdMWC4u)
+
 ## Referencing
 If the ROSCO Toolbox played a role in your research, please cite it. This software can be
 cited as:
 
-   NREL: ROSCO Toolbox. Version 2.2.0, https://github.com/NREL/rosco_toolbox, 2021.
+   NREL: ROSCO Toolbox. Version 2.3.0, https://github.com/NREL/rosco_toolbox, 2021.
 
 For LaTeX users:
 
 ```
 @misc{ROSCO_toolbox_2021,
     author = {NREL},
-    title = {{ROSCO Toolbox. Version 2.2.0}},
+    title = {{ROSCO Toolbox. Version 2.3.0}},
     year = {2021},
     publisher = {GitHub},
     journal = {GitHub repository},
@@ -39,17 +43,15 @@ For LaTeX users:
 ```
 If the ROSCO generic tuning theory and implementation played a roll in your research, please cite the following paper
 ```
-@inproceedings{Abbas_WindTech2019,
-	doi = {10.1088/1742-6596/1452/1/012002},
-	url = {https://doi.org/10.1088%2F1742-6596%2F1452%2F1%2F012002},
-	year = 2020,
-	month = {jan},
-	publisher = {{IOP} Publishing},
-	volume = {1452},
-	pages = {012002},
-	author = {Nikhar J. Abbas and Alan Wright and Lucy Pao},
-	title = {An Update to the National Renewable Energy Laboratory Baseline Wind Turbine Controller},
-	journal = {Journal of Physics: Conference Series}
+@Article{wes-2021-19,
+AUTHOR = {Abbas, N. and Zalkind, D. and Pao, L. and Wright, A.},
+TITLE = {A Reference Open-Source Controller for Fixed and Floating Offshore Wind Turbines},
+JOURNAL = {Wind Energy Science Discussions},
+VOLUME = {2021},
+YEAR = {2021},
+PAGES = {1--33},
+URL = {https://wes.copernicus.org/preprints/wes-2021-19/},
+DOI = {10.5194/wes-2021-19}
 }
 ```
 Additionally, if you have extensively used the [ROSCO](https://github.com/NREL/ROSCO) controller or [WISDEM](https://github.com/wisdem/wisdem), please cite them accordingly. 
diff --git a/ROSCO b/ROSCO
index e53ff37a..7e100154 160000
--- a/ROSCO
+++ b/ROSCO
@@ -1,1 +1,1 @@
-Subproject commit e53ff37a50ced633873a324386f279404953fe52
+Subproject commit 7e100154bb8fad0fa10e7bca826d7ee1b68192b1
diff --git a/ROSCO_toolbox/__init__.py b/ROSCO_toolbox/__init__.py
index 63c85156..5f3415c4 100644
--- a/ROSCO_toolbox/__init__.py
+++ b/ROSCO_toolbox/__init__.py
@@ -3,4 +3,4 @@
 
 __author__ = """Nikhar J. Abbas and Daniel S. Zalkind"""
 __email__ = 'nikhar.abbas@nrel.gov'
-__version__ = '2.2.0'
\ No newline at end of file
+__version__ = '2.3.0'
\ No newline at end of file
diff --git a/ROSCO_toolbox/control_interface.py b/ROSCO_toolbox/control_interface.py
index 4dc6ea49..34de1c6c 100644
--- a/ROSCO_toolbox/control_interface.py
+++ b/ROSCO_toolbox/control_interface.py
@@ -9,7 +9,7 @@
 # CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
-from ctypes import byref, cdll, c_int, POINTER, c_float, c_char_p, c_double, create_string_buffer, c_int32
+from ctypes import byref, cdll, c_int, POINTER, c_float, c_char_p, c_double, create_string_buffer, c_int32, c_void_p
 import numpy as np
 from numpy.ctypeslib import ndpointer
 
@@ -35,19 +35,31 @@ class ControllerInterface():
 
     """
 
-    def __init__(self, lib_name, param_filename='DISCON.IN'):
+    def __init__(self, lib_name, param_filename='DISCON.IN', **kwargs):
         """
         Setup the interface
         """
         self.lib_name = lib_name
         self.param_name = param_filename
 
-        # Temp fixed parameters
+        # Set default parameters
         # PARAMETERS
         self.DT = 0.1
         self.num_blade = 3
         self.char_buffer = 500
         self.avr_size = 500
+        self.sim_name = 'simDEBUG'
+
+        # Set kwargs, like DT
+        for (k, w) in kwargs.items():
+            try:
+                setattr(self, k, w)
+            except:
+                pass
+
+        self.init_discon()
+
+    def init_discon(self):
 
         # Initialize
         self.pitch = 0
@@ -56,10 +68,12 @@ def __init__(self, lib_name, param_filename='DISCON.IN'):
         self.discon = cdll.LoadLibrary(self.lib_name)
         self.avrSWAP = np.zeros(self.avr_size)
 
-        # Define some avrSWAP parameters
+        # Define some avrSWAP parameters, NOTE: avrSWAP indices are offset by -1 from Fortran
         self.avrSWAP[2] = self.DT
         self.avrSWAP[60] = self.num_blade
-        self.avrSWAP[20] = 1 # HARD CODE initial rot speed = 1 rad/s
+        self.avrSWAP[19] = 1.0 # HARD CODE initial gen speed = 1 rad/s
+        self.avrSWAP[20] = 1.0 # HARD CODE initial rot speed = 1 rad/s
+        self.avrSWAP[82] = 0 # HARD CODE initial nacIMU = 0
         self.avrSWAP[26] = 10 # HARD CODE initial wind speed = 10 m/s
 
 
@@ -76,7 +90,7 @@ def __init__(self, lib_name, param_filename='DISCON.IN'):
         self.aviFAIL = c_int32() # 1
         self.accINFILE = self.param_name.encode('utf-8')
         # self.avcOUTNAME = create_string_buffer(1000) # 'DEMO'.encode('utf-8')
-        self.avcOUTNAME = 'simDEBUG.dbg'.encode('utf-8')
+        self.avcOUTNAME = (self.sim_name + '.RO.dbg').encode('utf-8')
         self.avcMSG = create_string_buffer(1000)
         self.discon.DISCON.argtypes = [POINTER(c_float), POINTER(c_int32), c_char_p, c_char_p, c_char_p] # (all defined by ctypes)
 
@@ -103,7 +117,7 @@ def call_discon(self):
         self.avrSWAP = data
 
 
-    def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws):
+    def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws,NacIMU_FA_Acc=0):
         '''
         Runs the controller. Passes current turbine state to the controller, and returns control inputs back
         
@@ -123,6 +137,9 @@ def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws):
                   rotor speed, (rad/s)
         ws: float
             wind speed, (m/s)
+        NacIMU_FA_Acc : float
+            nacelle IMU accel. in the nodding dir. , (m/s**2)
+            default to 0 (fixed-bottom, simple 1-DOF sim does not include it, but OpenFAST linearizations do)
         '''
 
         # Add states to avr
@@ -136,6 +153,7 @@ def call_controller(self,t,dt,pitch,torque,genspeed,geneff,rotspeed,ws):
         self.avrSWAP[19] = genspeed
         self.avrSWAP[20] = rotspeed
         self.avrSWAP[26] = ws
+        self.avrSWAP[82] = NacIMU_FA_Acc
 
         # call controller
         self.call_discon()
@@ -152,3 +170,17 @@ def show_control_values(self):
         '''
         print('Pitch',self.pitch)
         print('Torque',self.torque)
+
+    def kill_discon(self):
+        '''
+        Unload the dylib from memory: https://stackoverflow.com/questions/359498/how-can-i-unload-a-dll-using-ctypes-in-python
+        This is unix-specific, but there seems to be a windows solution as well
+        '''
+
+        print('Shutting down {}'.format(self.lib_name))
+        handle = self.discon._handle
+        dlclose_func = self.discon.dlclose
+        dlclose_func.argtypes = [c_void_p]
+
+        del self.discon
+        dlclose_func(handle)
diff --git a/ROSCO_toolbox/controller.py b/ROSCO_toolbox/controller.py
index b2ec3ae6..7d911759 100644
--- a/ROSCO_toolbox/controller.py
+++ b/ROSCO_toolbox/controller.py
@@ -135,6 +135,24 @@ def __init__(self, controller_params):
             else:
                 self.flp_maxpit = 0.0
 
+        # Gain scheduling/indexing
+        # Number of wind speed breakpoints, default = 60
+        if 'WS_GS_n' in controller_params:
+            self.WS_GS_n = controller_params['WS_GS_n']
+        else:
+            self.WS_GS_n = 60
+
+        # Number of pitch control breakpoints, default = 30
+        if 'PC_GS_n' in controller_params:
+            self.PC_GS_n = controller_params['PC_GS_n']
+        else:
+            self.PC_GS_n = 30
+
+        if self.WS_GS_n <= self.PC_GS_n:
+            raise Exception('Number of WS breakpoints is not greater than pitch control breakpoints')
+
+        
+
     def tune_controller(self, turbine):
         """
         Given a turbine model, tune a controller based on the NREL generic controller tuning process
@@ -158,8 +176,9 @@ def tune_controller(self, turbine):
         TSR_rated = rated_rotor_speed*R/turbine.v_rated  # TSR at rated
 
         # separate wind speeds by operation regions
-        v_below_rated = np.linspace(turbine.v_min,turbine.v_rated, num=30)             # below rated
-        v_above_rated = np.linspace(turbine.v_rated,turbine.v_max, num=30)             # above rated
+        # add one to above rated because we don't use rated in the pitch control gain scheduling
+        v_below_rated = np.linspace(turbine.v_min,turbine.v_rated, num=self.WS_GS_n-self.PC_GS_n)[:-1]             # below rated
+        v_above_rated = np.linspace(turbine.v_rated,turbine.v_max, num=self.PC_GS_n+1)             # above rated
         v = np.concatenate((v_below_rated, v_above_rated))
 
         # separate TSRs by operations regions
@@ -189,7 +208,8 @@ def tune_controller(self, turbine):
             # Find pitch angle as a function of expected operating CP for each TSR
             Cp_TSR = np.ndarray.flatten(turbine.Cp.interp_surface(turbine.pitch_initial_rad, TSR_op[i]))     # all Cp values for a given tsr
             Cp_op[i] = np.clip(Cp_op[i], np.min(Cp_TSR), np.max(Cp_TSR))        # saturate Cp values to be on Cp surface
-            f_cp_pitch = interpolate.interp1d(Cp_TSR,pitch_initial_rad)         # interpolate function for Cp(tsr) values
+            Cp_maxidx = Cp_TSR.argmax()                                                                 # Find maximum Cp value for this TSR
+            f_cp_pitch = interpolate.interp1d(Cp_TSR[Cp_maxidx:],pitch_initial_rad[Cp_maxidx:])         # interpolate function for Cp(tsr) values
             # expected operation blade pitch values
             if v[i] <= turbine.v_rated and isinstance(self.min_pitch, float): # Below rated & defined min_pitch
                 pitch_op[i] = min(self.min_pitch, f_cp_pitch(Cp_op[i]))
@@ -247,7 +267,7 @@ def tune_controller(self, turbine):
 
         # -- Find gain schedule --
         self.pc_gain_schedule = ControllerTypes()
-        self.pc_gain_schedule.second_order_PI(self.zeta_pc, self.omega_pc,A_pc,B_beta[-len(v_above_rated)+1:],linearize=True,v=v_above_rated[1:])
+        self.pc_gain_schedule.second_order_PI(self.zeta_pc, self.omega_pc,A_pc,B_beta[-len(v_above_rated)+1:],linearize=True,v=v_above_rated[1:])        
         self.vs_gain_schedule = ControllerTypes()
         self.vs_gain_schedule.second_order_PI(self.zeta_vs, self.omega_vs,A_vs,B_tau[0:len(v_below_rated)],linearize=False,v=v_below_rated)
 
@@ -479,7 +499,7 @@ def peak_shaving(self,controller, turbine):
             else:
                 Ct_max[i] = np.minimum( np.max(Ct_tsr), Ct_max[i])
             # Define minimum pitch angle
-            f_pitch_min = interpolate.interp1d(Ct_tsr, turbine.pitch_initial_rad, kind='cubic', bounds_error=False, fill_value=(turbine.pitch_initial_rad[0],turbine.pitch_initial_rad[-1]))
+            f_pitch_min = interpolate.interp1d(Ct_tsr, turbine.pitch_initial_rad, kind='linear', bounds_error=False, fill_value=(turbine.pitch_initial_rad[0],turbine.pitch_initial_rad[-1]))
             pitch_min[i] = max(controller.min_pitch, f_pitch_min(Ct_max[i]))
 
         controller.ps_min_bld_pitch = pitch_min
diff --git a/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py b/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py
index 310a8ca2..9297464e 100644
--- a/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py
+++ b/ROSCO_toolbox/ofTools/fast_io/FAST_reader.py
@@ -1018,7 +1018,7 @@ def read_AeroDyn15(self):
         self.fst_vt['AeroDyn15']['WakeMod']       = int(f.readline().split()[0])
         self.fst_vt['AeroDyn15']['AFAeroMod']     = int(f.readline().split()[0])
         self.fst_vt['AeroDyn15']['TwrPotent']     = int(f.readline().split()[0])
-        self.fst_vt['AeroDyn15']['TwrShadow']     = bool_read(f.readline().split()[0])
+        self.fst_vt['AeroDyn15']['TwrShadow'] = int(f.readline().split()[0])
         self.fst_vt['AeroDyn15']['TwrAero']       = bool_read(f.readline().split()[0])
         self.fst_vt['AeroDyn15']['FrozenWake']    = bool_read(f.readline().split()[0])
         if self.FAST_ver.lower() != 'fast8':
@@ -1092,11 +1092,13 @@ def read_AeroDyn15(self):
         self.fst_vt['AeroDyn15']['TwrElev']        = [None]*self.fst_vt['AeroDyn15']['NumTwrNds']
         self.fst_vt['AeroDyn15']['TwrDiam']        = [None]*self.fst_vt['AeroDyn15']['NumTwrNds']
         self.fst_vt['AeroDyn15']['TwrCd']          = [None]*self.fst_vt['AeroDyn15']['NumTwrNds']
+        self.fst_vt['AeroDyn15']['TwrTI']          = [None]*self.fst_vt['AeroDyn15']['NumTwrNds']
         for i in range(self.fst_vt['AeroDyn15']['NumTwrNds']):
             data = [float(val) for val in f.readline().split()]
             self.fst_vt['AeroDyn15']['TwrElev'][i] = data[0] 
             self.fst_vt['AeroDyn15']['TwrDiam'][i] = data[1] 
             self.fst_vt['AeroDyn15']['TwrCd'][i]   = data[2]
+            self.fst_vt['AeroDyn15']['TwrTI'][i]   = data[3]
 
         # Outputs
         f.readline()
@@ -1402,12 +1404,16 @@ def read_ServoDyn(self):
         self.fst_vt['ServoDyn']['YawManRat'] = float_read(f.readline().split()[0])
         self.fst_vt['ServoDyn']['NacYawF']   = float_read(f.readline().split()[0])
 
-        # Tuned Mass Damper (tuned_mass_damper)
+        # Structural Control
         f.readline()
-        self.fst_vt['ServoDyn']['CompNTMD'] = bool_read(f.readline().split()[0])
-        self.fst_vt['ServoDyn']['NTMDfile'] = f.readline().split()[0][1:-1]
-        self.fst_vt['ServoDyn']['CompTTMD'] = bool_read(f.readline().split()[0])
-        self.fst_vt['ServoDyn']['TTMDfile'] = f.readline().split()[0][1:-1]
+        self.fst_vt['ServoDyn']['NumBStC']  = int(f.readline().split()[0])
+        self.fst_vt['ServoDyn']['BStCfiles'] = f.readline().split()[0][1:-1]
+        self.fst_vt['ServoDyn']['NumNStC']  = int(f.readline().split()[0])
+        self.fst_vt['ServoDyn']['NStCfiles'] = f.readline().split()[0][1:-1]
+        self.fst_vt['ServoDyn']['NumTStC'] = int(f.readline().split()[0])
+        self.fst_vt['ServoDyn']['TStCfiles'] = f.readline().split()[0][1:-1]
+        self.fst_vt['ServoDyn']['NumSStC'] = int(f.readline().split()[0])
+        self.fst_vt['ServoDyn']['SStCfiles'] = f.readline().split()[0][1:-1]
 
         # Bladed Interface and Torque-Speed Look-Up Table (bladed_interface)
         f.readline()
@@ -1572,15 +1578,21 @@ def read_HydroDyn(self):
         # FLOATING PLATFORM
         f.readline()
         self.fst_vt['HydroDyn']['PotMod']        = int_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['ExctnMod']      = int_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['RdtnMod']       = int_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['RdtnTMax']      = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['RdtnDT']        = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['NBody']         = int_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['NBodyMod']      = int_read(f.readline().split()[0])
         self.fst_vt['HydroDyn']['PotFile']       = os.path.normpath(os.path.join(os.path.split(hd_file)[0], f.readline().split()[0][1:-1]))
         self.fst_vt['HydroDyn']['WAMITULEN']     = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['PtfmRefxt']     = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['PtfmRefyt']     = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['PtfmRefzt']     = float_read(f.readline().split()[0])
+        self.fst_vt['HydroDyn']['PtfmRefztRot'] = float_read(f.readline().split()[0])
         self.fst_vt['HydroDyn']['PtfmVol0']      = float_read(f.readline().split()[0])
         self.fst_vt['HydroDyn']['PtfmCOBxt']     = float_read(f.readline().split()[0])
         self.fst_vt['HydroDyn']['PtfmCOByt']     = float_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['ExctnMod']      = int_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['RdtnMod']       = int_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['RdtnTMax']      = float_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['RdtnDT']        = float_read(f.readline().split()[0])
 
         # 2ND-ORDER FLOATING PLATFORM FORCES
         f.readline()
@@ -1589,18 +1601,9 @@ def read_HydroDyn(self):
         self.fst_vt['HydroDyn']['DiffQTF']       = int_read(f.readline().split()[0]) # ?
         self.fst_vt['HydroDyn']['SumQTF']        = int_read(f.readline().split()[0]) # ?
 
-        # FLOATING PLATFORM FORCE FLAGS
-        f.readline()
-        self.fst_vt['HydroDyn']['PtfmSgF']       = bool_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['PtfmSwF']       = bool_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['PtfmHvF']       = bool_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['PtfmRF']        = bool_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['PtfmPF']        = bool_read(f.readline().split()[0])
-        self.fst_vt['HydroDyn']['PtfmYF']        = bool_read(f.readline().split()[0])
-
         # PLATFORM ADDITIONAL STIFFNESS AND DAMPING
         f.readline()
-        self.fst_vt['HydroDyn']['AddF0']         = [float(idx) for idx in f.readline().strip().split()[:6]]
+        self.fst_vt['HydroDyn']['AddF0']         = np.array([[float(idx) for idx in f.readline().strip().split()[0]] for i in range(6)])
         self.fst_vt['HydroDyn']['AddCLin']       = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)])
         self.fst_vt['HydroDyn']['AddBLin']       = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)])
         self.fst_vt['HydroDyn']['AddBQuad']      = np.array([[float(idx) for idx in f.readline().strip().split()[:6]] for i in range(6)])
@@ -1666,10 +1669,12 @@ def read_HydroDyn(self):
         self.fst_vt['HydroDyn']['SimplCaMG']   = float(ln[3])
         self.fst_vt['HydroDyn']['SimplCp']     = float(ln[4])
         self.fst_vt['HydroDyn']['SimplCpMG']   = float(ln[5])
-        self.fst_vt['HydroDyn']['SimplAxCa']   = float(ln[6])
-        self.fst_vt['HydroDyn']['SimplAxCaMG'] = float(ln[7])
-        self.fst_vt['HydroDyn']['SimplAxCp']   = float(ln[8])
-        self.fst_vt['HydroDyn']['SimplAxCpMG'] = float(ln[9])
+        self.fst_vt['HydroDyn']['SimplAxCd']   = float(ln[6])
+        self.fst_vt['HydroDyn']['SimplAxCdMG'] = float(ln[7])
+        self.fst_vt['HydroDyn']['SimplAxCa'] = float(ln[8])
+        self.fst_vt['HydroDyn']['SimplAxCaMG'] = float(ln[9])
+        self.fst_vt['HydroDyn']['SimplAxCp'] = float(ln[10])
+        self.fst_vt['HydroDyn']['SimplAxCpMG'] = float(ln[11])
 
         #DEPTH-BASED HYDRODYNAMIC COEFFICIENTS
         f.readline()
diff --git a/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py b/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py
index bbbe6b0f..9aee635f 100644
--- a/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py
+++ b/ROSCO_toolbox/ofTools/fast_io/FAST_writer.py
@@ -885,7 +885,7 @@ def write_AeroDyn15(self):
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['WakeMod'], 'WakeMod', '- Type of wake/induction model (switch) {0=none, 1=BEMT}\n'))
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['AFAeroMod'], 'AFAeroMod', '- Type of blade airfoil aerodynamics model (switch) {1=steady model, 2=Beddoes-Leishman unsteady model} [must be 1 when linearizing]\n'))
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['TwrPotent'], 'TwrPotent', '- Type tower influence on wind based on potential flow around the tower (switch) {0=none, 1=baseline potential flow, 2=potential flow with Bak correction}\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['TwrShadow'], 'TwrShadow', '- Calculate tower influence on wind based on downstream tower shadow? (flag)\n'))
+        f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['TwrShadow'], 'TwrShadow', '- Calculate tower influence on wind based on downstream tower shadow? (flag)\n'))
         f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['TwrAero'], 'TwrAero', '- Calculate tower aerodynamic loads? (flag)\n'))
         f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['FrozenWake'], 'FrozenWake', '- Assume frozen wake during linearization? (flag) [used only when WakeMod=1 and when linearizing]\n'))
         if self.FAST_ver.lower() != 'fast8':
@@ -940,10 +940,10 @@ def write_AeroDyn15(self):
         f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['AeroDyn15']['ADBlFile3']+'"', 'ADBlFile(3)', '- Name of file containing distributed aerodynamic properties for Blade #3 (-) [unused if NumBl < 3]\n'))
         f.write('======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]\n')
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['NumTwrNds'], 'NumTwrNds', '- Number of tower nodes used in the analysis  (-) [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]\n'))
-        f.write('TwrElev        TwrDiam        TwrCd\n')
-        f.write('(m)              (m)           (-)\n')
-        for TwrElev, TwrDiam, TwrCd in zip(self.fst_vt['AeroDyn15']['TwrElev'], self.fst_vt['AeroDyn15']['TwrDiam'], self.fst_vt['AeroDyn15']['TwrCd']):
-            f.write('{: 2.15e} {: 2.15e} {: 2.15e} \n'.format(TwrElev, TwrDiam, TwrCd))
+        f.write('TwrElev        TwrDiam        TwrCd        TwrTI (used only with TwrShadow=2)\n')
+        f.write('(m)              (m)           (-)         (-)\n')
+        for TwrElev, TwrDiam, TwrCd, TwrTI in zip(self.fst_vt['AeroDyn15']['TwrElev'], self.fst_vt['AeroDyn15']['TwrDiam'], self.fst_vt['AeroDyn15']['TwrCd'], self.fst_vt['AeroDyn15']['TwrTI']):
+            f.write('{: 2.15e} {: 2.15e} {: 2.15e} {: 2.15e}\n'.format(TwrElev, TwrDiam, TwrCd, TwrTI))
         f.write('======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]\n')
         f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['SumPrint'], 'SumPrint', '- Generate a summary file listing input options and interpolated properties to "<rootname>.AD.sum"?  (flag)\n'))
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['AeroDyn15']['NBlOuts'], 'NBlOuts', '- Number of blade node outputs [0 - 9] (-)\n'))
@@ -1266,11 +1266,15 @@ def write_ServoDyn(self):
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['TYawManS'], 'TYawManS', '- Time to start override yaw maneuver and end standard yaw control (s)\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['YawManRat'], 'YawManRat', '- Yaw maneuver rate (in absolute value) (deg/s)\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['NacYawF'], 'NacYawF', '- Final yaw angle for override yaw maneuvers (degrees)\n'))
-        f.write('---------------------- TUNED MASS DAMPER ---------------------------------------\n')
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['CompNTMD'], 'CompNTMD', '- Compute nacelle tuned mass damper {true/false} (flag)\n'))
-        f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['NTMDfile']+'"', 'NTMDfile', '- Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['CompTTMD'], 'CompTTMD', '- Compute tower tuned mass damper {true/false} (flag)\n'))
-        f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['TTMDfile']+'"', 'TTMDfile', '- Name of the file for tower tuned mass damper (quoted string) [unused when CompTTMD is false]\n'))
+        f.write('---------------------- STRUCTURAL CONTROL ---------------------------------------\n')
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['NumBStC'], 'NumBStC', '- Number of blade structural controllers (integer)\n'))
+        f.write('{!s:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['BStCfiles']+'"', 'BStCfiles', '- Name of the file for blade tuned mass damper (quoted string) [unused when CompNTMD is false]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['NumNStC'], 'NumNStC', '- Number of nacelle structural controllers (integer)\n'))
+        f.write('{!s:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['NStCfiles']+'"', 'NStCfiles', '- Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['NumTStC'], 'NumTStC', '- Number of tower structural controllers (integer)\n'))
+        f.write('{!s:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['TStCfiles']+'"', 'TStCfiles', '- Name of the file for tower tuned mass damper (quoted string) [unused when CompNTMD is false]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['ServoDyn']['NumSStC'], 'NumSStC', '- Number of sbustructure structural controllers (integer)\n'))
+        f.write('{!s:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['SStCfiles']+'"', 'SStCfiles', '- Name of the file for sbustructure tuned mass damper (quoted string) [unused when CompNTMD is false]\n'))
         f.write('---------------------- BLADED INTERFACE ---------------------------------------- [used only with Bladed Interface]\n')
         f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['DLL_FileName']+'"', 'DLL_FileName', '- Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]\n'))
         f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['ServoDyn']['DLL_InFile']+'"', 'DLL_InFile', '- Name of input file sent to the DLL (-) [used only with Bladed Interface]\n'))
@@ -1470,29 +1474,34 @@ def write_HydroDyn(self):
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['CurrDIDir'], 'CurrDIDir', '- Depth-independent current heading direction    (degrees) [used only when CurrMod=1]\n'))
         f.write('---------------------- FLOATING PLATFORM --------------------------------------- [unused with WaveMod=6]\n')
         f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PotMod'], 'PotMod', '- Potential-flow model {0: none=no potential flow, 1: frequency-to-time-domain transforms based on WAMIT output, 2: fluid-impulse theory (FIT)} (switch)\n'))
+        f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['HydroDyn']['ExctnMod'], 'ExctnMod', '- Wave Excitation model {0: None, 1: DFT, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ssexctn INPUT FILE]\n'))
+        f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnMod'], 'RdtnMod', '- Radiation memory-effect model {0: no memory-effect calculation, 1: convolution, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ss INPUT FILE]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnTMax'], 'RdtnTMax', '- Analysis time for wave radiation kernel calculations (sec) [only used when PotMod=1; determines RdtnDOmega=Pi/RdtnTMax in the cosine transform; MAKE SURE THIS IS LONG ENOUGH FOR THE RADIATION IMPULSE RESPONSE FUNCTIONS TO DECAY TO NEAR-ZERO FOR THE GIVEN PLATFORM!]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnDT'], 'RdtnDT', '- Time step for wave radiation kernel calculations (sec) [only used when PotMod=1; DT<=RdtnDT<=0.1 recommended; determines RdtnOmegaMax=Pi/RdtnDT in the cosine transform]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['NBody'], 'NBody', '- Number of WAMIT bodies to be used (-) [>=1; only used when PotMod=1. If NBodyMod=1, the WAMIT data contains a vector of size 6*NBody x 1 and matrices of size 6*NBody x 6*NBody; if NBodyMod>1, there are NBody sets of WAMIT data each with a vector of size 6 x 1 and matrices of size 6 x 6]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['NBodyMod'], 'NBodyMod', '- Body coupling model {1: include coupling terms between each body and NBody in HydroDyn equals NBODY in WAMIT, 2: neglect coupling terms between each body and NBODY=1 with XBODY=0 in WAMIT, 3: Neglect coupling terms between each body and NBODY=1 with XBODY=/0 in WAMIT} (switch) [only used when PotMod=1]\n'))
         f.write('{:<22} {:<11} {:}'.format('"'+self.fst_vt['HydroDyn']['PotFile']+'"', 'PotFile', '- Root name of potential-flow model data; WAMIT output files containing the linear, nondimensionalized, hydrostatic restoring matrix (.hst), frequency-dependent hydrodynamic added mass matrix and damping matrix (.1), and frequency- and direction-dependent wave excitation force vector per unit wave amplitude (.3) (quoted string) [MAKE SURE THE FREQUENCIES INHERENT IN THESE WAMIT FILES SPAN THE PHYSICALLY-SIGNIFICANT RANGE OF FREQUENCIES FOR THE GIVEN PLATFORM; THEY MUST CONTAIN THE ZERO- AND INFINITE-FREQUENCY LIMITS!]\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['WAMITULEN'], 'WAMITULEN', '- Characteristic body length scale used to redimensionalize WAMIT output (meters) [only used when PotMod=1]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmRefxt'], 'PtfmRefxt', '- The xt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmRefyt'], 'PtfmRefyt', '- The yt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmRefzt'], 'PtfmRefzt', '- The zt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1. If NBodyMod=2,PtfmRefzt=0.0]\n'))
+        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmRefztRot'], 'PtfmRefztRot', '- The rotation about zt of the body reference frame(s) from xt/yt (degrees) [1 to NBody] [only used when PotMod=1]\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmVol0'], 'PtfmVol0', '- Displaced volume of water when the platform is in its undisplaced position (m^3) [only used when PotMod=1; USE THE SAME VALUE COMPUTED BY WAMIT AS OUTPUT IN THE .OUT FILE!]\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmCOBxt'], 'PtfmCOBxt', '- The xt offset of the center of buoyancy (COB) from the platform reference point (meters)  [only used when PotMod=1]\n'))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmCOByt'], 'PtfmCOByt', '- The yt offset of the center of buoyancy (COB) from the platform reference point (meters)  [only used when PotMod=1]\n'))
-        f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['HydroDyn']['ExctnMod'], 'ExctnMod', '- Wave Excitation model {0: None, 1: DFT, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ssexctn INPUT FILE]\n'))
-        f.write('{:<22d} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnMod'], 'RdtnMod', '- Radiation memory-effect model {0: no memory-effect calculation, 1: convolution, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ss INPUT FILE]\n'))
-        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnTMax'], 'RdtnTMax', '- Analysis time for wave radiation kernel calculations (sec) [only used when PotMod=1; determines RdtnDOmega=Pi/RdtnTMax in the cosine transform; MAKE SURE THIS IS LONG ENOUGH FOR THE RADIATION IMPULSE RESPONSE FUNCTIONS TO DECAY TO NEAR-ZERO FOR THE GIVEN PLATFORM!]\n'))
-        f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['RdtnDT'], 'RdtnDT', '- Time step for wave radiation kernel calculations (sec) [only used when PotMod=1; DT<=RdtnDT<=0.1 recommended; determines RdtnOmegaMax=Pi/RdtnDT in the cosine transform]\n'))
         f.write('---------------------- 2ND-ORDER FLOATING PLATFORM FORCES ---------------------- [unused with WaveMod=0 or 6, or PotMod=0 or 2]\n')
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['MnDrift'], 'MnDrift', "- Mean-drift 2nd-order forces computed                                       {0: None; [7, 8, 9, 10, 11, or 12]: WAMIT file to use} [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero]\n"))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['NewmanApp'], 'NewmanApp', "- Mean- and slow-drift 2nd-order forces computed with Newman's approximation {0: None; [7, 8, 9, 10, 11, or 12]: WAMIT file to use} [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero. Used only when WaveDirMod=0]\n"))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['DiffQTF'], 'DiffQTF', "- Full difference-frequency 2nd-order forces computed with full QTF          {0: None; [10, 11, or 12]: WAMIT file to use}          [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero]\n"))
         f.write('{:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['SumQTF'], 'SumQTF', "- Full summation -frequency 2nd-order forces computed with full QTF          {0: None; [10, 11, or 12]: WAMIT file to use}\n"))
-        f.write('---------------------- FLOATING PLATFORM FORCE FLAGS  -------------------------- [unused with WaveMod=6]\n')
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmSgF'], 'PtfmSgF', '- Platform horizontal surge translation force (flag) or DEFAULT\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmSwF'], 'PtfmSwF', '- Platform horizontal sway translation force (flag) or DEFAULT\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmHvF'], 'PtfmHvF', '- Platform vertical heave translation force (flag) or DEFAULT\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmRF'], 'PtfmRF', '- Platform roll tilt rotation force (flag) or DEFAULT\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmPF'], 'PtfmPF', '- Platform pitch tilt rotation force (flag) or DEFAULT\n'))
-        f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['HydroDyn']['PtfmYF'], 'PtfmYF', '- Platform yaw rotation force (flag) or DEFAULT\n'))
         f.write('---------------------- PLATFORM ADDITIONAL STIFFNESS AND DAMPING  --------------\n')
-        f.write(" ".join(['{:14}'.format(i) for i in self.fst_vt['HydroDyn']['AddF0']])+"   AddF0    - Additional preload (N, N-m)\n")
+        for j in range(6):
+            ln = '{:14}   '.format(self.fst_vt['HydroDyn']['AddF0'][j][0])
+            if j == 0:
+                ln = ln + 'AddF0    - Additional preload (N, N-m) [If NBodyMod=1, one size 6*NBody x 1 vector; if NBodyMod>1, NBody size 6 x 1 vectors]\n'
+            else:
+                ln = ln + '\n'
+            f.write(ln)
         for j in range(6):
             try:
                 ln = " ".join(['{:14}'.format(i) for i in self.fst_vt['HydroDyn']['AddCLin'][j,:]])
@@ -1567,6 +1576,8 @@ def write_HydroDyn(self):
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplCaMG']))
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplCp']))
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplCpMG']))
+        ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplAxCd']))
+        ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplAxCdMG']))
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplAxCa']))
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplAxCaMG']))
         ln.append('{:^11}'.format(self.fst_vt['HydroDyn']['SimplAxCp']))
diff --git a/ROSCO_toolbox/ofTools/util/spectral.py b/ROSCO_toolbox/ofTools/util/spectral.py
index e5af299d..307d4f7c 100644
--- a/ROSCO_toolbox/ofTools/util/spectral.py
+++ b/ROSCO_toolbox/ofTools/util/spectral.py
@@ -573,7 +573,7 @@ def fnextpow2(x):
         if nperseg is None:
             if noverlap is None:
                 overlap_frac=0.5
-            elif noverlap is 0:
+            elif noverlap == 0:
                 overlap_frac=0
             else:
                 raise NotImplementedError('TODO noverlap set but not nperseg')
diff --git a/ROSCO_toolbox/turbine.py b/ROSCO_toolbox/turbine.py
index 34721c46..739553a9 100644
--- a/ROSCO_toolbox/turbine.py
+++ b/ROSCO_toolbox/turbine.py
@@ -20,6 +20,16 @@
 
 from ROSCO_toolbox.utilities import load_from_txt
 
+# Load OpenFAST readers
+try:
+    import weis.aeroelasticse
+    use_weis = True
+    print('Using weis.aeroelasticse in ROSCO_toolbox...')
+except:
+    use_weis = False
+    print('ofTools in ROSCO_toolbox...')
+
+
 # Some useful constants
 now = datetime.datetime.now()
 pi = np.pi
@@ -159,7 +169,10 @@ def load_from_fast(self, FAST_InputFile,FAST_directory, FAST_ver='OpenFAST',dev_
             txt_filename: str, optional
                           filename for *.txt, only used if rot_source='txt'
         """
-        from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST
+        if use_weis:
+            from weis.aeroelasticse.FAST_reader import InputReader_OpenFAST
+        else:
+            from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST
 
         print('Loading FAST model: %s ' % FAST_InputFile)
         self.TurbineName = FAST_InputFile.strip('.fst')
@@ -316,8 +329,12 @@ def generate_rotperf_fast(self, openfast_path, FAST_runDirectory=None, run_BeamD
             'serial' - run in serial, 'multi' - run using python multiprocessing tools, 
             'mpi' - run using mpi tools
         '''
-        from ROSCO_toolbox.ofTools.case_gen import runFAST_pywrapper, CaseGen_General
-        from ROSCO_toolbox.ofTools.util import FileTools
+        if use_weis:
+            from weis.aeroelasticse import runFAST_pywrapper, CaseGen_General
+            from weis.aeroelasticse.Util import FileTools
+        else:
+            from ROSCO_toolbox.ofTools.case_gen import runFAST_pywrapper, CaseGen_General
+            from ROSCO_toolbox.ofTools.util import FileTools
         # Load pCrunch tools
         from pCrunch import pdTools, Processing
 
@@ -499,7 +516,10 @@ def load_blade_info(self):
         -----------
             self - note: needs to contain fast input file info provided by load_from_fast.
         '''
-        from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST
+        if use_weis:
+            from weis.aeroelasticse.FAST_reader import InputReader_OpenFAST
+        else:
+            from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST
         from wisdem.ccblade.ccblade import CCAirfoil, CCBlade
 
         # Create CC-Blade Rotor
@@ -587,7 +607,14 @@ def __init__(self,performance_table, pitch_initial_rad, TSR_initial):
         # --- Find TSR ---
         # Make finer mesh for Tip speed ratios at "optimal" blade pitch angle, do a simple lookup. 
         #       -- nja: this seems to work a little better than interpolating
-        performance_beta_max = np.ndarray.flatten(performance_table[:,self.max_ind[1]]) # performance metric at maximizing pitch angle
+
+        # Find the 1D performance table when pitch is at the maximum part of the Cx surface:
+        performance_beta_max = np.ndarray.flatten(performance_table[:,self.max_ind[1][-1]]) # performance metric at the last maximizing pitch angle
+        
+        # If there is more than one max pitch angle:
+        if len(self.max_ind[1]) > 1:
+            print('ROSCO_toolbox Warning: repeated maximum values in a performance table and the last one @ pitch = {} rad. was taken...'.format(self.pitch_opt[-1]))
+
         TSR_ind = np.arange(0,len(TSR_initial))
         TSR_fine_ind = np.linspace(TSR_initial[0],TSR_initial[-1],int(TSR_initial[-1] - TSR_initial[0])*100)
         f_TSR = interpolate.interp1d(TSR_initial,TSR_initial,bounds_error='False',kind='quadratic')    # interpolate function for Cp(tsr) values
diff --git a/ROSCO_toolbox/utilities.py b/ROSCO_toolbox/utilities.py
index 8c210c0d..c39f0ff5 100644
--- a/ROSCO_toolbox/utilities.py
+++ b/ROSCO_toolbox/utilities.py
@@ -28,9 +28,9 @@
 from matplotlib import transforms
 from itertools import takewhile, product
 import struct
+import subprocess
 import ROSCO_toolbox
 
-from ROSCO_toolbox.ofTools.util import spectral
 # Some useful constants
 now = datetime.datetime.now()
 pi = np.pi
@@ -131,7 +131,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C
     file.write('!------- WIND SPEED ESTIMATOR ---------------------------------------------\n')
     file.write('{:<13.3f}       ! WE_BladeRadius	- Blade length (distance from hub center to blade tip), [m]\n'.format(turbine.rotor_radius))
     file.write('{:<11d}         ! WE_CP_n			- Amount of parameters in the Cp array\n'.format(1))
-    file.write(          '{}    ! WE_CP - Parameters that define the parameterized CP(lambda) function\n'.format(''.join('{:<2.1f} '.format(0.0) for i in range(4))))
+    file.write('{}              ! WE_CP - Parameters that define the parameterized CP(lambda) function\n'.format(''.join('{:<2.1f} '.format(0.0) for i in range(1))))
     file.write('{:<13.1f}		! WE_Gamma			- Adaption gain of the wind speed estimator algorithm [m/rad]\n'.format(0.0))
     file.write('{:<13.1f}       ! WE_GearboxRatio	- Gearbox ratio [>=1],  [-]\n'.format(turbine.Ng))
     file.write('{:<014.5f}      ! WE_Jtot			- Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2]\n'.format(turbine.J))
@@ -157,7 +157,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C
     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(-1))
-    file.write('{:<13.1f}       ! FA_HPF_CornerFreq	- Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s]\n'.format(0.0))
+    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(0.0))
     file.write('{:<13.1f}       ! FA_IntSat			- Integrator saturation (maximum signal amplitude contribution to pitch from FA damper), [rad]\n'.format(0.0))
     file.write('\n')
     file.write('!------- MINIMUM PITCH SATURATION -------------------------------------------\n')
@@ -455,7 +455,7 @@ def DISCON_dict(turbine, controller, txt_filename=None):
     return DISCON_dict
 
 
-def run_openfast(fast_dir,fastcall='openfast',fastfile=None,chdir=False):
+def run_openfast(fast_dir, fastcall='openfast', fastfile=None, chdir=True):
     '''
     Runs a openfast openfast simulation.
     
@@ -483,18 +483,11 @@ def run_openfast(fast_dir,fastcall='openfast',fastfile=None,chdir=False):
     print('Using {} to run OpenFAST simulation'.format(fastfile))
 
     if chdir: # Change cwd before calling OpenFAST -- note: This is an artifact of needing to call OpenFAST from the same directory as DISCON.IN
-        # save starting file path 
-        original_path = os.getcwd()
-        # change path, run OpenFAST
-        os.chdir(fast_dir)
-        print('Running OpenFAST simulation for {} through the ROSCO toolbox...'.format(fastfile))
-        os.system('{} {}'.format(fastcall, os.path.join(fastfile)))
-        print('OpenFAST simulation complete. ')
-        # return to original path
-        os.chdir(original_path)
+        cwd = fast_dir
     else:
-        # Run OpenFAST
-        print('Running OpenFAST simulation for {} through the ROSCO toolbox...'.format(fastfile))
-        os.system('{} {}'.format(fastcall, os.path.join(fast_dir,fastfile)))
-        print('OpenFAST simulation complete. ')
+        cwd = None
 
+    print('Running OpenFAST simulation for {} through the ROSCO toolbox...'.format(fastfile))
+        # os.system('{} {}'.format(fastcall, os.path.join(fastfile)))
+    subprocess.run([fastcall, os.path.join(fastfile)], check=True, cwd=cwd)
+    print('OpenFAST simulation complete.')
diff --git a/Test_Cases/5MW_Land_Simulink/NRELOffshrBsline5MW_Onshore_ServoDyn.dat b/Test_Cases/5MW_Land_Simulink/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
index 0da37026..8599775b 100644
--- a/Test_Cases/5MW_Land_Simulink/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
+++ b/Test_Cases/5MW_Land_Simulink/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
@@ -57,11 +57,15 @@ True          GenTiStp     - Method to stop the generator {T: timed using TimGen
      9999.9   TYawManS     - Time to start override yaw maneuver and end standard yaw control (s)
           2   YawManRat    - Yaw maneuver rate (in absolute value) (deg/s)
           0   NacYawF      - Final yaw angle for override yaw maneuvers (degrees)
----------------------- TUNED MASS DAMPER ---------------------------------------
-False         CompNTMD     - Compute nacelle tuned mass damper {true/false} (flag)
-"../5MW_Baseline/NRELOffshrBsline5MW_ServoDyn_TMD.dat"    NTMDfile     - Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]
-False         CompTTMD     - Compute tower tuned mass damper {true/false} (flag)
-"../5MW_Baseline/NRELOffshrBsline5MW_ServoDyn_TMD.dat"    TTMDfile     - Name of the file for tower tuned mass damper (quoted string) [unused when CompTTMD is false]
+---------------------- STRUCTURAL CONTROL --------------------------------------
+0             NumBStC      - Number of blade structural controllers (integer)
+"unused"      BStCfiles    - Name of the files for blade structural controllers (quoted strings) [unused when NumBStC==0]
+0             NumNStC      - Number of nacelle structural controllers (integer)
+"unused"      NStCfiles    - Name of the files for nacelle structural controllers (quoted strings) [unused when NumNStC==0]
+0             NumTStC      - Number of tower structural controllers (integer)
+"unused"      TStCfiles    - Name of the files for tower structural controllers (quoted strings) [unused when NumTStC==0]
+0             NumSStC      - Number of substructure structural controllers (integer)
+"unused"      SStCfiles    - Name of the files for substructure structural controllers (quoted strings) [unused when NumSStC==0]
 ---------------------- BLADED INTERFACE ---------------------------------------- [used only with Bladed Interface]
 "../../ROSCO/build/libdiscon.dylib"    DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
 "DISCON.IN"    DLL_InFile   - Name of input file sent to the DLL (-) [used only with Bladed Interface]
diff --git a/Test_Cases/BAR_10/BAR_10_AeroDyn15.dat b/Test_Cases/BAR_10/BAR_10_AeroDyn15.dat
index d0bf6829..ed92372d 100644
--- a/Test_Cases/BAR_10/BAR_10_AeroDyn15.dat
+++ b/Test_Cases/BAR_10/BAR_10_AeroDyn15.dat
@@ -6,7 +6,7 @@ False                  Echo        - Echo the input to "<rootname>.AD.ech"?  (fl
 1                      WakeMod     - Type of wake/induction model (switch) {0=none, 1=BEMT}
 2                      AFAeroMod   - Type of blade airfoil aerodynamics model (switch) {1=steady model, 2=Beddoes-Leishman unsteady model} [must be 1 when linearizing]
 1                      TwrPotent   - Type tower influence on wind based on potential flow around the tower (switch) {0=none, 1=baseline potential flow, 2=potential flow with Bak correction}
-True                   TwrShadow   - Calculate tower influence on wind based on downstream tower shadow? (flag)
+0                   TwrShadow   - Calculate tower influence on wind based on downstream tower shadow? (flag)
 True                   TwrAero     - Calculate tower aerodynamic loads? (flag)
 False                  FrozenWake  - Assume frozen wake during linearization? (flag) [used only when WakeMod=1 and when linearizing]
 False                  CavitCheck  - Perform cavitation check? (flag) TRUE will turn off unsteady aerodynamics
@@ -82,17 +82,17 @@ True                   UseBlCm     - Include aerodynamic pitching moment in calc
 "BAR_10_AeroDyn15_blade.dat" ADBlFile(3) - Name of file containing distributed aerodynamic properties for Blade #3 (-) [unused if NumBl < 3]
 ======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
 9                      NumTwrNds   - Number of tower nodes used in the analysis  (-) [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
-TwrElev        TwrDiam        TwrCd
-(m)              (m)           (-)
- 2.740000000000000e+01  6.000000000000000e+00  1.000000000000000e+00 
- 4.110000000000000e+01  6.000000000000000e+00  1.000000000000000e+00 
- 5.480000000000000e+01  6.000000000000000e+00  1.000000000000000e+00 
- 6.850000000000000e+01  6.000000000000000e+00  1.000000000000000e+00 
- 8.220000000000000e+01  6.000000000000000e+00  1.000000000000000e+00 
- 9.590000000000001e+01  6.000000000000000e+00  1.000000000000000e+00 
- 1.096000000000000e+02  6.000000000000000e+00  1.000000000000000e+00 
- 1.233000000000000e+02  5.890792130000000e+00  1.000000000000000e+00 
- 1.370000000000000e+02  5.530325920000000e+00  1.000000000000000e+00 
+TwrElev                 TwrDiam                TwrCd                   TwrTI (used only with TwrShadow=2)
+(m)                     (m)                    (-)                     (-)
+ 2.740000000000000e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 4.110000000000000e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 5.480000000000000e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 6.850000000000000e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 8.220000000000000e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 9.590000000000001e+01  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 1.096000000000000e+02  6.000000000000000e+00  1.000000000000000e+00   1.0e-1
+ 1.233000000000000e+02  5.890792130000000e+00  1.000000000000000e+00   1.0e-1
+ 1.370000000000000e+02  5.530325920000000e+00  1.000000000000000e+00   1.0e-1
 ======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
 True                   SumPrint    - Generate a summary file listing input options and interpolated properties to "<rootname>.AD.sum"?  (flag)
 9                      NBlOuts     - Number of blade node outputs [0 - 9] (-)
diff --git a/Test_Cases/BAR_10/BAR_10_ServoDyn.dat b/Test_Cases/BAR_10/BAR_10_ServoDyn.dat
index a429c67d..ebbbc9bf 100644
--- a/Test_Cases/BAR_10/BAR_10_ServoDyn.dat
+++ b/Test_Cases/BAR_10/BAR_10_ServoDyn.dat
@@ -57,11 +57,15 @@ True                   GenTiStp    - Method to stop the generator {T: timed usin
 9999.9                 TYawManS    - Time to start override yaw maneuver and end standard yaw control (s)
 2.0                    YawManRat   - Yaw maneuver rate (in absolute value) (deg/s)
 0.0                    NacYawF     - Final yaw angle for override yaw maneuvers (degrees)
----------------------- TUNED MASS DAMPER ---------------------------------------
-False                  CompNTMD    - Compute nacelle tuned mass damper {true/false} (flag)
-"a.dat"                NTMDfile    - Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]
-False                  CompTTMD    - Compute tower tuned mass damper {true/false} (flag)
-"b.dat"                TTMDfile    - Name of the file for tower tuned mass damper (quoted string) [unused when CompTTMD is false]
+---------------------- STRUCTURAL CONTROL --------------------------------------
+0             NumBStC      - Number of blade structural controllers (integer)
+"unused"      BStCfiles    - Name of the files for blade structural controllers (quoted strings) [unused when NumBStC==0]
+0             NumNStC      - Number of nacelle structural controllers (integer)
+"unused"      NStCfiles    - Name of the files for nacelle structural controllers (quoted strings) [unused when NumNStC==0]
+0             NumTStC      - Number of tower structural controllers (integer)
+"unused"      TStCfiles    - Name of the files for tower structural controllers (quoted strings) [unused when NumTStC==0]
+0             NumSStC      - Number of substructure structural controllers (integer)
+"unused"      SStCfiles    - Name of the files for substructure structural controllers (quoted strings) [unused when NumSStC==0]
 ---------------------- BLADED INTERFACE ---------------------------------------- [used only with Bladed Interface]
 "../../ROSCO/install/lib/libdiscon.dylib" DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
 "BAR_10_DISCON.IN"     DLL_InFile  - Name of input file sent to the DLL (-) [used only with Bladed Interface]
diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_HydroDyn.dat b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_HydroDyn.dat
index cd3a3fa7..fa88672b 100644
--- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_HydroDyn.dat
+++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_HydroDyn.dat
@@ -45,29 +45,33 @@ False            Echo           - Echo the input file data (flag)
            0     CurrDIDir      - Depth-independent current heading direction    (degrees) [used only when CurrMod=1] 
 ---------------------- FLOATING PLATFORM --------------------------------------- [unused with WaveMod=6] 
            1     PotMod         - Potential-flow model {0: none=no potential flow, 1: frequency-to-time-domain transforms based on WAMIT output, 2: fluid-impulse theory (FIT)} (switch) 
+           1     ExctnMod       - Wave-excitation model {0: no wave-excitation calculation, 1: DFT, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ssexctn INPUT FILE]
+           1     RdtnMod        - Radiation memory-effect model {0: no memory-effect calculation, 1: convolution, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ss INPUT FILE] 
+          60     RdtnTMax       - Analysis time for wave radiation kernel calculations (sec) [only used when PotMod=1; determines RdtnDOmega=Pi/RdtnTMax in the cosine transform; MAKE SURE THIS IS LONG ENOUGH FOR THE RADIATION IMPULSE RESPONSE FUNCTIONS TO DECAY TO NEAR-ZERO FOR THE GIVEN PLATFORM!] 
+    "DEFAULT"    RdtnDT         - Time step for wave radiation kernel calculations (sec) [only used when PotMod=1; DT<=RdtnDT<=0.1 recommended; determines RdtnOmegaMax=Pi/RdtnDT in the cosine transform] 
+            1    NBody          - Number of WAMIT bodies to be used (-) [>=1; only used when PotMod=1. If NBodyMod=1, the WAMIT data contains a vector of size 6*NBody x 1 and matrices of size 6*NBody x 6*NBody; if NBodyMod>1, there are NBody sets of WAMIT data each with a vector of size 6 x 1 and matrices of size 6 x 6]
+            1    NBodyMod       - Body coupling model {1: include coupling terms between each body and NBody in HydroDyn equals NBODY in WAMIT, 2: neglect coupling terms between each body and NBODY=1 with XBODY=0 in WAMIT, 3: Neglect coupling terms between each body and NBODY=1 with XBODY=/0 in WAMIT} (switch) [only used when PotMod=1]
 "HydroData/IEA-15-240-RWT-UMaineSemi"         PotFile        - Root name of potential-flow model data; WAMIT output files containing the linear, nondimensionalized, hydrostatic restoring matrix (.hst), frequency-dependent hydrodynamic added mass matrix and damping matrix (.1), and frequency- and direction-dependent wave excitation force vector per unit wave amplitude (.3) (quoted string) [MAKE SURE THE FREQUENCIES INHERENT IN THESE WAMIT FILES SPAN THE PHYSICALLY-SIGNIFICANT RANGE OF FREQUENCIES FOR THE GIVEN PLATFORM; THEY MUST CONTAIN THE ZERO- AND INFINITE-FREQUENCY LIMITS!] 
            1     WAMITULEN      - Characteristic body length scale used to redimensionalize WAMIT output (meters) [only used when PotMod=1] 
+           0.0   PtfmRefxt      - The xt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1]
+           0.0   PtfmRefyt      - The yt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1]
+           0.0   PtfmRefzt      - The zt offset of the body reference point(s) from (0,0,0) (meters) [1 to NBody] [only used when PotMod=1. If NBodyMod=2,PtfmRefzt=0.0]
+           0.0   PtfmRefztRot   - The rotation about zt of the body reference frame(s) from xt/yt (degrees) [1 to NBody] [only used when PotMod=1]
    20206.34889   PtfmVol0       - Displaced volume of water when the platform is in its undisplaced position (m^3) [only used when PotMod=1; USE THE SAME VALUE COMPUTED BY WAMIT AS OUTPUT IN THE .OUT FILE!] 
            0     PtfmCOBxt      - The xt offset of the center of buoyancy (COB) from the platform reference point (meters)  [only used when PotMod=1] 
            0     PtfmCOByt      - The yt offset of the center of buoyancy (COB) from the platform reference point (meters)  [only used when PotMod=1]
-           0     ExctnMod       - Wave Excitation model {0: None, 1: DFT, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ssexctn INPUT FILE]
-           1     RdtnMod        - Radiation memory-effect model {0: no memory-effect calculation, 1: convolution, 2: state-space} (switch) [only used when PotMod=1; STATE-SPACE REQUIRES *.ss INPUT FILE] 
-          60     RdtnTMax       - Analysis time for wave radiation kernel calculations (sec) [only used when PotMod=1; determines RdtnDOmega=Pi/RdtnTMax in the cosine transform; MAKE SURE THIS IS LONG ENOUGH FOR THE RADIATION IMPULSE RESPONSE FUNCTIONS TO DECAY TO NEAR-ZERO FOR THE GIVEN PLATFORM!] 
-    "DEFAULT"    RdtnDT         - Time step for wave radiation kernel calculations (sec) [only used when PotMod=1; DT<=RdtnDT<=0.1 recommended; determines RdtnOmegaMax=Pi/RdtnDT in the cosine transform] 
 ---------------------- 2ND-ORDER FLOATING PLATFORM FORCES ---------------------- [unused with WaveMod=0 or 6, or PotMod=0 or 2] 
            0     MnDrift        - Mean-drift 2nd-order forces computed                                       {0: None; [7, 8, 9, 10, 11, or 12]: WAMIT file to use} [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero] 
            0     NewmanApp      - Mean- and slow-drift 2nd-order forces computed with Newman's approximation {0: None; [7, 8, 9, 10, 11, or 12]: WAMIT file to use} [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero. Used only when WaveDirMod=0] 
 		      12     DiffQTF        - Full difference-frequency 2nd-order forces computed with full QTF          {0: None; [10, 11, or 12]: WAMIT file to use}          [Only one of MnDrift, NewmanApp, or DiffQTF can be non-zero]  
            0     SumQTF         - Full summation -frequency 2nd-order forces computed with full QTF          {0: None; [10, 11, or 12]: WAMIT file to use} 
----------------------- FLOATING PLATFORM FORCE FLAGS  -------------------------- [unused with WaveMod=6] 
-True             PtfmSgF        - Platform horizontal surge translation force (flag) or DEFAULT 
-True             PtfmSwF        - Platform horizontal sway translation force (flag) or DEFAULT 
-True             PtfmHvF        - Platform vertical heave translation force (flag) or DEFAULT 
-True             PtfmRF         - Platform roll tilt rotation force (flag) or DEFAULT 
-True             PtfmPF         - Platform pitch tilt rotation force (flag) or DEFAULT 
-True             PtfmYF         - Platform yaw rotation force (flag) or DEFAULT 
 ---------------------- PLATFORM ADDITIONAL STIFFNESS AND DAMPING  -------------- 
-             0             0             0             0             0             0   AddF0    - Additional preload (N, N-m) 
+             0  AddF0    - Additional preload (N, N-m) [If NBodyMod=1, one size 6*NBody x 1 vector; if NBodyMod>1, NBody size 6 x 1 vectors]
+             0
+             0
+             0
+             0
+             0
              0             0             0             0             0             0   AddCLin  - Additional linear stiffness (N/m, N/rad, N-m/m, N-m/rad) 
              0             0             0             0             0             0 
              0             0             0             0             0             0 
@@ -102,10 +106,10 @@ JointID   Jointxi     Jointyi     Jointzi  JointAxID   JointOvrlp   [JointOvrlp=
 PropSetID    PropD         PropThck                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
    (-)        (m)            (m)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
     1          .00001        0.000001                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
----------------------- SIMPLE HYDRODYNAMIC COEFFICIENTS (model 1) --------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
-     SimplCd    SimplCdMG    SimplCa    SimplCaMG    SimplCp    SimplCpMG   SimplAxCa  SimplAxCaMG  SimplAxCp   SimplAxCpMG                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
-       (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
-       0        0.00        0.00        0.00        0.00        0.00        0.00        0.00        0.00        0.00                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+---------------------- SIMPLE HYDRODYNAMIC COEFFICIENTS (model 1) --------------
+     SimplCd    SimplCdMG    SimplCa    SimplCaMG    SimplCp    SimplCpMG   SimplAxCd  SimplAxCdMG  SimplAxCa  SimplAxCaMG  SimplAxCp   SimplAxCpMG
+       (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)         (-)
+       1.00        1.00        1.00        1.00        1.00        1.00        1.00        0.00        0.00        1.00        1.00        1.00                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
 ---------------------- DEPTH-BASED HYDRODYNAMIC COEFFICIENTS (model 2) ---------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
              0   NCoefDpth       - Number of depth-dependent coefficients (-)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
 Dpth      DpthCd   DpthCdMG   DpthCa   DpthCaMG       DpthCp   DpthCpMG   DpthAxCa   DpthAxCaMG       DpthAxCp   DpthAxCpMG                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ServoDyn.dat b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ServoDyn.dat
index c86a078d..2bde0d1b 100644
--- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ServoDyn.dat
+++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi_ServoDyn.dat
@@ -57,13 +57,17 @@ True                   GenTiStp    - Method to stop the generator {T: timed usin
 9999.9                 TYawManS    - Time to start override yaw maneuver and end standard yaw control (s)
 2.0                    YawManRat   - Yaw maneuver rate (in absolute value) (deg/s)
 0.0                    NacYawF     - Final yaw angle for override yaw maneuvers (degrees)
----------------------- TUNED MASS DAMPER ---------------------------------------
-False                  CompNTMD    - Compute nacelle tuned mass damper {true/false} (flag)
-"unused"               NTMDfile    - Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]
-False                  CompTTMD    - Compute tower tuned mass damper {true/false} (flag)
-"unused"               TTMDfile    - Name of the file for tower tuned mass damper (quoted string) [unused when CompTTMD is false]
+---------------------- STRUCTURAL CONTROL --------------------------------------
+0             NumBStC      - Number of blade structural controllers (integer)
+"unused"      BStCfiles    - Name of the files for blade structural controllers (quoted strings) [unused when NumBStC==0]
+0             NumNStC      - Number of nacelle structural controllers (integer)
+"unused"      NStCfiles    - Name of the files for nacelle structural controllers (quoted strings) [unused when NumNStC==0]
+0             NumTStC      - Number of tower structural controllers (integer)
+"unused"      TStCfiles    - Name of the files for tower structural controllers (quoted strings) [unused when NumTStC==0]
+0             NumSStC      - Number of substructure structural controllers (integer)
+"unused"      SStCfiles    - Name of the files for substructure structural controllers (quoted strings) [unused when NumSStC==0]
 ---------------------- BLADED INTERFACE ---------------------------------------- [used only with Bladed Interface]
-"../../ROSCO/install/lib/libdiscon.dylib"  DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
+"../../ROSCO/install/lib/libdiscon.so"  DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
 "ServoData/DISCON-UMaineSemi.IN"   DLL_InFile  - Name of input file sent to the DLL (-) [used only with Bladed Interface]
 "DISCON"               DLL_ProcName - Name of procedure in DLL to be called (-) [case sensitive; used only with DLL Interface]
 "default"              DLL_DT      - Communication interval for dynamic library (s) (or "default") [used only with Bladed Interface]
diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT_AeroDyn15.dat b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT_AeroDyn15.dat
index 182bc835..ced863ef 100644
--- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT_AeroDyn15.dat
+++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT_AeroDyn15.dat
@@ -6,7 +6,7 @@ False                  Echo        - Echo the input to "<rootname>.AD.ech"?  (fl
 1                      WakeMod      - Type of wake/induction model (switch) {0=none, 1=BEMT, 2=DBEMT, 3=OLAF} [WakeMod cannot be 2 or 3 when linearizing]
 2                      AFAeroMod    - Type of blade airfoil aerodynamics model (switch) {1=steady model, 2=Beddoes-Leishman unsteady model} [AFAeroMod must be 1 when linearizing]
 1                      TwrPotent   - Type tower influence on wind based on potential flow around the tower (switch) {0=none, 1=baseline potential flow, 2=potential flow with Bak correction}
-False                  TwrShadow    - Calculate tower influence on wind based on downstream tower shadow (switch) {0=none, 1=Powles model, 2=Eames model}
+0                      TwrShadow    - Calculate tower influence on wind based on downstream tower shadow (switch) {0=none, 1=Powles model, 2=Eames model}
 True                   TwrAero     - Calculate tower aerodynamic loads? (flag)
 False                  FrozenWake  - Assume frozen wake during linearization? (flag) [used only when WakeMod=1 and when linearizing]
 False                  CavitCheck   - Perform cavitation check? (flag) [AFAeroMod must be 1 when CavitCheck=true]
@@ -102,19 +102,19 @@ True                   UseBlCm     - Include aerodynamic pitching moment in calc
 "IEA-15-240-RWT_AeroDyn15_blade.dat" ADBlFile(3) - Name of file containing distributed aerodynamic properties for Blade #3 (-) [unused if NumBl < 3]
 ======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
 11                     NumTwrNds   - Number of tower nodes used in the analysis  (-) [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
-TwrElev        TwrDiam        TwrCd
-(m)              (m)           (-)
-  15.           10.             1.
-  28.           10.             1.
-  41.            9.926          1.
-  54.            9.443          1.
-  67.            8.833          1.
-  80.            8.151          1.
-  93.            7.39           1.
- 106.            6.909          1.
- 119.            6.748          1.
- 132.            6.572          1.
- 144.495         6.5            1.
+TwrElev        TwrDiam        TwrCd     TwrTI (used only with TwrShadow=2)
+(m)              (m)           (-)      (-)
+  15.           10.             1.      1.0e-1
+  28.           10.             1.      1.0e-1
+  41.            9.926          1.      1.0e-1
+  54.            9.443          1.      1.0e-1
+  67.            8.833          1.      1.0e-1
+  80.            8.151          1.      1.0e-1
+  93.            7.39           1.      1.0e-1
+ 106.            6.909          1.      1.0e-1
+ 119.            6.748          1.      1.0e-1
+ 132.            6.572          1.      1.0e-1
+ 144.495         6.5            1.      1.0e-1
 ======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
 True                   SumPrint    - Generate a summary file listing input options and interpolated properties to "<rootname>.AD.sum"?  (flag)
 9                      NBlOuts     - Number of blade node outputs [0 - 9] (-)
diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN b/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
index 3a4cb4cf..6a93ca7b 100644
--- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
+++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
@@ -1,5 +1,5 @@
 ! Controller parameter input file for the IEA-15-240-RWT-UMaineSemi wind turbine
-!    - File written using ROSCO version 2.2.0 controller tuning logic on 03/05/21
+!    - File written using ROSCO version 2.2.0 controller tuning logic on 06/02/21
 
 !------- 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}
@@ -28,12 +28,12 @@
 1.16240   1.00000   ! F_FlpCornerFreq   - Corner frequency and damping in the second order low pass filter of the blade root bending moment for flap control [rad/s, -].
 
 !------- BLADE PITCH CONTROL ----------------------------------------------
-29                  ! PC_GS_n			- Amount of gain-scheduling table entries
-0.061302  0.088725  0.110460  0.129231  0.146101  0.161618  0.176096  0.189763  0.202765  0.215213  0.227190  0.238754  0.249961  0.260854  0.271463  0.281817  0.291944  0.301857  0.311577  0.321120  0.330501  0.339728  0.348811  0.357760  0.366583  0.375288  0.383879  0.392362  0.400744                ! PC_GS_angles	    - Gain-schedule table: pitch angles [rad].
--1.590144  -1.390427  -1.225842  -1.087870  -0.970537  -0.869535  -0.781676  -0.704552  -0.636308  -0.575495  -0.520961  -0.471781  -0.427204  -0.386613  -0.349495  -0.315424  -0.284039  -0.255034  -0.228149  -0.203159  -0.179871  -0.158116  -0.137749  -0.118640  -0.100676  -0.083758  -0.067796  -0.052712  -0.038435                ! PC_GS_KP		- Gain-schedule table: pitch controller kp gains [s].
--0.205073  -0.185297  -0.168999  -0.155336  -0.143717  -0.133716  -0.125016  -0.117378  -0.110621  -0.104599  -0.099198  -0.094328  -0.089914  -0.085895  -0.082219  -0.078845  -0.075737  -0.072865  -0.070203  -0.067728  -0.065422  -0.063268  -0.061251  -0.059359  -0.057580  -0.055905  -0.054324  -0.052831  -0.051417                ! PC_GS_KI		- Gain-schedule table: pitch controller ki gains [-].
-0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0                ! PC_GS_KD			- Gain-schedule table: pitch controller kd gains
-0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0                ! PC_GS_TF			- Gain-schedule table: pitch controller tf gains (derivative filter)
+30                  ! PC_GS_n			- Amount of gain-scheduling table entries
+0.060198  0.087134  0.108448  0.126859  0.143398  0.158602  0.172796  0.186189  0.198928  0.211120  0.222850  0.234173  0.245147  0.255807  0.266195  0.276325  0.286231  0.295933  0.305445  0.314779  0.323952  0.332975  0.341858  0.350608  0.359236  0.367748  0.376149  0.384447  0.392644  0.400744                ! PC_GS_angles	    - Gain-schedule table: pitch angles [rad].
+-1.598670  -1.403333  -1.241551  -1.105363  -0.989139  -0.888791  -0.801272  -0.724271  -0.655999  -0.595051  -0.540309  -0.490871  -0.446001  -0.405095  -0.367649  -0.333242  -0.301519  -0.272177  -0.244958  -0.219639  -0.196029  -0.173960  -0.153286  -0.133879  -0.115625  -0.098425  -0.082190  -0.066841  -0.052308  -0.038527                ! PC_GS_KP		- Gain-schedule table: pitch controller kp gains [s].
+-0.206015  -0.186658  -0.170625  -0.157129  -0.145612  -0.135667  -0.126994  -0.119364  -0.112598  -0.106558  -0.101133  -0.096234  -0.091788  -0.087734  -0.084023  -0.080613  -0.077470  -0.074562  -0.071864  -0.069355  -0.067016  -0.064829  -0.062780  -0.060857  -0.059048  -0.057343  -0.055734  -0.054213  -0.052773  -0.051408                ! PC_GS_KI		- Gain-schedule table: pitch controller ki gains [-].
+0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0                ! PC_GS_KD			- Gain-schedule table: pitch controller kd gains
+0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0                ! PC_GS_TF			- Gain-schedule table: pitch controller tf gains (derivative filter)
 1.570800000000      ! PC_MaxPit			- Maximum physical pitch limit, [rad].
 0.000000000000      ! PC_MinPit			- Minimum physical pitch limit, [rad].
 0.034900000000      ! PC_MaxRat			- Maximum pitch rate (in absolute value) in pitch controller, [rad/s].
@@ -60,7 +60,7 @@
 19624046.66639      ! VS_RtTq			- Rated torque, [Nm].
 0.791680000000      ! VS_RefSpd			- Rated generator speed [rad/s]
 1                   ! VS_n				- Number of generator PI torque controller gains
--38005940.32297      ! VS_KP				- Proportional gain for generator PI torque controller [-]. (Only used in the transitional 2.5 region if VS_ControlMode =/ 2)
+-38676765.16943      ! VS_KP				- Proportional gain for generator PI torque controller [-]. (Only used in the transitional 2.5 region if VS_ControlMode =/ 2)
 -4588245.18720      ! VS_KI				- Integral gain for generator PI torque controller [s]. (Only used in the transitional 2.5 region if VS_ControlMode =/ 2)
 9.00                ! VS_TSRopt			- Power-maximizing region 2 tip-speed-ratio [rad].
 
@@ -71,7 +71,7 @@
 !------- WIND SPEED ESTIMATOR ---------------------------------------------
 120.000             ! WE_BladeRadius	- Blade length (distance from hub center to blade tip), [m]
 1                   ! WE_CP_n			- Amount of parameters in the Cp array
-0.0 0.0 0.0 0.0     ! WE_CP - Parameters that define the parameterized CP(lambda) function
+0.0               ! WE_CP - Parameters that define the parameterized CP(lambda) function
 0.0          		! WE_Gamma			- Adaption gain of the wind speed estimator algorithm [m/rad]
 1.0                 ! WE_GearboxRatio	- Gearbox ratio [>=1],  [-]
 318628138.00000      ! WE_Jtot			- Total drivetrain inertia, including blades, hub and casted generator inertia to LSS, [kg m^2]
@@ -79,8 +79,8 @@
 "Cp_Ct_Cq.IEA15MW.txt"      ! PerfFileName      - File containing rotor performance tables (Cp,Ct,Cq)
 104     72          ! PerfTableSize     - Size of rotor performance tables, first number refers to number of blade pitch angles, second number referse to number of tip-speed ratios
 60                  ! WE_FOPoles_N      - Number of first-order system poles used in EKF
-3.00 3.27 3.53 3.80 4.07 4.33 4.60 4.87 5.14 5.40 5.67 5.94 6.20 6.47 6.74 7.00 7.27 7.54 7.80 8.07 8.34 8.60 8.87 9.14 9.41 9.67 9.94 10.21 10.47 10.74 10.74 11.23 11.72 12.22 12.71 13.20 13.69 14.18 14.67 15.17 15.66 16.15 16.64 17.13 17.62 18.12 18.61 19.10 19.59 20.08 20.57 21.07 21.56 22.05 22.54 23.03 23.52 24.02 24.51 25.00               ! WE_FOPoles_v      - Wind speeds corresponding to first-order system poles [m/s]
--0.02366483 -0.02577018 -0.02787553 -0.02998089 -0.03208624 -0.03419159 -0.03629695 -0.03840230 -0.04050765 -0.04261301 -0.04471836 -0.04682371 -0.04892907 -0.05103442 -0.05313977 -0.05524513 -0.05735048 -0.05945583 -0.06156119 -0.06366654 -0.06577189 -0.06787725 -0.06998260 -0.07208795 -0.07419331 -0.07629866 -0.07840401 -0.08050937 -0.08261472 -0.08472008 -0.07921295 -0.05358619 -0.05636426 -0.06307564 -0.07173987 -0.08172495 -0.09271927 -0.10454428 -0.11705643 -0.13017613 -0.14379976 -0.15793978 -0.17258746 -0.18766434 -0.20315149 -0.21909644 -0.23538854 -0.25208919 -0.26915631 -0.28659300 -0.30437969 -0.32249538 -0.34096095 -0.35974552 -0.37881117 -0.39822177 -0.41789494 -0.43785131 -0.45808118 -0.47857910               ! WE_FOPoles        - First order system poles [1/s]
+3.00 3.27 3.53 3.80 4.07 4.33 4.60 4.87 5.14 5.40 5.67 5.94 6.20 6.47 6.74 7.00 7.27 7.54 7.80 8.07 8.34 8.60 8.87 9.14 9.41 9.67 9.94 10.21 10.47 10.74 11.22 11.69 12.17 12.64 13.12 13.59 14.07 14.54 15.02 15.49 15.97 16.44 16.92 17.39 17.87 18.35 18.82 19.30 19.77 20.25 20.72 21.20 21.67 22.15 22.62 23.10 23.57 24.05 24.52 25.00               ! WE_FOPoles_v      - Wind speeds corresponding to first-order system poles [m/s]
+-0.02366483 -0.02577018 -0.02787553 -0.02998089 -0.03208624 -0.03419159 -0.03629695 -0.03840230 -0.04050765 -0.04261301 -0.04471836 -0.04682371 -0.04892907 -0.05103442 -0.05313977 -0.05524513 -0.05735048 -0.05945583 -0.06156119 -0.06366654 -0.06577189 -0.06787725 -0.06998260 -0.07208795 -0.07419331 -0.07629866 -0.07840401 -0.08050937 -0.08261472 -0.07921295 -0.05363855 -0.05601196 -0.06228621 -0.07050010 -0.08000290 -0.09048156 -0.10174863 -0.11365942 -0.12615707 -0.13917960 -0.15270665 -0.16669562 -0.18106141 -0.19588503 -0.21106595 -0.22664340 -0.24258187 -0.25886648 -0.27550565 -0.29248186 -0.30978187 -0.32738614 -0.34531736 -0.36353035 -0.38202289 -0.40082810 -0.41987220 -0.43918834 -0.45876266 -0.47857910               ! WE_FOPoles        - First order system poles [1/s]
 
 !------- YAW CONTROL ------------------------------------------------------
 0.0                 ! Y_ErrThresh		- Yaw error threshold. Turbine begins to yaw when it passes this. [rad^2 s]
@@ -97,20 +97,20 @@
 
 !------- 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
-0.0                 ! FA_HPF_CornerFreq	- Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s]
+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]
 
 !------- MINIMUM PITCH SATURATION -------------------------------------------
 60                  ! PS_BldPitchMin_N  - Number of values in minimum blade pitch lookup table (should equal number of values in PS_WindSpeeds and PS_BldPitchMin)
-3.0000 3.2669 3.5338 3.8007 4.0676 4.3345 4.6014 4.8683 5.1352 5.4021 5.6690 5.9359 6.2028 6.4697 6.7366 7.0034 7.2703 7.5372 7.8041 8.0710 8.3379 8.6048 8.8717 9.1386 9.4055 9.6724 9.9393 10.2062 10.4731 10.7400 10.7400 11.2317 11.7234 12.2152 12.7069 13.1986 13.6903 14.1821 14.6738 15.1655 15.6572 16.1490 16.6407 17.1324 17.6241 18.1159 18.6076 19.0993 19.5910 20.0828 20.5745 21.0662 21.5579 22.0497 22.5414 23.0331 23.5248 24.0166 24.5083 25.0000               ! PS_WindSpeeds     - Wind speeds corresponding to minimum blade pitch angles [m/s]
-0.06981317 0.06981317 0.06981317 0.06544985 0.06108652 0.06108652 0.05672320 0.05235988 0.04363323 0.03926991 0.03054326 0.02617994 0.01745329 0.01308997 0.00436332 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000               ! PS_BldPitchMin    - Minimum blade pitch angles [rad]
+3.0000 3.2669 3.5338 3.8007 4.0676 4.3345 4.6014 4.8683 5.1352 5.4021 5.6690 5.9359 6.2028 6.4697 6.7366 7.0034 7.2703 7.5372 7.8041 8.0710 8.3379 8.6048 8.8717 9.1386 9.4055 9.6724 9.9393 10.2062 10.4731 10.7400 11.2153 11.6907 12.1660 12.6413 13.1167 13.5920 14.0673 14.5427 15.0180 15.4933 15.9687 16.4440 16.9193 17.3947 17.8700 18.3453 18.8207 19.2960 19.7713 20.2467 20.7220 21.1973 21.6727 22.1480 22.6233 23.0987 23.5740 24.0493 24.5247 25.0000               ! PS_WindSpeeds     - Wind speeds corresponding to minimum blade pitch angles [m/s]
+0.06981317 0.06981317 0.06981317 0.06544985 0.06108652 0.06108652 0.05672320 0.05235988 0.04363323 0.03926991 0.03054326 0.02617994 0.01745329 0.01308997 0.00436332 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00846242 0.02297908 0.03562095 0.04679173 0.05441817 0.06531126 0.07566895 0.08563939 0.09530571 0.10472222 0.11392608 0.12294997 0.13181647 0.14054481 0.14914972 0.15764424 0.16603901 0.17434237 0.18256207 0.19070469 0.19877714 0.20678372 0.21472883 0.22261606 0.23044510 0.23822156 0.24594897 0.25362874 0.26126159 0.26884689 0.27639120 0.28389435 0.29135253 0.29877202 0.30615320               ! PS_BldPitchMin    - Minimum blade pitch angles [rad]
 
 !------- SHUTDOWN -----------------------------------------------------------
 0.400740000000      ! SD_MaxPit         - Maximum blade pitch angle to initiate shutdown, [rad]
 0.418880000000      ! SD_CornerFreq     - Cutoff Frequency for first order low-pass filter for blade pitch angle, [rad/s]
 
 !------- Floating -----------------------------------------------------------
--9.32577000000      ! Fl_Kp             - Nacelle velocity proportional feedback gain [s]
+-9.37521000000      ! Fl_Kp             - Nacelle velocity proportional feedback gain [s]
 
 !------- FLAP ACTUATION -----------------------------------------------------
 0.000000000000      ! Flp_Angle         - Initial or steady state flap angle [rad]
diff --git a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_AeroDyn15.dat b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_AeroDyn15.dat
index 91955de0..7a358d7f 100644
--- a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_AeroDyn15.dat
+++ b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_AeroDyn15.dat
@@ -6,7 +6,7 @@ False         Echo               - Echo the input to "<rootname>.AD.ech"?  (flag
           1   WakeMod            - Type of wake/induction model (switch) {0=none, 1=BEMT, 2=DBEMT, 3=OLAF}  [WakeMod cannot be 2 or 3 when linearizing]
           2   AFAeroMod          - Type of blade airfoil aerodynamics model (switch) {1=steady model, 2=Beddoes-Leishman unsteady model} [AFAeroMod must be 1 when linearizing]
           1   TwrPotent          - Type tower influence on wind based on potential flow around the tower (switch) {0=none, 1=baseline potential flow, 2=potential flow with Bak correction}
-False         TwrShadow          - Calculate tower influence on wind based on downstream tower shadow? (flag)
+          0   TwrShadow          - Calculate tower influence on wind based on downstream tower shadow? (flag)
 True          TwrAero            - Calculate tower aerodynamic loads? (flag)
 False         FrozenWake         - Assume frozen wake during linearization? (flag) [used only when WakeMod=1 and when linearizing]
 False         CavitCheck         - Perform cavitation check? (flag) [AFAeroMod must be 1 when CavitCheck=true]
@@ -60,20 +60,20 @@ True          UseBlCm            - Include aerodynamic pitching moment in calcul
 "NRELOffshrBsline5MW_AeroDyn_blade.dat"    ADBlFile(3)        - Name of file containing distributed aerodynamic properties for Blade #3 (-) [unused if NumBl < 3]
 ======  Tower Influence and Aerodynamics ============================================================= [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
          12   NumTwrNds         - Number of tower nodes used in the analysis  (-) [used only when TwrPotent/=0, TwrShadow=True, or TwrAero=True]
-TwrElev        TwrDiam        TwrCd
-(m)              (m)           (-)
-0.0000000E+00  6.0000000E+00  1.0000000E+00  
-8.5261000E+00  5.7870000E+00  1.0000000E+00  
-1.7053000E+01  5.5740000E+00  1.0000000E+00  
-2.5579000E+01  5.3610000E+00  1.0000000E+00  
-3.4105000E+01  5.1480000E+00  1.0000000E+00  
-4.2633000E+01  4.9350000E+00  1.0000000E+00  
-5.1158000E+01  4.7220000E+00  1.0000000E+00  
-5.9685000E+01  4.5090000E+00  1.0000000E+00  
-6.8211000E+01  4.2960000E+00  1.0000000E+00  
-7.6738000E+01  4.0830000E+00  1.0000000E+00  
-8.5268000E+01  3.8700000E+00  1.0000000E+00  
-8.7600000E+01  3.8700000E+00  1.0000000E+00  
+TwrElev        TwrDiam        TwrCd            TwrTI (used only with TwrShadow=2)
+(m)              (m)           (-)             (-)
+0.0000000E+00  6.0000000E+00  1.0000000E+00  1.000000E-01  
+8.5261000E+00  5.7870000E+00  1.0000000E+00  1.000000E-01  
+1.7053000E+01  5.5740000E+00  1.0000000E+00  1.000000E-01  
+2.5579000E+01  5.3610000E+00  1.0000000E+00  1.000000E-01  
+3.4105000E+01  5.1480000E+00  1.0000000E+00  1.000000E-01  
+4.2633000E+01  4.9350000E+00  1.0000000E+00  1.000000E-01  
+5.1158000E+01  4.7220000E+00  1.0000000E+00  1.000000E-01  
+5.9685000E+01  4.5090000E+00  1.0000000E+00  1.000000E-01  
+6.8211000E+01  4.2960000E+00  1.0000000E+00  1.000000E-01  
+7.6738000E+01  4.0830000E+00  1.0000000E+00  1.000000E-01  
+8.5268000E+01  3.8700000E+00  1.0000000E+00  1.000000E-01  
+8.7600000E+01  3.8700000E+00  1.0000000E+00  1.000000E-01  
 ======  Outputs  ====================================================================================
 True          SumPrint            - Generate a summary file listing input options and interpolated properties to "<rootname>.AD.sum"?  (flag)
           0   NBlOuts             - Number of blade node outputs [0 - 9] (-)
diff --git a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
index 47961d9c..d16d9f38 100644
--- a/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
+++ b/Test_Cases/NREL-5MW/NRELOffshrBsline5MW_Onshore_ServoDyn.dat
@@ -57,13 +57,17 @@ True          GenTiStp     - Method to stop the generator {T: timed using TimGen
      9999.9   TYawManS     - Time to start override yaw maneuver and end standard yaw control (s)
           2   YawManRat    - Yaw maneuver rate (in absolute value) (deg/s)
           0   NacYawF      - Final yaw angle for override yaw maneuvers (degrees)
----------------------- TUNED MASS DAMPER ---------------------------------------
-False         CompNTMD     - Compute nacelle tuned mass damper {true/false} (flag)
-"../5MW_Baseline/NRELOffshrBsline5MW_ServoDyn_TMD.dat"    NTMDfile     - Name of the file for nacelle tuned mass damper (quoted string) [unused when CompNTMD is false]
-False         CompTTMD     - Compute tower tuned mass damper {true/false} (flag)
-"../5MW_Baseline/NRELOffshrBsline5MW_ServoDyn_TMD.dat"    TTMDfile     - Name of the file for tower tuned mass damper (quoted string) [unused when CompTTMD is false]
+---------------------- STRUCTURAL CONTROL --------------------------------------
+0             NumBStC      - Number of blade structural controllers (integer)
+"unused"      BStCfiles    - Name of the files for blade structural controllers (quoted strings) [unused when NumBStC==0]
+0             NumNStC      - Number of nacelle structural controllers (integer)
+"unused"      NStCfiles    - Name of the files for nacelle structural controllers (quoted strings) [unused when NumNStC==0]
+0             NumTStC      - Number of tower structural controllers (integer)
+"unused"      TStCfiles    - Name of the files for tower structural controllers (quoted strings) [unused when NumTStC==0]
+0             NumSStC      - Number of substructure structural controllers (integer)
+"unused"      SStCfiles    - Name of the files for substructure structural controllers (quoted strings) [unused when NumSStC==0]
 ---------------------- BLADED INTERFACE ---------------------------------------- [used only with Bladed Interface]
-"../../ROSCO/install/lib/libdiscon.dylib"    DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
+"../../ROSCO/install/lib/libdiscon.so"    DLL_FileName - Name/location of the dynamic library {.dll [Windows] or .so [Linux]} in the Bladed-DLL format (-) [used only with Bladed Interface]
 "DISCON.IN"    DLL_InFile   - Name of input file sent to the DLL (-) [used only with Bladed Interface]
 "DISCON"      DLL_ProcName - Name of procedure in DLL to be called (-) [case sensitive; used only with DLL Interface]
 "default"     DLL_DT       - Communication interval for dynamic library (s) (or "default") [used only with Bladed Interface]
diff --git a/Tune_Cases/IEA15MW.yaml b/Tune_Cases/IEA15MW.yaml
index d926751d..ae2a6b44 100644
--- a/Tune_Cases/IEA15MW.yaml
+++ b/Tune_Cases/IEA15MW.yaml
@@ -35,7 +35,7 @@ controller_params:
   Y_ControlMode:      0                     # Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
   SS_Mode:            1                     # Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
   WE_Mode:            2                     # Wind speed estimator mode {0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.)}
-  PS_Mode:            2                     # Pitch saturation mode {0: no pitch saturation, 1: peak shaving, 2: Cp-maximizing pitch saturation, 3: peak shaving and Cp-maximizing pitch saturation}
+  PS_Mode:            3                     # Pitch saturation mode {0: no pitch saturation, 1: peak shaving, 2: Cp-maximizing pitch saturation, 3: peak shaving and Cp-maximizing pitch saturation}
   SD_Mode:            0                     # Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown}
   Fl_Mode:            1                     # Floating specific feedback mode {0: no nacelle velocity feedback, 1: nacelle velocity feedback}
   Flp_Mode:           0                     # Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control}
diff --git a/docs/conf.py b/docs/conf.py
index 19f3e183..c5e3f667 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -75,9 +75,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '2.2'
+version = '2.3'
 # The full version, including alpha/beta/rc tags.
-release = 'v2.2.0'
+release = 'v2.3.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/index.rst b/docs/index.rst
index b6f6eaec..bc19a083 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,10 +1,3 @@
-.. toctree::
-
-   source/install.rst
-   source/standard_use.rst
-   source/rosco_toolbox.rst
-   source/rosco.rst
-
 ROSCO toolbox documentation
 ===========================
 .. only:: html
@@ -27,23 +20,48 @@ Figure :numref:`fig-RT` shows the general workflow for the ROSCO toolchain.
 
 **ROSCO Toolbox**
 
-- Generic tuning of NREL's ROSCO controller
-- Simple 1-DOF turbine simulations for quick controller capability verifications
-- Parsing of OpenFAST input and output files
-- Block diagrams of these capabilities can be seen in architecture.png.
+* Generic tuning of NREL's ROSCO controller
+* Simple 1-DOF turbine simulations for quick controller capability verifications
+* Parsing of OpenFAST input and output files
+* Block diagrams of these capabilities can be seen in architecture.png.
 
 **ROSCO Controller**
 
-- Fortran based
-- Follows Bladed-style control interface
-- Modular
+* Fortran based
+* Follows Bladed-style control interface
+* Modular
 
-**Standard Use** 
+Standard Use
+------------
 
 For the standard use case in OpenFAST, ROSCO will need to be compiled. This is made possible via the instructions found in :ref:`install`. Once the controller is compiled, the turbine model needs to point to the compiled binary. In OpenFAST, this is ensured by changing the :code:`DLL_FileName` parameter in the ServoDyn input file. 
 
 Additionally, an additional input file is needed for the ROSCO controller. Though the controller only needs to be compiled once, each individual turbine/controller tuning requires an input file. This input file is generically dubbed "DISCON.IN''. In OpenFAST, the :code:`DLL_InFile` parameter should be set to point to the desired input file. The ROSCO toolbox is used to automatically generate the input file. These instructions are provided in the instructions for :ref:`standard_use`.
 
+Technical Documentation
+-----------------------
+A publication highlighting much of the theory behind the controller tuning and implementation methods can be found at:
+https://wes.copernicus.org/preprints/wes-2021-19/
+
+Survey
+------
+Please help us better understand the ROSCO user-base and how we can improve ROSCO through this brief survey:
+
+.. raw:: html
+   
+   <iframe width="640px" height= "480px" src= "https://forms.office.com/Pages/ResponsePage.aspx?id=fp3yoM0oVE-EQniFrufAgGWnC45k8q5Kl90RBkHijqBUN0JTNzBJT1QwMjIzNDhCWDlDTUZPWDdMWC4u&embed=true" frameborder= "0" marginwidth= "0" marginheight= "0" style= "border: none; max-width:100%; max-height:100vh" allowfullscreen webkitallowfullscreen mozallowfullscreen msallowfullscreen> </iframe>
+   
+|
+Directory
+---------
+
+.. toctree::
+   :numbered:
+   
+   source/install.rst
+   source/standard_use.rst
+   source/rosco_toolbox.rst
+   source/rosco.rst
 
 **License**
 Copyright 2020 NREL
diff --git a/docs/source/install.rst b/docs/source/install.rst
index 45b3e2fb..d42410aa 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -4,33 +4,32 @@
 
 Installing the ROSCO tools
 ===========================
-Both the ROSCO controller and ROSCO toolbox should be installed if one wishes to leverage the full ROSCO toolchain. 
-Of course, some users may only wish to download and install only the controller or the toolbox. 
+Depending on what is needed, a user can choose to use just the ROSCO controller or to use both the ROSCO controller and the toolbox.
+Both the controller and the toolbox should be installed if one wishes to leverage the full ROSCO toolchain.
 
-For users wishing to use just the ROSCO toolbox or the ROSCO toolbox and controller, please skip to the section on section :ref:`installing_rt`. 
-Here we will provide the necessary steps to install the ROSCO toolbox and the ROSCO controller simultaneously. 
-For users planning to only download and compile the ROSCO controller, please follow the instructions on :ref:`compiling_rosco`. 
+For users who wish to use the ROSCO toolbox (with or without the controller), please skip to the section on section :ref:`rosco_toolbox`. 
+For users planning to only download and compile the ROSCO controller, please follow the instructions on :ref:`rosco_controller`. 
 For information on best practices to update to the most recent version of the ROSCO toolbox, see :ref:`updating_rt`.
 
 
-.. _compiling_rosco:
+.. _rosco_controller:
 
-Compiling ROSCO
+ROSCO controller
 ----------------
 The standard ROSCO controller is based in Fortran and must be compiled; this code can be found at: https://github.com/NREL/ROSCO. 
 Of course, the advanced user can compile the downloaded code using their own desired methods (e.g. Visual Studio). 
 Otherwise, a few of the more common compiling methods are detailed on this page. 
 Additionally, the most recent tagged version releases are `available for download <https://github.com/NREL/ROSCO/tags>`_. 
 
-If one wishes to download the code via the command line, we provide two supported options. 
-For non-developers (those not interested in modifying the source code), the controller can be downloaded via Anaconda. 
-For developers, CMake can be used to compile the Fortran code. 
+If one wishes to download the code via the command line, we provide two supported options in the subsections below. 
+For non-developers (those not interested in modifying the source code), the a 64-bit version of the compiled controller can be downloaded via Anaconda. 
+For users needing a 32-bit version on Windows and/or developers, CMake can be used to compile the Fortran code. 
 
 
-Anaconda for non-developers:
-.............................
+Anaconda download for non-developers
+.....................................
 
-For users familiar with Anaconda_, ROSCO is available through the conda-forge channel. 
+For users familiar with Anaconda_, a 64-bit version of ROSCO is available through the conda-forge channel. 
 In order to download the most recently compiled version release, from an anaconda powershell (Windows) or terminal (Mac/Linux) window, create a new anaconda virtual environment: 
 ::
 
@@ -54,12 +53,19 @@ The ROSCO binary file can be copied to your desired folder using:
 
     cp $CONDA_PREFIX/lib/libdiscon.* .
 
+on linux or:
+::
+
+    copy %CONDA_PREFIX%/lib/libdiscon.* .
+
 
-CMake for developers:
-.....................
-CMake_ provides a straightforward option for many users, particularly those on a Mac or Linux. 
-For windows users, we recommend using MinGW_ to compile similarly. 
-ROSCO can be compiled by first cloning the source code from git using:
+on Windows.
+
+
+CMake for developers (Mac/linux)
+.................................
+CMake_ provides a straightforward option for many users, particularly those on a Mac or Linux.
+On Mac/Linux, ROSCO can be compiled by first cloning the source code from git using:
 ::
 
     git clone https://github.com/NREL/ROSCO.git
@@ -70,18 +76,46 @@ And then compiling using CMake:
     cd ROSCO
     mkdir build
     cd build
-    cmake ..                        # Mac/Linus
-    cmake .. -G "MinGW Makefiles"   # Windows
+    cmake ..
     make install
 
-This will generate a file called :code:`libdiscon.*` in the :code:`/ROSCO/install/lib` directory. 
+This will generate a file called :code:`libdiscon.so` (Linux) or :code:`libdiscon.dylib` (Mac) in the :code:`/ROSCO/install/lib` directory. 
+
 
-Note: when compiling with MinGW on Windows, modifying the cmake line (4th command) in the above code block to :code:`cmake .. -G "MinGWMakefiles" -DCMake_Fortran_COMPILER=gfortran` may solve compiler errors that may arise, depending on your local configuration.
 
-.. _installing_rt:
+CMake for developers/32-bit (Windows)
+......................................
+
+To compile ROSCO on Windows, you first need a Fortran compiler. If you need a 32-bit DLL, then we recommend `installing MinGW <http://capsis.cirad.fr/capsis/documentation/mingw-installation>`_ (Section 2).
+If you require a 64-bit version, you can install the MSYS2 toolchain through conda::
+
+    conda install m2w64-toolchain libpython
+
+Note that if you have the 64-bit toolchain installed in your environment, you might have conflicts with the 32-bit compiler. We recommend therefore keeping separate environments if you want to compile 32- or 64-bit.
+
+Once you have your Fortran compiler successfully installed and configured, the build process is similar to on Mac and linux:
+::
 
-Installing the ROSCO toolbox
+    cd ROSCO
+    mkdir build
+    cd build
+    cmake .. -G "MinGW Makefiles"
+    mingw32-make
+
+Note that the :code:`mingw32-make` command is (confusingly) valid for both 64-bit and 32-bit MinGW.
+
+This will generate a file called :code:`libdiscon.dll` in the :code:`/ROSCO/install/lib` directory. 
+
+
+.. _rosco_toolbox:
+
+Full ROSCO toolbox
 ----------------------------
+
+We recommend using the full ROSCO toolbox so that you can leverage the entire toolchain.
+
+Installing
+..............
 Installation of the complete ROSCO toolbox is made easy through `Anaconda <https://www.anaconda.com/>`_. 
 If you do not already have Anaconda installed on your machine, please install it. 
 
@@ -136,7 +170,7 @@ Note that if you do choose to install the ROSCO Toolbox this way, you will not h
 .. _updating_rt:
 
 Updating the ROSCO Toolbox
---------------------------
+...........................
 Simple git commands should update the toolbox and controller as development continues:
 ```
 git pull
@@ -149,6 +183,6 @@ and then recompile and reinstall as necessary...
 .. _MinGW: https://mingw-w64.org/
 
 
-Getting Started with the ROSCO Toolbox
---------------------------------------
+Getting Started
+...................
 Please see a the :ref:`standard_use` for several example scripts using ROSCO and the ROSCO_toolbox.
diff --git a/docs/source/rosco_toolbox.rst b/docs/source/rosco_toolbox.rst
index d51d9b37..2fda0629 100644
--- a/docs/source/rosco_toolbox.rst
+++ b/docs/source/rosco_toolbox.rst
@@ -12,7 +12,7 @@ File Structure
 ---------------
 The primary tools of the ROSCO toolbox are separated into several folders. They include the following:
 
-*ROSCO_toolbox*
+**ROSCO_toolbox**
 ...............
 The source code for the ROSCO toolbox generic tuning implementations lives here. 
 
@@ -23,23 +23,23 @@ The source code for the ROSCO toolbox generic tuning implementations lives here.
     * :code:`sim.py` is a simple 1-DOF model simulator
     * **ofTools** is a folder containing a large set of tools to handle OpenFAST_ input files - this is primarily used to run large simulation sets and to handle reading and processing of OpenFAST input and output files. 
 
-*Examples*
+**Examples**
 ..........
 A number of examples are included to showcase the numerous capabilities of the ROSCO toolbox; they are described in the :ref:`standard_use`.
 
-*Matlab_Toolbox*
+**Matlab_Toolbox**
 ................
 A simulink implementation of the ROSCO controller is included in the Matlab Toolbox. Some requisite MATLAB utility scripts are also included.
 
-*ROSCO_testing*
+**ROSCO_testing**
 ...............
 Testing scripts for the ROSCO toolbox are held here and showcased with :code:`run_testing.py`. These can be used to compare different controller tunings or different controllers all together.
 
-*Test_Cases*
+**Test_Cases**
 ............
 Example OpenFAST models consistent with the latest release of OpenFAST are provided here for simple testing and simulation cases.
 
-*Tune_Cases*
+**Tune_Cases**
 ............
 Some example tuning scripts and tuning input files are provided here. The code found in :code:`tune_ROSCO.py` can be modified by the user to easily enable tuning of their own wind turbine model.
 
@@ -173,7 +173,7 @@ A yaml_ formatted input file is used for the standard ROSCO toolbox tuning proce
     - :code:`WE_Mode`
     - Yes
     - Int 
-    - Wind speed estimator mode. 0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.)
+    - Wind speed estimator mode. 0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.), 2: Extended Kalman filter
   * - 
     - :code:`PS_Mode`
     - Yes
@@ -277,4 +277,4 @@ A yaml_ formatted input file is used for the standard ROSCO toolbox tuning proce
      
 
 .. _OpenFAST: https://github.com/openfast/openfast
-.. _yaml: https://yaml.org/
\ No newline at end of file
+.. _yaml: https://yaml.org/
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 00000000..2cb76313
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,11 @@
+channels:
+  - conda-forge
+  - defaults
+
+dependencies:
+  - matplotlib
+  - numpy
+  - pytest
+  - scipy
+  - pyYAML
+  - pandas
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 64104f31..87b10845 100644
--- a/setup.py
+++ b/setup.py
@@ -41,7 +41,7 @@
 EMAIL = 'nikhar.abbas@nrel.gov'
 AUTHOR = 'NREL National Wind Technology Center'
 REQUIRES_PYTHON = '>=3.4'
-VERSION = '2.2.0'
+VERSION = '2.3.0'
 
 # These packages are required for all of the code to be executed. 
 # - Maybe you can get away with older versions...
@@ -51,6 +51,8 @@
     'pytest',
     'scipy',
     'pyYAML',
+    'future',
+    'pandas'
 ]
 
 # Read the docs, one day, so we'll throw it in here!
@@ -174,7 +176,7 @@ def run(self):
         self.status('Uploading the package to PyPI via Twine...')
         os.system('twine upload dist/*')
 
-        self.status('Pushing git tags…')
+        self.status('Pushing git tags...')
         os.system('git tag v{0}'.format(about['__version__']))
         os.system('git push --tags')
         


Test patch:
diff --git a/ROSCO_testing/ROSCO_testing.py b/ROSCO_testing/ROSCO_testing.py
index 4aa7675e..2380ac96 100644
--- a/ROSCO_testing/ROSCO_testing.py
+++ b/ROSCO_testing/ROSCO_testing.py
@@ -13,7 +13,7 @@
 '''
 
 import numpy as np
-import os
+import os, platform
 import glob
 import multiprocessing as mp
 
@@ -37,7 +37,7 @@ def __init__(self, **kwargs):
 
         # Setup simulation parameters
         self.runDir = os.path.join(os.path.dirname( os.path.realpath(__file__) ), 'testing' )   # directory to run simulations in
-        self.windDir = None
+        self.wind_dir = None
         self.namebase = 'ROtest'    # root name for output simulations
         self.FAST_exe = 'openfast_single'       # name of openfast executable (may need full path)
         self.Turbsim_exe = 'turbsim_single'     # name of turbsim executable
@@ -54,6 +54,7 @@ def __init__(self, **kwargs):
         self.mpi_run = False
         self.mpi_comm_map_down = []
         self.outfile_fmt = 2 # 1 = .txt, 2 = binary, 3 = both
+        self.comp_dir = None
 
         # Setup turbine parameters 
         #  - Default to NREL 5MW 
@@ -104,9 +105,9 @@ def ROSCO_Test_lite(self, more_case_inputs={}, U=[]):
 
         # Check for time and wind inputs
         if ('Fst','TMax') in more_case_inputs.keys():
-            TMax = np.max(more_case_inputs[('Fst','TMax')]['vals'])
+            self.TMax = np.max(more_case_inputs[('Fst','TMax')]['vals'])
         else:
-            TMax = 330
+            self.TMax = 330
         
         if len(U) > 0:
             WindSpeeds = U
@@ -135,7 +136,7 @@ def ROSCO_Test_lite(self, more_case_inputs={}, U=[]):
         iec.Turbulence_Class = self.Turbulence_Class
         iec.D = fastRead.fst_vt['ElastoDyn']['TipRad']*2.
         iec.z_hub = fastRead.fst_vt['InflowWind']['RefHt']
-        iec.TMax = TMax
+        iec.TMax = self.TMax
 
         iec.dlc_inputs = {}
         iec.dlc_inputs['DLC'] = [1.1]  # ,6.1,6.3]
@@ -145,8 +146,8 @@ def ROSCO_Test_lite(self, more_case_inputs={}, U=[]):
         iec.transient_dir_change = '-'  # '+','-','both': sign for transient events in EDC, EWS
         iec.transient_shear_orientation = 'v'  # 'v','h','both': vertical or horizontal shear for EWS
 
-        if self.windDir:
-            iec.wind_dir = self.windDir
+        if self.wind_dir:
+            iec.wind_dir = self.wind_dir
         else:
             iec.wind_dir = os.path.join(self.runDir, 'wind')
             
@@ -168,7 +169,7 @@ def ROSCO_Test_lite(self, more_case_inputs={}, U=[]):
             iec.comm_map_down = mpi_comm_map_down
 
         case_inputs = {}
-        case_inputs[("Fst", "TMax")] = {'vals': [TMax], 'group': 0}
+        case_inputs[("Fst", "TMax")] = {'vals': [self.TMax], 'group': 0}
         case_inputs[("Fst", "OutFileFmt")] = {'vals': [self.outfile_fmt], 'group': 0}
 
         case_inputs[('ServoDyn', 'GenTiStr')] = {'vals': ['True'], 'group': 0}
@@ -258,9 +259,9 @@ def ROSCO_Test_heavy(self, more_case_inputs={}, U=[]):
 
         # Check for time and wind inputs
         if ('Fst','TMax') in more_case_inputs.keys():
-            TMax = np.max(more_case_inputs[('Fst','TMax')]['vals'])
+            self.TMax = np.max(more_case_inputs[('Fst','TMax')]['vals'])
         else:
-            TMax = 630
+            self.TMax = 630
         
         if len(U) > 0:
             WindSpeeds = U
@@ -299,7 +300,7 @@ def ROSCO_Test_heavy(self, more_case_inputs={}, U=[]):
         iec.Turbulence_Class = self.Turbulence_Class
         iec.D = fastRead.fst_vt['ElastoDyn']['TipRad']*2.
         iec.z_hub = fastRead.fst_vt['InflowWind']['RefHt']
-        iec.TMax = TMax
+        iec.TMax = self.TMax
 
         iec.dlc_inputs = {}
         iec.dlc_inputs['DLC'] = [1.3, 1.4] 
@@ -311,8 +312,8 @@ def ROSCO_Test_heavy(self, more_case_inputs={}, U=[]):
         iec.uniqueSeeds = True
         iec.uniqueWaveSeeds = True
 
-        if self.windDir:
-            iec.wind_dir = self.windDir
+        if self.wind_dir:
+            iec.wind_dir = self.wind_dir
         else:
             iec.wind_dir = os.path.join(self.runDir, 'wind')
         iec.case_name_base = self.namebase
@@ -333,7 +334,7 @@ def ROSCO_Test_heavy(self, more_case_inputs={}, U=[]):
             iec.comm_map_down = mpi_comm_map_down
 
         case_inputs = {}
-        case_inputs[("Fst", "TMax")] = {'vals': [TMax], 'group': 0}
+        case_inputs[("Fst", "TMax")] = {'vals': [self.TMax], 'group': 0}
         case_inputs[("Fst", "OutFileFmt")] = {'vals': [self.outfile_fmt], 'group': 0}
 
         case_inputs[('ServoDyn', 'GenTiStr')] = {'vals': ['False'], 'group': 0}
@@ -421,13 +422,13 @@ def ROSCO_Controller_Comp(self, controller_paths, testtype='light', more_case_in
         '''
         # Save initial run directory
         run_dir_init = self.runDir
-        wind_dir_init = self.windDir
+        wind_dir_init = self.wind_dir
         for ci, path in enumerate(controller_paths):
             # specify rosco path
             self.rosco_path = path
             # temporarily change run directories
             self.runDir = os.path.join(run_dir_init,'controller_{}'.format(ci)) # specific directory for each controller
-            self.windDir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
+            self.wind_dir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
 
             if testtype.lower() == 'light':
                 self.ROSCO_Test_lite(more_case_inputs=more_case_inputs, U=U)
@@ -438,7 +439,7 @@ def ROSCO_Controller_Comp(self, controller_paths, testtype='light', more_case_in
 
         # reset self
         self.runDir = run_dir_init
-        self.windDir = wind_dir_init
+        self.wind_dir = wind_dir_init
     
     def ROSCO_DISCON_Comp(self, DISCON_filenames, testtype='light', more_case_inputs={}, U=[]):
         '''
@@ -454,15 +455,15 @@ def ROSCO_DISCON_Comp(self, DISCON_filenames, testtype='light', more_case_inputs
 
         # Save initial run directory
         run_dir_init = self.runDir
-        wind_dir_init = self.windDir
+        wind_dir_init = self.wind_dir
         for ci, discon in enumerate(DISCON_filenames):
             # temporarily change run directories
             self.runDir = os.path.join(run_dir_init, 'controller_{}'.format(ci))
-            self.windDir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
+            self.wind_dir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
 
             # Point to different DISCON.IN files using more_case_inputs
             more_case_inputs[('ServoDyn', 'DLL_InFile')] = {'vals': [discon], 'group': 0}
-            self.windDir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
+            self.wind_dir = os.path.join(run_dir_init, 'wind')  # wind in base runDir
 
             if testtype.lower() == 'light':
                 self.ROSCO_Test_lite(more_case_inputs=more_case_inputs, U=U)
@@ -473,20 +474,42 @@ def ROSCO_DISCON_Comp(self, DISCON_filenames, testtype='light', more_case_inputs
 
         # reset self
         self.runDir = run_dir_init
-        self.windDir = wind_dir_init
+        self.wind_dir = wind_dir_init
 
     def print_results(self,outfiles):
 
+        figs_fname = 'test_outputs.pdf'
+
+        # remove initial transients if more than two minutes if simulation time
+        if self.TMax > 120:
+            tmin = 60
+        else:
+            tmin = 0
+
+        # Comparison plot
+        if self.comp_dir:
+            tmin = 100      # if comparing, I'd like to start the comparison at 100 seconds
+            op_comp = output_processing.output_processing()
+            outfiles_comp = [os.path.join(self.comp_dir,os.path.split(of)[1]) for of in outfiles]
+            try:
+                FAST_Comp = op_comp.load_fast_out(outfiles_comp, tmin=tmin)
+                figs_fname = 'comp_outputs.pdf'
+            except:
+                print('Could not load openfast outputs for comparison, only plotting current test')
+                tmin = 0
+                figs_fname = 'test_outputs.pdf'
+
+
         op = output_processing.output_processing()
-        FAST_Output = op.load_fast_out(outfiles, tmin=0)
+        FAST_Output = op.load_fast_out(outfiles, tmin=tmin)
 
-        figs_fname = 'test_outputs.pdf'        
+                
         with PdfPages(os.path.join(self.runDir,figs_fname)) as pdf:
-            for fast_out in FAST_Output:
+            for i_out, fast_out in enumerate(FAST_Output):
                 if self.FAST_InputFile == 'NREL-5MW.fst':
                     plots2make = {'Baseline': ['Wind1VelX', 'GenPwr', 'RotSpeed', 'BldPitch1', 'GenTq']}
                 else:
-                    plots2make = {'Baseline': ['Wind1VelX', 'GenPwr', 'RotSpeed', 'BldPitch1', 'GenTq','PtfmPitch']}
+                    plots2make = {'Baseline': ['Wind1VelX', 'GenPwr', 'RotSpeed', 'BldPitch1', 'GenTq','PtfmPitch','TwrBsMyt']}
 
                 numplots    = len(plots2make)
                 maxchannels = np.max([len(plots2make[key]) for key in plots2make.keys()])
@@ -498,6 +521,9 @@ def print_results(self,outfiles):
                         subplt = fig.add_subplot(gs0[cid])
                         try:
                             subplt.plot(fast_out['Time'], fast_out[channel])
+                            if self.comp_dir:
+                                subplt.plot(FAST_Comp[i_out]['Time'], FAST_Comp[i_out][channel])
+
                             unit_idx = fast_out['meta']['channels'].index(channel)
                             subplt.set_ylabel('{:^} \n ({:^})'.format(
                                                 channel,
@@ -522,35 +548,36 @@ def print_results(self,outfiles):
 
     ## =================== INITIALIZATION ===================
     # Setup simulation parameters
-    rt.runDir = '/Users/nabbas/Documents/Projects/ROSCO_dev/WSE_updates/WSE_Testing'              # directory for FAST simulations
     rt.namebase = 'IEA-15MW'     # Base name for FAST files 
-    rt.FAST_exe = '/Users/nabbas/Documents/WindEnergyToolbox/WEIS/local/bin/openfast'     # OpenFAST executable path
-    rt.Turbsim_exe = '/Users/nabbas/openfast/install/bin/turbsim_single'   # Turbsim executable path
-    rt.FAST_ver = 'OpenFAST'            # FAST version
-    rt.rosco_path = ['/Users/nabbas/Documents/WindEnergyToolbox/ROSCO/build-master/libdiscon.dylib',
-                    '/Users/nabbas/Documents/WindEnergyToolbox/ROSCO/build-wse/libdiscon.dylib',
-                    ]                   # path to compiled ROSCO controller
-    rt.dev_branch = True                # dev branch of Openfast?
-    rt.debug_level = 2                  # debug level. 0 - no outputs, 1 - minimal outputs, 2 - all outputs
-    rt.overwrite = True                 # overwite fast sims?
-    rt.cores = 4                        # number of cores if multiprocessings
-    rt.mpi_run = False                  # run using mpi
-    rt.mpi_comm_map_down = []           # core mapping for MPI
-    rt.outfile_fmt = 2                  # 1 = .txt, 2 = binary, 3 = both
+    rt.FAST_exe = 'openfast'     # OpenFAST executable path
+    rt.Turbsim_exe = 'turbsim'   # Turbsim executable path
+    rt.FAST_ver = 'OpenFAST'     # FAST version
+    # path to compiled ROSCO controller
+    if platform.system() == 'Windows':
+        rt.rosco_path = os.path.join(os.getcwd(), '../ROSCO/build/libdiscon.dll')
+    elif platform.system() == 'Darwin':
+        rt.rosco_path = os.path.join(os.getcwd(), '../ROSCO/build/libdiscon.dylib')
+    else:
+        rt.rosco_path = os.path.join(os.getcwd(), '../ROSCO/build/libdiscon.so')
+    rt.dev_branch = True         # dev branch of Openfast?
+    rt.debug_level = 2           # debug level. 0 - no outputs, 1 - minimal outputs, 2 - all outputs
+    rt.overwrite = True          # overwite fast sims?
+    rt.cores = 4                 # number of cores if multiprocessings
+    rt.mpi_run = False           # run using mpi
+    rt.mpi_comm_map_down = []    # core mapping for MPI
+    rt.outfile_fmt = 2           # 1 = .txt, 2 = binary, 3 = both
     rt.dev_branch= 'True'
-    # Post Processing Parameters
-    reCrunch = True                     # re-run pCrunch?
 
     # Setup turbine
     rt.Turbine_Class = 'I'
     rt.Turbulence_Class = 'B'
-    rt.FAST_directory = '/Users/nabbas/Documents/WindEnergyToolbox/WEIS/examples/OpenFAST_models/IEA-15-240-RWT/IEA-15-240-RWT-Monopile'
-    rt.FAST_InputFile = 'IEA-15-240-RWT-Monopile.fst'
+    rt.FAST_directory = os.path.join(os.getcwd(), '../Test_Cases/IEA-15-240-RWT-UMaineSemi')
+    rt.FAST_InputFile = 'IEA-15-240-RWT-UMaineSemi.fst'
 
     # Additional inputs 
     # ---- DT for this test! ----
     case_inputs={}
-    case_inputs[('Fst', 'TMax')] = {'vals': [330], 'group': 0}
+    case_inputs[('Fst', 'TMax')] = {'vals': [60], 'group': 0}
     case_inputs[('Fst', 'DT')] = {'vals': [0.01], 'group': 0}
     case_inputs[('Fst', 'CompElast')] = {'vals': [1], 'group': 0}
 
@@ -563,5 +590,5 @@ def print_results(self,outfiles):
     U = [5, 9, 12, 15]
 
     # Run test
-    rt.ROSCO_Controller_Comp(rt.rosco_path, testtype='light', more_case_inputs=case_inputs, U=U)
+    rt.ROSCO_Test_lite(more_case_inputs=case_inputs, U=U)
 
diff --git a/ROSCO_testing/run_Testing.py b/ROSCO_testing/run_Testing.py
index 9434c933..ef201d42 100644
--- a/ROSCO_testing/run_Testing.py
+++ b/ROSCO_testing/run_Testing.py
@@ -7,6 +7,8 @@
 import ROSCO_testing
 import importlib
 
+os.system("taskset -p 0xffffffffffff %d" % os.getpid())
+
 
 def run_testing(turbine2test, testtype, rosco_binaries=[], discon_files=[], **kwargs):
     '''
@@ -83,22 +85,26 @@ def run_testing(turbine2test, testtype, rosco_binaries=[], discon_files=[], **kw
 
     # Setup ROSCO testing parameters
     rt_kwargs = {} 
-    rt_kwargs['runDir']     = os.path.join(this_dir,'results/IEA-15MW')        # directory for FAST simulations
-    rt_kwargs['namebase']   = 'lite_test'     # Base name for FAST files 
-    rt_kwargs['FAST_exe']   = 'openfast'       # OpenFAST executable path
+    rt_kwargs['runDir']     = os.path.join('/scratch/dzalkind/ROSCO_testing','version2.3.0')        # directory for FAST simulations
+    rt_kwargs['namebase']   = 'heavy_test'     # Base name for FAST files 
+    rt_kwargs['FAST_exe']   = '/home/dzalkind/Tools/openfast-main/install/bin/openfast'       # OpenFAST executable path
+    rt_kwargs['wind_dir']   = os.path.join('/scratch/dzalkind/ROSCO_testing','wind','IEA-15_heavy')       # OpenFAST executable path
     rt_kwargs['Turbsim_exe']= 'turbsim'    # Turbsim executable path
     rt_kwargs['FAST_ver']   = 'OpenFAST'            # FAST version
     rt_kwargs['dev_branch'] = True                  # dev branch of Openfast?
     rt_kwargs['debug_level']= 2                     # debug level. 0 - no outputs, 1 - minimal outputs, 2 - all outputs
     rt_kwargs['overwrite']  = False                 # overwite fast sims?
-    rt_kwargs['cores']      = 4                     # number of cores if multiprocessing
+    rt_kwargs['cores']      = 36                     # number of cores if multiprocessing
     rt_kwargs['mpi_run']    = False                 # run using mpi
     rt_kwargs['mpi_comm_map_down'] = []             # core mapping for MPI
     rt_kwargs['outfile_fmt'] = 2                    # 1 = .txt, 2 = binary, 3 = both
+    # rt_kwargs['comp_dir']   = '/projects/ssc/dzalkind/ROSCO/ROSCO_testing/version2.2.0'                    # 1 = .txt, 2 = binary, 3 = both
+    rt_kwargs['comp_dir']   = '/scratch/dzalkind/ROSCO_testing/pr_46'                    # 1 = .txt, 2 = binary, 3 = both
+    
 
     # ---- Define test type ----
     turbine2test = 'IEA-15MW'   # IEA-15MW or NREL-5MW
-    testtype     = 'lite'       # lite, heavy, binary-comp, discon-comp
+    testtype     = 'heavy'       # lite, heavy, binary-comp, discon-comp
 
     # Only fill one of these if comparing controllers
     rosco_binaries = [glob.glob(os.path.join(this_dir,'../ROSCO/build/libdiscon.*'))[0]] # Differently named libdiscons to compare
diff --git a/ROSCO_testing/submit.sh b/ROSCO_testing/submit.sh
index 182cf9a0..c32de7bd 100644
--- a/ROSCO_testing/submit.sh
+++ b/ROSCO_testing/submit.sh
@@ -14,13 +14,15 @@ nOF=60  # Number of openfast runs per finite-difference evaluation
 nC=$((nDV + nDV * nOF)) # Number of cores needed. Make sure to request an appropriate number of nodes = N / 36
 ## nC=72
 
-source activate /home/dzalkind/.conda-envs/weis-env4
-which python
+# module load conda
+# conda activate rt-env
+source activate /home/dzalkind/.conda-envs/rosco-env
+# which python
 
 # module purge
 # module load conda
 # module load comp-intel intel-mpi mkl
 
 
-python run_Testing.py
+mpirun -n 36 python run_Testing.py
 #  python weis_driver.py