Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inverse design #139

Merged
merged 19 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3e2201b
Squashed 'WISDEM/' changes from 52306ecd8..14433a82c
johnjasa Jul 6, 2021
fccc825
Updating WISDEM for inverse design capabilities
johnjasa Jul 6, 2021
20a1ade
Squashed 'WISDEM/' changes from 14433a82c..3cb08c0b2
johnjasa Jul 19, 2021
25c795f
Updating WISDEM
johnjasa Jul 19, 2021
aaaa9d4
Updating WISDEM
johnjasa Jul 20, 2021
f407930
Squashed 'WISDEM/' changes from 3cb08c0b2..3396e6725
johnjasa Jul 20, 2021
8652805
resolving conflicts
dakotaramos Jul 20, 2021
510d042
resolving conflicts
dakotaramos Jul 20, 2021
f3646cc
Adding transition piece cost and mass to floating
dakotaramos Jul 22, 2021
f08c616
Attempt to adding transition piece mass and cost
dakotaramos Jul 22, 2021
2777046
fixing merge conflicts
dakotaramos Jul 22, 2021
9df10c5
place holder for variable ballast inertia calc
dakotaramos Jul 23, 2021
19de3b5
Added skeletal structure for total I computation with variable ballas…
johnjasa Jul 23, 2021
7b86b64
draft for computing I_total with variable ballast and transition piec…
dakotaramos Jul 24, 2021
efb35b7
Squashed 'WISDEM/' changes from 3396e672..63f9512f
gbarter Jul 26, 2021
25dd4db
Merge commit 'efb35b7bf25d91c4cf7beae7aed3e18bcc3aaee5' into dakota-i…
gbarter Jul 26, 2021
eff0a64
Updating platform_center_of_mass name and fixing ring_stiffener dimen…
johnjasa Jul 26, 2021
9dd86f3
Squashed 'WISDEM/' changes from 63f9512f..485a20f3
gbarter Jul 27, 2021
2c11070
Merge commit '9dd86f3ba049a30b6d5c3c5da58c001ce38e6403' into dakota-i…
gbarter Jul 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions WISDEM/examples/09_floating/IEA-15-240-RWT_VolturnUS-S.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,8 @@ components:
joint2: col3_lower_pontoon
outer_shape: *pontlow_out
internal_structure: *pontlow_int
transition_piece_mass: 100.e3
transition_piece_cost: 300.e3
mooring:
nodes:
- name: line1_anchor
Expand Down
50 changes: 50 additions & 0 deletions WISDEM/examples/16_inverse_design/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
general:
folder_output: outputs
fname_output: refturb_output

design_variables:
floating:
joints:
flag: True
z_coordinate:
- names: [spar_keel]
lower_bound: -40.0
upper_bound: -15.0
- names: [spar_freeboard]
lower_bound: -40.0
upper_bound: -15.0
members:
flag: True
groups:
- names: [spar]
ballast:
lower_bound: 1.0
upper_bound: 1e4

mooring:
line_length:
flag: True
lower_bound: 100.0
upper_bound: 1000.0

merit_figure: inverse_design

inverse_design:
floatingse.platform_mass:
ref_value: 6.e6
units: kg
floatingse.mooring_mass:
ref_value: 4.5e5
units: kg
floatingse.system_structural_center_of_mass:
ref_value: [0., -60.]
indices: [1, 2]
units: m

driver:
optimization:
flag: True # Flag to enable optimization
tol: 1.e-4 # Optimality tolerance
solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN'
step_size: 1.e-6 # Step size for finite differencing
form: forward # Finite differencing mode, either forward or central
16 changes: 16 additions & 0 deletions WISDEM/examples/16_inverse_design/inverse_spar_design.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os
import sys
import time

from wisdem.commonse.mpi_tools import MPI
from wisdem.glue_code.runWISDEM import run_wisdem

