Skip to content
27 changes: 22 additions & 5 deletions ard/collection/optiwindnet_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@
import numpy as np

from optiwindnet.mesh import make_planar_embedding
from optiwindnet.interarraylib import L_from_site
from optiwindnet.interarraylib import L_from_site, calcload
from optiwindnet.heuristics import EW_presolver
from optiwindnet.MILP import OWNWarmupFailed, solver_factory, ModelOptions

from . import templates


def _S_from_terse_links(terse_links, **kwargs):
T = terse_links.shape[0]
S = nx.Graph(T=T, R=abs(terse_links.min()), **kwargs)
S.add_edges_from(tuple(zip(range(T), terse_links)))
calcload(S)
if "capacity" not in kwargs:
S.graph["capacity"] = S.graph["max_load"]
return S


def _own_L_from_inputs(inputs: dict, discrete_inputs: dict) -> nx.Graph:
# get the metadata and data for the OWN warm-starter from the inputs
T = len(inputs["x_turbines"])
Expand Down Expand Up @@ -122,6 +132,9 @@ class OptiwindnetCollection(templates.CollectionTemplate):
-------
total_length_cables : float
the total length of cables used in the collection system network
terse_links : np.ndarray
a 1D numpy int array encoding the electrical connections of the collection
system (tree topology), with length `N_turbines`

Discrete Outputs
-------
Expand All @@ -134,9 +147,6 @@ class OptiwindnetCollection(templates.CollectionTemplate):
length `N_turbines`
max_load_cables : int
the maximum cable capacity required by the collection system
terse_links : np.ndarray
a 1D numpy int array encoding the electrical connections of the collection
system (tree topology), with length `N_turbines`
"""

def initialize(self):
Expand All @@ -156,6 +166,13 @@ def setup_partials(self):
["x_turbines", "y_turbines", "x_substations", "y_substations"],
method="exact",
)
self.declare_partials(
["terse_links"],
["x_turbines", "y_turbines", "x_substations", "y_substations"],
method="exact",
val=0.0,
dependent=False,
)

def compute(
self,
Expand Down Expand Up @@ -254,14 +271,14 @@ def compute(
# pack and ship
self.graph = G
discrete_outputs["graph"] = G # TODO: remove for terse links, below!
discrete_outputs["terse_links"] = terse_links
discrete_outputs["length_cables"] = length_cables
discrete_outputs["load_cables"] = load_cables
discrete_outputs["max_load_cables"] = S.graph["max_load"]
# TODO: remove this assert after enough testing
assert (
abs(length_cables.sum() - G.size(weight="length")) < 1e-7
), f"difference: {length_cables.sum() - G.size(weight='length')}"
outputs["terse_links"] = terse_links
outputs["total_length_cables"] = length_cables.sum()

def compute_partials(self, inputs, J, discrete_inputs=None):
Expand Down
2 changes: 1 addition & 1 deletion ard/collection/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ def setup(self):

# set up outputs for the collection system
self.add_output("total_length_cables", 0.0, units="m")
self.add_output("terse_links", np.full((self.N_turbines,), -1))
self.add_discrete_output("length_cables", np.zeros((self.N_turbines,)))
self.add_discrete_output("terse_links", np.full((self.N_turbines,), -1))
self.add_discrete_output("load_cables", np.zeros((self.N_turbines,)))
self.add_discrete_output("max_load_cables", 0.0)
self.add_discrete_output("graph", None)
Expand Down
32 changes: 27 additions & 5 deletions ard/cost/orbit_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
from ORBIT.core.library import default_library
from ORBIT.core.library import initialize_library

from ard.collection.optiwindnet_wrap import _S_from_terse_links
from ard.collection.optiwindnet_wrap import _own_L_from_inputs
from ard.cost.wisdem_wrap import ORBIT_setup_latents


def generate_orbit_location_from_graph(
graph, # TODO: replace with a terse_links representation
terse_links,
X_turbines,
Y_turbines,
X_substations,
Expand Down Expand Up @@ -57,6 +59,10 @@ def generate_orbit_location_from_graph(
if the recursive setup seems to be stuck in a loop
"""

