Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ Moreover, the design of any *one* of these aspects affects all the rest!
In brief, we are designing `Ard` to be: principled, modular, extensible, and effective, to allow resource-specific wind farm layout optimization with realistic, well-posed constraints, holistic and complex objectives, and natural incorporation of multiple fidelities and disciplines.

## Documentation

Ard documentation is available at [https://wisdem.github.io/Ard](https://wisdem.github.io/Ard)
Ard documentation is available at [https://NLRWindSystems.github.io/Ard](https://NLRWindSystems.github.io/Ard)

## Installation instructions

Expand Down Expand Up @@ -145,7 +144,7 @@ In this example, the wind farm layout is parametrized with two angles, named ori
Additionally, we have offshore examples adjacent to the onshore example in the `examples` subdirectory.
In the beta pre-release stage, the constituent subcomponents of these problems are known to work and have full testing coverage.

These cases start from a four parameter farm layout, compute land use area, make FLORIS estimates of annual energy production (AEP), compute turbine capital costs, balance-of-station (BOS), and operational costs elements of NREL's turbine systems engineering tool [WISDEM](https://github.com/wisdem/wisdem), and finally give summary estimates of plant finance figures.
These cases start from a four parameter farm layout, compute land use area, make FLORIS estimates of annual energy production (AEP), compute turbine capital costs, balance-of-station (BOS), and operational costs elements of NREL's turbine systems engineering tool [WISDEM](https://github.com/NLRWindSystems/wisdem), and finally give summary estimates of plant finance figures.
The components that achieve this can be assembled to either run a single top-down analysis run, or run an optimization.

# Contributing to `Ard`
Expand Down
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
44 changes: 39 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,22 @@ 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_)
L = _own_L_from_inputs(
{
"x_turbines": X_turbines,
"y_turbines": Y_turbines,
"x_substations": X_substations,
"y_substations": Y_substations,
},
{
"x_border": None,
"y_border": None,
},
)
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 +227,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 +307,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 +379,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 +450,7 @@ def setup(self):
"total_capex_kW",
"bos_capex",
"installation_capex",
"graph",
"terse_links",
"x_turbines",
"y_turbines",
"x_substations",
Expand All @@ -443,3 +463,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 @@ -131,6 +131,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 @@ -143,7 +144,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