## File management
run_dir = os.path.dirname(os.path.realpath(__file__)) + os.sep
wisdem_examples = os.path.dirname(os.path.dirname(run_dir))
fname_wt_input_oc3 = os.path.join(wisdem_examples, "09_floating", "nrel5mw-spar_oc3.yaml")
fname_modeling_options = os.path.join(wisdem_examples, "09_floating", "modeling_options_noRNA.yaml")
fname_analysis_options = run_dir + os.sep + "analysis_options.yaml"


wt_opt, modeling_options, opt_options = run_wisdem(fname_wt_input_oc3, fname_modeling_options, fname_analysis_options)
128 changes: 98 additions & 30 deletions WISDEM/wisdem/floatingse/floating_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
RIGID = 1e30
EPS = 1e-6

# TODO:
# - Added mass, hydro stiffness for tower sim


class PlatformFrame(om.ExplicitComponent):
def initialize(self):
Expand Down Expand Up @@ -58,6 +55,12 @@ def setup(self):
self.add_input(f"member{k}:Pz", np.zeros(MEMMAX), units="N/m")
self.add_input(f"member{k}:qdyn", np.zeros(MEMMAX), units="Pa")

self.add_input("transition_node", np.zeros(3), units="m")
self.add_input("transition_piece_mass", 0.0, units="kg")
self.add_input("transition_piece_cost", 0.0, units="USD")

self.add_output("transition_piece_I", np.zeros(6), units="kg*m**2")

self.add_output("platform_nodes", NULL * np.ones((NNODES_MAX, 3)), units="m")
self.add_output("platform_Fnode", NULL * np.ones((NNODES_MAX, 3)), units="N")
self.add_output("platform_Rnode", NULL * np.ones(NNODES_MAX), units="m")
Expand Down Expand Up @@ -85,25 +88,24 @@ def setup(self):
self.add_discrete_output("platform_elem_memid", [-1] * NELEM_MAX)
self.add_output("platform_displacement", 0.0, units="m**3")
self.add_output("platform_center_of_buoyancy", np.zeros(3), units="m")
self.add_output("platform_center_of_mass", np.zeros(3), units="m")
self.add_output("platform_hull_center_of_mass", np.zeros(3), units="m")
self.add_output("platform_centroid", np.zeros(3), units="m")
self.add_output("platform_ballast_mass", 0.0, units="kg")
self.add_output("platform_hull_mass", 0.0, units="kg")
self.add_output("platform_mass", 0.0, units="kg")
self.add_output("platform_I_total", np.zeros(6), units="kg*m**2")
self.add_output("platform_I_hull", np.zeros(6), units="kg*m**2")
self.add_output("platform_cost", 0.0, units="USD")
self.add_output("platform_Awater", 0.0, units="m**2")
self.add_output("platform_Iwater", 0.0, units="m**4")
self.add_output("platform_added_mass", np.zeros(6), units="kg")
self.add_output("platform_variable_capacity", np.zeros(n_member), units="m**3")

self.node_mem2glob = {}
# self.node_glob2mem = {}

def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
# This shouldn't change during an optimization, so save some time?
if len(self.node_mem2glob) == 0:
self.set_connectivity(inputs, outputs)
# Seems like we have to run this each time as numbering can change during optimization
self.node_mem2glob = {}
self.set_connectivity(inputs, outputs)

self.set_node_props(inputs, outputs)
self.set_element_props(inputs, outputs, discrete_inputs, discrete_outputs)
Expand Down Expand Up @@ -157,7 +159,9 @@ def set_node_props(self, inputs, outputs):
n_member = opt["floating"]["members"]["n_members"]

# Number of valid nodes
nnode = np.where(outputs["platform_nodes"][:, 0] == NULL)[0][0]
node_platform = outputs["platform_nodes"]
nnode = np.where(node_platform[:, 0] == NULL)[0][0]
node_platform = node_platform[:nnode, :]

# Find greatest radius of all members at node intersections
Rnode = np.zeros(nnode)
Expand All @@ -175,6 +179,13 @@ def set_node_props(self, inputs, outputs):
iglob = self.node_mem2glob[(k, icb)]
Fnode[iglob, 2] += inputs[f"member{k}:buoyancy_force"]