# create graph from terse links
tlm = np.astype(terse_links, np.int_)
graph = _S_from_terse_links(tlm)

# get all edges, sorted by the first node then the second node
edges_to_process = [edge for edge in graph.edges]
edges_to_process.sort(key=lambda x: (x[0], x[1]))
Expand Down Expand Up @@ -209,7 +215,9 @@ def initialize(self):
super().initialize()

self.options.declare("case_title", default="working")
self.options.declare("modeling_options")
self.options.declare(
"modeling_options", types=dict, desc="Ard modeling options"
)
self.options.declare("approximate_branches", default=False)

def setup(self):
Expand Down Expand Up @@ -287,7 +295,7 @@ def setup(self):
self.N_substations = self.modeling_options["layout"]["N_substations"]

# bring in collection system design
self.add_discrete_input("graph", None)
self.add_input("terse_links", np.full((self.N_turbines,), -1))

# add the detailed turbine and substation locations
self.add_input("x_turbines", np.zeros((self.N_turbines,)), units="km")
Expand Down Expand Up @@ -359,7 +367,7 @@ def compile_orbit_config_file(

# generate the csv data needed to locate the farm elements
generate_orbit_location_from_graph(
discrete_inputs["graph"],
inputs["terse_links"],
inputs["x_turbines"],
inputs["y_turbines"],
inputs["x_substations"],
Expand Down Expand Up @@ -430,7 +438,7 @@ def setup(self):
"total_capex_kW",
"bos_capex",
"installation_capex",
"graph",
"terse_links",
"x_turbines",
"y_turbines",
"x_substations",
Expand All @@ -443,3 +451,17 @@ def setup(self):
# connect
for key in variable_mapping.keys():
self.connect(key, f"orbit.{key}")

def setup_partials(self):

self.declare_partials(
"*",
"*",
method="fd",
step=1.0e-5,
form="central",
step_calc="rel_avg",
)
self.declare_partials(
"terse_links", "*", method="exact", val=0.0, dependent=False
)
2 changes: 1 addition & 1 deletion test/ard/unit/collection/test_optiwindnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def test_modeling(self, subtests):
# make sure that the outputs in the component match what we planned
output_list = [k for k, v in self.collection.list_outputs()]
for var_to_check in [
"terse_links",
"total_length_cables",
]:
assert var_to_check in output_list
Expand All @@ -141,7 +142,6 @@ def test_modeling(self, subtests):
"length_cables",
"load_cables",
"max_load_cables",
"terse_links",
]:
assert var_to_check in discrete_output_list

Expand Down
5 changes: 2 additions & 3 deletions test/ard/unit/collection/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def test_setup(self):
for var_to_check in [
"length_cables",
"load_cables",
"terse_links",
"total_length_cables",
"max_load_cables",
]:
Expand All @@ -87,9 +88,7 @@ def test_setup(self):
discrete_output_list = [
k for k, v in self.coll_temp._discrete_outputs.items()
]
for var_to_check in [
"terse_links",
]:
for var_to_check in []:
assert var_to_check in discrete_output_list

def test_compute(self):
Expand Down
6 changes: 3 additions & 3 deletions test/ard/unit/cost/test_orbit_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def test_raise_error(self):
"y_substations",
],
)
model.connect("collection.graph", "orbit.graph")
model.connect("collection.terse_links", "orbit.terse_links")

model.set_input_defaults(
"x_turbines", modeling_options["layout"]["x_turbines"], units="km"
Expand Down Expand Up @@ -328,7 +328,7 @@ def test_baseline_farm(self, subtests):
"y_substations",
],
)
model.connect("collection.graph", "orbit.graph")
model.connect("collection.terse_links", "orbit.terse_links")

model.set_input_defaults(
"x_turbines", modeling_options["layout"]["x_turbines"], units="km"
Expand Down Expand Up @@ -525,7 +525,7 @@ def setup_method(self):
"y_substations",
],
)
self.model.connect("collection.graph", "orbit.graph")
self.model.connect("collection.terse_links", "orbit.terse_links")

self.model.set_input_defaults(
"x_turbines", self.modeling_options["layout"]["x_turbines"], units="km"
Expand Down
Loading