import numpy
import dolfin
from .minimum_distance_constraints import MinimumDistanceConstraints
from ..turbine_cache import TurbineCache
[docs]class BaseFarm(object):
"""A base Farm class from which other Farm classes should be derived."""
def __init__(self, domain=None, turbine=None, site_ids=None):
"""Create an empty Farm."""
# Create a chaching object for the interpolated turbine friction fields
# (as their computation is very expensive)
self.turbine_cache = TurbineCache()
self._parameters = {"friction": [], "position": []}
self.domain = domain
self._set_turbine_specification(turbine)
# The measure of the farm site
self.site_dx = self.domain.dx(site_ids)
[docs] def update(self):
self.turbine_cache.update(self)
@property
[docs] def friction_function(self):
tf = self.turbine_cache["turbine_field"]
return tf
def _get_turbine_specification(self):
if self._turbine_specification is None:
raise ValueError("The turbine specification has not yet been set.")
return self._turbine_specification
def _set_turbine_specification(self, turbine_specification):
self._turbine_specification = turbine_specification
self.turbine_cache.set_turbine_specification(
self._turbine_specification)
turbine_specification = property(_get_turbine_specification,
_set_turbine_specification,
"The turbine specification.")
@property
[docs] def number_of_turbines(self):
"""The number of turbines in the farm.
:returns: The number of turbines in the farm.
:rtype: int
"""
return len(self.turbine_cache["position"])
@property
[docs] def control_array(self):
"""A serialized representation of the farm based on the controls.
:returns: A serialized representation of the farm based on the controls.
:rtype: numpy.ndarray
"""
m = []
if self._turbine_specification.smeared:
m = numpy.zeros(self._turbine_function_space.dim())
else:
if (self._turbine_specification.controls.friction or
self._turbine_specification.controls.dynamic_friction):
m += numpy.reshape(
self._parameters["friction"], -1).tolist()
if self._turbine_specification.controls.position:
m += numpy.reshape(
self._parameters["position"], -1).tolist()
return numpy.asarray(m)
@property
[docs] def turbine_positions(self):
"""The positions of turbines within the farm.
:returns: The positions of turbines within the farm.
:rtype: :func:`list`
"""
return self._parameters["position"]
@property
[docs] def turbine_frictions(self):
"""The friction coefficients of turbines within the farm.
:returns: The friction coefficients of turbines within the farm.
:rtype: :func:`list`
"""
return self._parameters["friction"]
[docs] def add_turbine(self, coordinates):
"""Add a turbine to the farm at the given coordinates.
Creates a new turbine of the same specification as the prototype turbine
and places it at coordinates.
:param coordinates: The x-y coordinates where the turbine should be placed.
:type coordinates: :func:`list`
"""
if self._turbine_specification is None:
raise ValueError("A turbine specification has not been set.")
turbine = self._turbine_specification
self._parameters["position"].append(coordinates)
self._parameters["friction"].append(turbine.friction)
dolfin.info("Turbine added at (%.2f, %.2f)." % (coordinates[0],
coordinates[1]))
def _staggered_turbine_layout(self, num_x, num_y, site_x_start, site_x_end,
site_y_start, site_y_end):
"""Adds a staggered, rectangular turbine layout to the farm.
A rectangular turbine layout with turbines evenly spread out in each
direction across the domain.
:param turbine: Defines the type of turbine to add to the farm.
:type turbine: Turbine object.
:param num_x: The number of turbines placed in the x-direction.
:type num_x: int
:param num_y: The number of turbines placed in the y-direction (will be one less in each second row).
:type num_y: int
:param site_x_start: The minimum x-coordinate of the site.
:type site_x_start: float
:param site_x_end: The maximum x-coordinate of the site.
:type site_x_end: float
:param site_y_start: The minimum y-coordinate of the site.
:type site_y_start: float
:param site_y_end: The maximum y-coordinate of the site.
:type site_y_end: float
:raises: ValueError
"""
if self._turbine_specification is None:
raise ValueError("A turbine specification has not been set.")
turbine = self._turbine_specification
# Generate the start and end points in the desired layout.
start_x = site_x_start + turbine.radius
start_y = site_y_start + turbine.radius
end_x = site_x_end - turbine.radius
end_y = site_y_end - turbine.radius
# Check that we can fit enough turbines in each direction.
too_many_x = turbine.diameter*num_x > end_x-start_x
too_many_y = turbine.diameter*num_y > end_y-start_y
# Raise exceptions if too many turbines are placed in a certain
# direction.
if too_many_x and too_many_y:
raise ValueError("Too many turbines in the x and y direction")
elif too_many_x:
raise ValueError("Too many turbines in the x direction")
elif too_many_y:
raise ValueError("Too many turbines in the y direction")
# Iterate over the x and y positions and append them to the turbine
# list.
for i, x in enumerate(numpy.linspace(start_x, end_x, num_x)):
if i % 2 == 0:
for y in numpy.linspace(start_y, end_y, num_y):
self.add_turbine((x,y))
else:
ys = numpy.linspace(start_y, end_y, num_y)
for i in range(len(ys)-1):
self.add_turbine((x, ys[i] + 0.5*(ys[i+1]-ys[i])))
dolfin.info("Added %i turbines to the site in an %ix%i rectangular "
"array." % (num_x*num_y, num_x, num_y))
def _regular_turbine_layout(self, num_x, num_y, site_x_start, site_x_end,
site_y_start, site_y_end):
"""Adds a rectangular turbine layout to the farm.
A rectangular turbine layout with turbines evenly spread out in each
direction across the domain.
:param turbine: Defines the type of turbine to add to the farm.
:type turbine: Turbine object.
:param num_x: The number of turbines placed in the x-direction.
:type num_x: int
:param num_y: The number of turbines placed in the y-direction.
:type num_y: int
:param site_x_start: The minimum x-coordinate of the site.
:type site_x_start: float
:param site_x_end: The maximum x-coordinate of the site.
:type site_x_end: float
:param site_y_start: The minimum y-coordinate of the site.
:type site_y_start: float
:param site_y_end: The maximum y-coordinate of the site.
:type site_y_end: float
:raises: ValueError
"""
if self._turbine_specification is None:
raise ValueError("A turbine specification has not been set.")
turbine = self._turbine_specification
# Generate the start and end points in the desired layout.
start_x = site_x_start + turbine.radius
start_y = site_y_start + turbine.radius
end_x = site_x_end - turbine.radius
end_y = site_y_end - turbine.radius
# Check that we can fit enough turbines in each direction.
too_many_x = turbine.diameter*num_x > end_x-start_x
too_many_y = turbine.diameter*num_y > end_y-start_y
# Raise exceptions if too many turbines are placed in a certain
# direction.
if too_many_x and too_many_y:
raise ValueError("Too many turbines in the x and y direction")
elif too_many_x:
raise ValueError("Too many turbines in the x direction")
elif too_many_y:
raise ValueError("Too many turbines in the y direction")
# Iterate over the x and y positions and append them to the turbine
# list.
for x in numpy.linspace(start_x, end_x, num_x):
for y in numpy.linspace(start_y, end_y, num_y):
self.add_turbine((x,y))
dolfin.info("Added %i turbines to the site in an %ix%i rectangular "
"array." % (num_x*num_y, num_x, num_y))
[docs] def set_turbine_positions(self, positions):
"""Sets the turbine position and an equal friction parameter.
:param list positions: List of tuples containint x-y coordinates of
turbines to be added.
"""
self.turbine_cache["position"] = positions
self.turbine_cache["friction"] = (
self._turbine_specification.friction*numpy.ones(len(positions)))
self.update()
[docs] def site_boundary_constraints(self):
"""Raises NotImplementedError if called."""
return NotImplementedError("The Farm base class does not have "
"boundaries.")
[docs] def minimum_distance_constraints(self):
"""Returns an instance of MinimumDistanceConstraints.
:returns: An instance of InequalityConstraint defining the minimum distance between turbines.
:rtype: :doc:`opentidalfarm.farm.MinimumDistanceConstraints`
"""
# Check we have some turbines.
n_turbines = len(self.turbine_positions)
if (n_turbines < 1):
raise ValueError("Turbines must be deployed before minimum "
"distance constraints can be calculated.")
controls = self._turbine_specification.controls
minimum_distance = self._turbine_specification.minimum_distance
positions = self.turbine_positions
return MinimumDistanceConstraints(positions, minimum_distance, controls)