# Get transition piece inertial properties
itrans_platform = util.closest_node(node_platform, inputs["transition_node"])
m_trans = float(inputs["transition_piece_mass"])
r_trans = Rnode[itrans_platform]
I_trans = m_trans * r_trans ** 2.0 * np.r_[0.5, 0.5, 1.0, np.zeros(3)]
outputs["transition_piece_I"] = I_trans

# Store outputs
outputs["platform_Rnode"] = NULL * np.ones(NNODES_MAX)
outputs["platform_Rnode"][:nnode] = Rnode
Expand Down Expand Up @@ -265,13 +276,21 @@ def set_element_props(self, inputs, outputs, discrete_inputs, discrete_outputs):
cg_plat += imass * inputs[f"member{k}:center_of_mass"]
cb_plat += ivol * inputs[f"member{k}:center_of_buoyancy"]

# Add transition piece
m_trans = inputs["transition_piece_mass"]
cg_trans = inputs["transition_node"]
I_trans = util.assembleI(outputs["transition_piece_I"])
mass += m_trans
cost += inputs["transition_piece_cost"]
cg_plat += m_trans * cg_trans

# Finalize outputs
cg_plat /= mass
cb_plat /= volume

# With CG known, loop back through to compute platform I
unit_z = np.array([0.0, 0.0, 1.0])
I_total = np.zeros((3, 3))
I_hull = np.zeros((3, 3))
for k in range(n_member):
xyz_k = inputs[f"member{k}:nodes_xyz"]
inodes = np.where(xyz_k[:, 0] == NULL)[0][0]
Expand All @@ -287,10 +306,14 @@ def set_element_props(self, inputs, outputs, discrete_inputs, discrete_outputs):

# Rotate member inertia tensor
I_k = util.assembleI(inputs[f"member{k}:I_total"])
I_k2 = T @ I_k @ T.T
I_k_rot = T @ I_k @ T.T

# Now do parallel axis theorem
I_total += np.array(I_k2) + imass * (np.dot(R, R) * np.eye(3) - np.outer(R, R))
I_hull += np.array(I_k_rot) + imass * (np.dot(R, R) * np.eye(3) - np.outer(R, R))

# Add in transition piece
R = cg_plat - cg_trans
I_hull += I_trans + m_trans * (np.dot(R, R) * np.eye(3) - np.outer(R, R))

# Store outputs
nelem = elem_A.size
Expand Down Expand Up @@ -340,9 +363,9 @@ def set_element_props(self, inputs, outputs, discrete_inputs, discrete_outputs):
outputs["platform_hull_mass"] = mass - m_ball
outputs["platform_cost"] = cost
outputs["platform_displacement"] = volume
outputs["platform_center_of_mass"] = cg_plat
outputs["platform_hull_center_of_mass"] = cg_plat
outputs["platform_center_of_buoyancy"] = cb_plat
outputs["platform_I_total"] = util.unassembleI(I_total)
outputs["platform_I_hull"] = util.unassembleI(I_hull)
outputs["platform_Awater"] = Awater
outputs["platform_Iwater"] = Iwater
outputs["platform_added_mass"] = m_added
Expand All @@ -357,7 +380,8 @@ def setup(self):

def compute(self, inputs, outputs):
transition_node = inputs["transition_node"]
tower_top_node = transition_node
tower_top_node = 0 #previous code altered the original definition of transition_node
tower_top_node += transition_node
tower_top_node[2] += float(inputs["tower_height"])
outputs["tower_top_node"] = tower_top_node

Expand Down Expand Up @@ -395,8 +419,9 @@ def setup(self):
self.add_input("platform_elem_Pz1", NULL * np.ones(NELEM_MAX), units="N/m")
self.add_input("platform_elem_Pz2", NULL * np.ones(NELEM_MAX), units="N/m")
self.add_input("platform_elem_qdyn", NULL * np.ones(NELEM_MAX), units="Pa")
self.add_input("platform_center_of_mass", np.zeros(3), units="m")
self.add_input("platform_hull_center_of_mass", np.zeros(3), units="m")
self.add_input("platform_mass", 0.0, units="kg")
self.add_input("platform_I_hull", np.zeros(6), units="kg*m**2")
self.add_input("platform_displacement", 0.0, units="m**3")

