From 5fe42eb942e31f6d843494fdf31902a539a2ca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20A=2E=20Michel=C3=A9n=20Str=C3=B6fer?= Date: Wed, 12 Jul 2023 15:08:13 -0600 Subject: [PATCH] part 1 only --- examples/tutorial_4_Pioneer.ipynb | 357 +----------------------------- 1 file changed, 3 insertions(+), 354 deletions(-) diff --git a/examples/tutorial_4_Pioneer.ipynb b/examples/tutorial_4_Pioneer.ipynb index 2b7c4639..0551e575 100644 --- a/examples/tutorial_4_Pioneer.ipynb +++ b/examples/tutorial_4_Pioneer.ipynb @@ -17,8 +17,8 @@ "This tutorial is divided into three parts. The first sets up the problem including the points above. The second and third show results using a regular and irregular wave, respectively.\n", "\n", "1. [Model setup](#1-model-setup)\n", - "2. [Regular wave results](#2-regular-wave-results)\n", - "3. [Irregular wave results](#3-irregular-wave)\n", + "\n", "\n", "\n", "

\"Diagram

" @@ -617,357 +617,6 @@ " uniform_shift=False,\n", " dof_names=bem_data.influenced_dof.values,)" ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Regular wave results\n", - "### 2.1 Solve\n", - "As always, we will optimize for electrical power absorption." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "obj_fun = average_electrical_power\n", - "results = wec.solve(\n", - " waves_regular,\n", - " obj_fun,\n", - " nstate_opt,\n", - " scale_x_wec=1e1,\n", - " scale_x_opt=1e-2,\n", - " scale_obj=1e-2,\n", - ")\n", - "print(f'Optimal average power: {results.fun:.2f} W')" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.2 Post-process and plot\n", - "Again, since we're not using the `PTO` module, post-processing using `wot.pto.PTO.post_process` is not an option here, so we have to manually post-process the outputs related to the PTO and flywheel. This is pretty intuitive using the functions we created earlier. The outputs related to the buoy can still be derived directly from `wot.wec.post_process`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nsubsteps = 5\n", - "wec_fdom, wec_tdom = wec.post_process(results, waves_regular, nsubsteps=nsubsteps)\n", - "\n", - "# Manually post-process PTO and flywheel outputs\n", - "x_wec, x_opt = wot.decompose_state(results.x, 1, nfreq)\n", - "fw_pos = np.dot(wec.time_mat_nsubsteps(nsubsteps), x_opt[nstate_pto:])\n", - "pto_pos = rel_position(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "pto_vel = rel_velocity(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "pto_force = f_motor(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "pto_force_fd = wec.td_to_fd(pto_force[::nsubsteps])\n", - "pto_mech_power = mechanical_power(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "pto_elec_power = electrical_power(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "avg_mech_power = np.mean(pto_mech_power)\n", - "avg_elec_power = np.mean(pto_elec_power)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some observations about the optimized Pioneer model:\n", - "\n", - " * The flywheel and buoy match frequency, but are out of phase due to the forcing from the spring and motor applied to both bodies.\n", - " * The gearing in series with the torsional spring (which reduces the effective spring stiffness) significantly amplifies the rotation of the flywheel despite the buoy only pitching modestly. The gear ratio of 3 was selected by the Pioneer team to keep the system in resonance considering the spring stiffness, moment of inertia of the flywheel, and the resonance frequency of the buoy. Try increasing the gear ratio or flywheel moment of inertia -- this will actually reduce the power generated since it will bring the effective stiffness of the flywheel out of resonance, causing less motion relative to the buoy.\n", - " * The mechanical and electrical power outputs are similar, since our impedance model has only a small amount of resistance and no inductance.\n", - " * The buoy's pitch amplitude is larger than expected for this device and is likely due to underestimation of the radiation damping by the BEM. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(nrows=4, sharex=True, figsize=(12, 12))\n", - "t = wec_tdom.time.values\n", - "\n", - "# Positions\n", - "ax[0].plot(t, fw_pos*180/np.pi, label='Flywheel', c='k')\n", - "ax[0].plot(t, pto_pos*180/np.pi, label='PTO', c='k', ls='--')\n", - "ax[0].set_title('Pitch Displacements')\n", - "ax[0].set_ylabel('Position [deg]')\n", - "ax[0].legend()\n", - "\n", - "ax0 = ax[0].twinx()\n", - "ax0.tick_params(axis='y', labelcolor='b')\n", - "(wec_tdom.pos*180/np.pi).plot(hue='influenced_dof', label='Buoy', ax=ax0, c='b')\n", - "ax0.set_ylabel('Buoy pos. [deg]', color='b')\n", - "ax0.set_title('')\n", - "\n", - "# Torques\n", - "ax[1].plot(t, pto_force, c='k')\n", - "ax[1].plot([t[0], t[-1]], [torque_peak_max, torque_peak_max], 'k--')\n", - "ax[1].plot([t[0], t[-1]], [-1*torque_peak_max, -1*torque_peak_max], 'k--')\n", - "ax[1].set_ylabel('PTO torque [N-m]')\n", - "ax[1].set_title('Torque')\n", - "\n", - "ax1 = ax[1].twinx()\n", - "ax1.tick_params(axis='y', labelcolor='b')\n", - "wec_tdom['force'].sel(type=['Froude_Krylov', 'diffraction']).sum('type').plot(ax=ax1, c='b')\n", - "ax1.set_ylabel('Excitation torque [N-m]', color='b')\n", - "ax1.set_title('Torque')\n", - "\n", - "# PTO Velocity\n", - "ax[2].plot(t, pto_vel)\n", - "ax[2].set_title('PTO Velocity')\n", - "ax[2].set_ylabel('Velocity [deg/s]')\n", - "\n", - "# Power\n", - "ax[3].plot(t, pto_mech_power, label='Mech. power ($\\\\bar{P}_{mech}$: ' + f'{avg_mech_power:.1f} W)')\n", - "ax[3].plot(t, pto_elec_power, linestyle='dashdot', label='Elec. power ($\\\\bar{P}_{elec}$: ' + f'{avg_elec_power:.1f} W)')\n", - "ax[3].set_title('Power Output')\n", - "ax[3].set_ylabel('Power [W]')\n", - "ax[3].legend()\n", - "ax[3].set_xlabel('Time [s]')\n", - "\n", - "for axi in ax:\n", - " axi.grid()\n", - " axi.label_outer()\n", - " axi.autoscale(axis='x', tight=True)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.3 Sankey Diagram" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use a Sankey diagram to visualize the average power flow from waves to electricity. On the very left is the theoretically possible optimal excitation. In order to reach this upper bound of exitation the WEC pitch velocity would need to be in phase with the pitch excitation force, in this case the radiated power is equal the amount of absorbed mechanical power. In practice, this will usually imply putting electrical power into the system (something we want to avoid!!!). \n", - "With co-design, we are instead tapping the unused potential while limiting PTO losses and radiated power." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "hydro_data = wot.linear_hydrodynamics(bem_data, np.array([[buoy_props['MOI']]]), k_buoy, friction=None) \n", - "hydro_data = wot.check_linear_damping(hydro_data, uniform_shift=False) \n", - "\n", - "Zi = wot.hydrodynamic_impedance(hydro_data)\n", - "Rad_res = np.real(Zi.squeeze())\n", - "\n", - "Fex = wec_fdom.force.sel(type=['Froude_Krylov', 'diffraction']).sum('type')\n", - "Vel = wec_fdom.vel\n", - "\n", - "P_max_absorbable = (np.abs(Fex)**2/(8*Rad_res) ).squeeze().sum('omega').item() # after Falnes Eq. 6.10\n", - "P_opt_excitation = 2*P_max_absorbable # after Falnes Eq. 6.10\n", - "P_radiated = ((1/2)*(Rad_res * np.abs(Vel)**2 ).squeeze().sum('omega').item()) # after Falnes Eq. 6.4\n", - "P_excited= (1/4)*(Fex*np.conjugate(Vel) + np.conjugate(Fex)*Vel ).squeeze().sum('omega').item() # after Falnes Eq. 6.3\n", - "P_absorbed = P_excited - P_radiated # after Falnes Eq. 6.2 absorbed by WEC-PTO (difference between actual excitation power and actual radiated power needs to be absorbed by PTO)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We also calculate the power dissipated due to flywheel friction and make sure that the absorbed power (calculated as the difference between excited and radiated power) matches the sum of mechanical power captured by the PTO and the power dissipated due to flywheel friction. We use a relative tolerance of 1%." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def fw_velocity(wec, x_wec, x_opt, waves, nsubsteps=1):\n", - " pos_fw = wec.vec_to_dofmat(x_opt[nstate_pto:])\n", - " vel_fw = np.dot(wec.derivative_mat, pos_fw)\n", - " time_matrix = wec.time_mat_nsubsteps(nsubsteps)\n", - " return np.dot(time_matrix, vel_fw)\n", - "\n", - "def fw_friction_power(wec, x_wec, x_opt, waves, nsubsteps=1):\n", - " force_td = f_friction(wec, x_wec, x_opt, waves, nsubsteps)\n", - " vel_td = fw_velocity(wec, x_wec, x_opt, waves, nsubsteps)\n", - " return vel_td * force_td\n", - "\n", - "fw_fric_power = fw_friction_power(wec, x_wec, x_opt, waves_regular, nsubsteps)\n", - "avg_fw_fric_power = np.mean(fw_fric_power)\n", - "\n", - "assert(np.isclose(P_absorbed, -1*(avg_mech_power -avg_fw_fric_power), rtol=0.01)) # assert that solver solution matches" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib.sankey import Sankey\n", - "\n", - "P_PTO_loss = avg_mech_power - avg_elec_power \n", - "P_unused = P_opt_excitation - P_excited # Difference between the theoretical optimum excitation, if the WEC velocity would be in resonance with the excitation force\n", - "\n", - "Power_flows = [P_opt_excitation, P_PTO_loss, -1*avg_fw_fric_power, -1*P_radiated, -1*P_unused, avg_elec_power, ]\n", - "\n", - "fig = plt.figure(figsize = [6,4])\n", - "ax = fig.add_subplot(1, 1, 1,)\n", - "sankey = Sankey(ax=ax, \n", - " scale=0.5/P_max_absorbable,\n", - " offset= 0,\n", - " format = '%.2f W',shoulder = 0.02)\n", - "\n", - "sankey.add(flows=Power_flows, \n", - " labels = ['Optimal Excitation \\n $ 2 \\\\frac{\\left [ F_e \\\\right ]^2}{8Re(Z_i)} = 2*P_{mech}^{max}$ ', \n", - " 'PTO-Loss \\n $ P_{mech} - P_{elec}$', \n", - " 'Flywheel friction',\n", - " 'Radiated \\n $ \\\\frac{1}{2} Re(Z_i) \\left [ U \\\\right ]^2 $ ', \n", - " 'Unused Potential \\n(neither absorbed nor radiated)', \n", - " 'Electrical'], \n", - " orientations=[0, -1, -1,-1, -1, 0], # arrow directions\n", - " pathlengths = [0.4,0.2,0.6,0.6,0.7,0.5,],\n", - " trunklength = 1.5,\n", - " edgecolor = '#2b8cbe',\n", - " facecolor = '#2b8cbe' )\n", - "\n", - "diagrams = sankey.finish()\n", - "for diagram in diagrams:\n", - " for text in diagram.texts:\n", - " text.set_fontsize(10);\n", - "plt.axis(\"off\") \n", - "plt.show()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Irregular wave\n", - "### 3.1 Solve\n", - "We will now run the same analysis for irregular waves. \n", - "\n", - "An interesting result is that due to the narrow banded resonance of the flywheel, the controller attempts to make the excitation force monochromatic with the resonant frequency. To achieve this it uses significant reactive power (power by the PTO into the system). This is still worth it, resulting in a larger average electrical power output. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "obj_fun = average_electrical_power\n", - "results = wec.solve(\n", - " waves_irregular,\n", - " obj_fun,\n", - " nstate_opt,\n", - " scale_x_wec=1e1,\n", - " scale_x_opt=1e-2,\n", - " scale_obj=1e-2,\n", - ")\n", - "print(f'Optimal average power: {results.fun:.2f} W')" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.2 Post-process and plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nsubsteps = 5\n", - "wec_fdom, wec_tdom = wec.post_process(results, waves_irregular, nsubsteps=nsubsteps)\n", - "\n", - "# Manually post-process PTO and flywheel outputs\n", - "x_wec, x_opt = wot.decompose_state(results.x, 1, nfreq)\n", - "fw_pos = np.dot(wec.time_mat_nsubsteps(nsubsteps), x_opt[nstate_pto:])\n", - "pto_pos = rel_position(wec, x_wec, x_opt, waves_irregular, nsubsteps)\n", - "pto_vel = rel_velocity(wec, x_wec, x_opt, waves_irregular, nsubsteps)\n", - "pto_force = f_motor(wec, x_wec, x_opt, waves_irregular, nsubsteps)\n", - "pto_force_fd = wec.td_to_fd(pto_force[::nsubsteps])\n", - "pto_mech_power = mechanical_power(wec, x_wec, x_opt, waves_irregular, nsubsteps)\n", - "pto_elec_power = electrical_power(wec, x_wec, x_opt, waves_irregular, nsubsteps)\n", - "avg_mech_power = np.mean(pto_mech_power)\n", - "avg_elec_power = np.mean(pto_elec_power)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(nrows=4, sharex=True, figsize=(12, 12))\n", - "t = wec_tdom.time.values\n", - "\n", - "# Positions\n", - "ax[0].plot(t, fw_pos*180/np.pi, label='Flywheel', c='k')\n", - "ax[0].plot(t, pto_pos*180/np.pi, label='PTO', c='k', ls='--')\n", - "ax[0].set_title('Pitch Displacements')\n", - "ax[0].set_ylabel('Position [deg]')\n", - "ax[0].legend()\n", - "\n", - "ax0 = ax[0].twinx()\n", - "ax0.tick_params(axis='y', labelcolor='b')\n", - "(wec_tdom.pos*180/np.pi).plot(hue='influenced_dof', label='Buoy', ax=ax0, c='b')\n", - "ax0.set_ylabel('Buoy pos. [deg]', color='b')\n", - "ax0.set_title('')\n", - "\n", - "# Torques\n", - "ax[1].plot(t, pto_force, c='k')\n", - "ax[1].plot([t[0], t[-1]], [torque_peak_max, torque_peak_max], 'k--')\n", - "ax[1].plot([t[0], t[-1]], [-1*torque_peak_max, -1*torque_peak_max], 'k--')\n", - "ax[1].set_ylabel('PTO torque [N-m]')\n", - "ax[1].set_title('Torque')\n", - "\n", - "ax1 = ax[1].twinx()\n", - "ax1.tick_params(axis='y', labelcolor='b')\n", - "wec_tdom['force'].sel(type=['Froude_Krylov', 'diffraction']).sum('type').plot(ax=ax1, c='b')\n", - "ax1.set_ylabel('Excitation torque [N-m]', color='b')\n", - "ax1.set_title('Torque')\n", - "\n", - "# PTO Velocity\n", - "ax[2].plot(t, pto_vel)\n", - "ax[2].set_title('PTO Velocity')\n", - "ax[2].set_ylabel('Velocity [deg/s]')\n", - "\n", - "# Power\n", - "ax[3].plot(t, pto_mech_power, label='Mech. power ($\\\\bar{P}_{mech}$: ' + f'{avg_mech_power:.1f} W)')\n", - "ax[3].plot(t, pto_elec_power, linestyle='dashdot', label='Elec. power ($\\\\bar{P}_{elec}$: ' + f'{avg_elec_power:.1f} W)')\n", - "ax[3].set_title('Power Output')\n", - "ax[3].set_ylabel('Power [W]')\n", - "ax[3].legend()\n", - "ax[3].set_xlabel('Time [s]')\n", - "\n", - "for axi in ax:\n", - " axi.grid()\n", - " axi.label_outer()\n", - " axi.autoscale(axis='x', tight=True)" - ] } ], "metadata": { @@ -986,7 +635,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.10" + "version": "3.10.0" } }, "nbformat": 4,