# SPDX-License-Identifier: MIT
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
import networkx as nx
import numpy as np
from scipy.spatial import ConvexHull
from .geometric import CoordPairs
__all__ = ('L_from_synthetic', 'equidistant')
def toyfarm():
VertexC = np.array(
[
# Terminals
[49.0, 993.0], # row 0
[-145.0, 388.0], # row 1
[275.0, 562.0],
[699.0, 566.0],
[-371.0, -147.0], # row 2
[371.0, 109.0],
[972.0, 206.0],
[-585.0, -655.0], # row 3
[90.0, -475.0],
[707.0, -244.0],
[-104.0, -966.0], # row 4
[494.0, -772.0],
# Root
[0.0, 0.0], # OSS
]
)
R = 1
T = 12
B = 0
# create networkx graph
G = nx.Graph(
R=R,
T=T,
B=B,
VertexC=VertexC,
border=np.array((0, 1, 4, 7, 10, 11, 9, 6, 3)),
name='toy',
handle='toy',
)
G.add_nodes_from(((n, {'kind': 'wtg'}) for n in range(T)))
G.add_nodes_from(((r, {'kind': 'oss'}) for r in range(-R, 0)))
return G
[docs]
def L_from_synthetic(
RootC: CoordPairs,
TerminalC: CoordPairs,
BorderC: CoordPairs | None = None,
name: str = '',
handle: str = 'synthetic',
) -> nx.Graph:
"""Special version of L_from_site() for the synthetic location geometry generator.
Example::
def make_tess(radius=5600, spacing=1000):
NodeC = equidistant(radius, center='centroid', spacing=spacing)
RootC = np.array((0.0, 0.0))
return L_from_synthetic(RootC, NodeC, name='SynthTess', handle='tess')
"""
T = TerminalC.shape[0]
R = RootC.shape[0]
# build data structures
if BorderC is None:
VertexC = np.vstack((TerminalC, RootC))
hull = ConvexHull(VertexC)
for v in hull.vertices:
# hack to avoid error in .mesh.make_planar_embedding()
vC = VertexC[v]
VertexC[v] += 1e-6 * vC
border = hull.vertices
B = 0
else:
VertexC = np.vstack((TerminalC, BorderC, RootC))
border = np.arange(T, T + len(BorderC))
B = BorderC.shape[0]
# create networkx graph
L = nx.Graph(
R=R, T=T, B=B, VertexC=VertexC, border=border, name=name, handle=handle
)
L.add_nodes_from(((n, {'kind': 'wtg'}) for n in range(T)))
L.add_nodes_from(((r, {'kind': 'oss'}) for r in range(-R, 0)))
return L
[docs]
def equidistant(
radius: float, center: str = 'centroid', spacing: float = 1.0
) -> CoordPairs:
"""Create coordinates for the vertices of a regular triangular tiling.
Args:
radius: of the circular area to cover
center: one of {'centroid', 'vertex'}
centroid: The coordinate origin is in the centroid of the central triangle.
vertex: The coordinate origin a vertex of the tiling.
spacing: the triangle's side
Returns:
Array of coordinates of the tiling's vertices.
"""
lim = (radius / spacing) ** 2
h = np.sqrt(3) / 2
if center == 'centroid':
def iswithin(x, y):
return x**2 + y**2 <= lim
Vsector = []
offset = np.sqrt(3) / 3
i = 0
repeat = True
# this loop fills a 120° sector
while True:
# x0 = (3*i + 2)*h/3
x0 = i * h + offset
if i % 2 == 0 and repeat:
# add line starting at 0°
y0 = 0
repeat = False
else:
# add line starting at 60°
y0 = x0 * h * 2
repeat = True
i += 1
if iswithin(x0, y0):
Vsector.append((x0, y0))
c = 1
while True:
x, y = x0 + c * h, y0 + c / 2
if iswithin(x, y):
Vsector.append((x, y))
r = np.sqrt(x**2 + y**2)
θ = 2 * np.pi / 3 - np.arctan2(y, x)
Vsector.append((r * np.cos(θ), r * np.sin(θ)))
else:
break
c += 1
else:
if not repeat:
break
# replicate the 120° sector created to fill the circle
Vsector = np.array(Vsector)
r = np.hypot(*Vsector.T)
θ = np.arctan2(*Vsector.T[::-1])
cos_sin = tuple(
np.c_[np.cos(θ + β), np.sin(θ + β)] for β in (2 * np.pi / 3, 4 * np.pi / 3)
)
output = np.r_[
tuple((Vsector,) + tuple(cs * r[:, np.newaxis] for cs in cos_sin))
]
elif center == 'vertex':
def addupper(x, y):
X, Y = (x + 0.5, y + h)
if X**2 + Y**2 <= lim:
yield X, Y
yield from addupper(X, Y)
def addlower(x, y):
X, Y = (x + 1, y)
if X**2 + Y**2 <= lim:
yield X, Y
yield from addlower(X, Y)
def addbranches(x, y):
yield from addlower(x, y)
X, Y = (x + 1.5, y + h)
if X**2 + Y**2 <= lim:
yield X, Y
yield from addbranches(X, Y)
yield from addupper(x, y)
firstbranch = (1.5, h)
Vsector = np.array(
tuple(addlower(0, 0)) + (firstbranch,) + tuple(addbranches(*firstbranch))
)
# replicate the 60° sector created to fill the circle
Vsector = np.array(Vsector)
r = np.hypot(*Vsector.T)
θ = np.arctan2(*Vsector.T[::-1])
cos_sin = tuple(
np.c_[np.cos(θ + β), np.sin(θ + β)] for β in np.pi / 3 * np.arange(1, 6)
)
output = np.r_[
tuple(
(np.zeros((1, 2), dtype=float),)
+ (Vsector,)
+ tuple(cs * r[:, np.newaxis] for cs in cos_sin)
)
]
else:
raise ValueError('Unknown option for <center>:', center)
return spacing * output