self.add_input("tower_nodes", NULL * np.ones((MEMMAX, 3)), units="m")
Expand Down Expand Up @@ -433,7 +458,6 @@ def setup(self):
self.add_input("rho_water", 0.0, units="kg/m**3")
self.add_input("tower_top_node", np.zeros(3), units="m")
self.add_input("transition_node", np.zeros(3), units="m")
self.add_input("transition_piece_mass", 0.0, units="kg")
self.add_input("rna_mass", 0.0, units="kg")
self.add_input("rna_cg", np.zeros(3), units="m")
self.add_input("mooring_neutral_load", np.zeros((n_attach, 3)), units="N")
Expand Down Expand Up @@ -478,7 +502,8 @@ def setup(self):
self.add_output("constr_variable_margin", val=0.0)
self.add_output("member_variable_volume", val=np.zeros(n_member), units="m**3")
self.add_output("member_variable_height", val=np.zeros(n_member))
self.add_output("transition_piece_I", np.zeros(6), units="kg*m**2")
self.add_output("platform_total_center_of_mass", np.zeros(3), units="m")
self.add_output("platform_I_total", np.zeros(6), units="kg*m**2")

def compute(self, inputs, outputs):
# Combine nodes
Expand Down Expand Up @@ -576,17 +601,17 @@ def compute(self, inputs, outputs):

# Mass summaries
m_platform = inputs["platform_mass"]
cg_platform = inputs["platform_hull_center_of_mass"]
I_platform = util.assembleI(inputs["platform_I_hull"])
m_tower = inputs["tower_mass"]
m_rna = inputs["rna_mass"]
m_trans = inputs["transition_piece_mass"]
m_sys = m_platform + m_tower + m_rna + m_trans
m_sys = m_platform + m_tower + m_rna
outputs["system_structural_mass"] = m_sys

outputs["system_structural_center_of_mass"] = (
m_platform * inputs["platform_center_of_mass"]
m_platform * cg_platform
+ m_tower * inputs["tower_center_of_mass"]
+ m_rna * (inputs["rna_cg"] + inputs["tower_top_node"])
+ m_trans * inputs["transition_node"]
) / m_sys

# Balance out variable ballast
Expand All @@ -600,6 +625,7 @@ def compute(self, inputs, outputs):
outputs["constr_variable_margin"] = V_variable / capacity_sum
V_variable_member = V_variable * capacity / capacity_sum
outputs["member_variable_volume"] = V_variable_member
m_variable_member = V_variable_member * rho_water

# Now find the CG of the variable mass assigned to each member
n_member = capacity.size
Expand Down Expand Up @@ -632,11 +658,51 @@ def compute(self, inputs, outputs):
m_sys * outputs["system_structural_center_of_mass"] + m_variable * cg_variable
) / (m_sys + m_variable)

# Transition piece properties
m_trans = float(inputs["transition_piece_mass"])
r_trans = inputs["platform_Rnode"][itrans_platform]
I_trans = m_trans * r_trans ** 2.0 * np.r_[0.5, 0.5, 1.0, np.zeros(3)]
outputs["transition_piece_I"] = I_trans
# Compute the total cg for the platform and the variable ballast together using a weighted sum approach
cg_plat_total = (m_variable * cg_variable + m_platform * cg_platform) / (m_variable + m_platform)
outputs["platform_total_center_of_mass"] = cg_plat_total

# Now loop again to compute variable I
unit_z = np.array([0.0, 0.0, 1.0])
I_variable = np.zeros((3, 3))
for k in range(n_member):
if V_variable_member[k] == 0.0:
continue

xyz = inputs[f"member{k}:nodes_xyz"]
inodes = np.where(xyz[:, 0] == NULL)[0][0]
xyz = xyz[:inodes, :]
vec_k = xyz[-1, :] - xyz[0, :]

ds = outputs["member_variable_height"][k]

# Compute I aligned with member
h_k = ds * np.sqrt(np.sum(vec_k ** 2))
if h_k == 0.0:
continue
r_k = np.sqrt(V_variable_member[k] / h_k / np.pi)
I_k = (
m_variable_member[k] * np.r_[(3 * r_k ** 2 + h_k ** 2) / 12.0 * np.ones(2), 0.5 * r_k ** 2, np.ones(3)]
)

# Rotate I to global c.s.
T = util.rotate_align_vectors(vec_k, unit_z)
I_k_rot = T @ util.assembleI(I_k) @ T.T

# Now do parallel axis theorem
R = cg_variable - cg_variable_member[k, :]
I_variable += np.array(I_k_rot) + m_variable_member[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R))

# Find platform I with variable contribution
I_total = np.zeros((3, 3))

# Compute the full moment of inertia for the platform and variable ballast
R = cg_plat_total - cg_platform
I_total += I_platform + m_platform * (np.dot(R, R) * np.eye(3) - np.outer(R, R))

R = cg_plat_total - cg_variable
I_total += I_variable + m_variable * (np.dot(R, R) * np.eye(3) - np.outer(R, R))
outputs["platform_I_total"] = util.unassembleI(I_total)


class FrameAnalysis(om.ExplicitComponent):
Expand All @@ -648,7 +714,7 @@ def setup(self):
n_attach = opt["mooring"]["n_attach"]

self.add_input("platform_mass", 0.0, units="kg")
self.add_input("platform_center_of_mass", np.zeros(3), units="m")
self.add_input("platform_hull_center_of_mass", np.zeros(3), units="m")
self.add_input("platform_added_mass", np.zeros(6), units="kg")
self.add_input("platform_center_of_buoyancy", np.zeros(3), units="m")
self.add_input("platform_displacement", 0.0, units="m**3")
Expand Down Expand Up @@ -818,8 +884,9 @@ def compute(self, inputs, outputs):
m_trans = float(inputs["transition_piece_mass"])
if frame == "tower":
m_trans += float(inputs["platform_mass"]) + inputs["platform_added_mass"][0] + m_variable
cg_trans = inputs["transition_node"] - inputs["platform_center_of_mass"]
cg_trans = inputs["transition_node"] - inputs["platform_hull_center_of_mass"]
I_trans[:3] += inputs["platform_added_mass"][3:]

else:
m_trans += m_variable
cg_trans = np.zeros(3)
Expand Down Expand Up @@ -885,6 +952,7 @@ def compute(self, inputs, outputs):
# Add the load case and run
myframe.addLoadCase(load_obj)
# myframe.write(f"{frame}.3dd")
# myframe.draw()
displacements, forces, reactions, internalForces, mass, modal = myframe.run()

# natural frequncies
Expand Down
8 changes: 6 additions & 2 deletions WISDEM/wisdem/glue_code/gc_PoseOptimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,8 @@ def set_objective(self, wt_opt):
else:
wt_opt.model.add_objective("floatingse.tower_mass", ref=1e6)

elif self.opt["merit_figure"] == "mononpile_mass":
wt_opt.model.add_objective("towerse.mononpile_mass", ref=1e6)
elif self.opt["merit_figure"] == "monopile_mass":
wt_opt.model.add_objective("towerse.monopile_mass", ref=1e6)

elif self.opt["merit_figure"] == "structural_mass":
if not self.modeling["flags"]["floating"]:
Expand Down Expand Up @@ -470,6 +470,10 @@ def set_objective(self, wt_opt):
wt_opt.model.add_objective("rotorse.rp.powercurve.Cp_regII", ref=-1.0)
else:
wt_opt.model.add_objective("rotorse.ccblade.CP", ref=-1.0)

elif self.opt["merit_figure"] == "inverse_design":
wt_opt.model.add_objective("inverse_design.objective")

else:
raise ValueError("The merit figure " + self.opt["merit_figure"] + " is unknown or not supported.")

Expand Down
Loading