Download as pdf or txt
Download as pdf or txt
You are on page 1of 19

G2Aero

Release v0.1.1

Olga Doronina, Zach Grey, Andrew Glaws

Nov 10, 2022


CONTENTS:

1 Organization 3

2 Citations 5
2.1 How To Guides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3 Technical Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4 Feedback, Support, and Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3 Indices and tables 15

i
ii
G2Aero, Release v0.1.1

G2Aero is a flexible and practical tool for design and deformation of 2D airfoils and 3D blades using data-driven
approaches. G2Aero utilizes the geometry of matrix manifolds – specifically the Grassmannian – to build a novel
framework for representing physics-based separable deformations of shapes. G2Aero offers the flexibility to generate
perturbations in a customizable way over any portion of the blade. The G2Aero framework utilizes data-driven methods
based on a curated database of physically relevant airfoils. Specific tools include:
• principal geodesic analysis over normal coordinate neighborhoods of matrix manifolds;
• a variety of data-regularized deformations to nominal 2D airfoil shapes;
• Riemannian interpolation connecting a sequence of airfoil cross-sections to build 3D blades from 2D data;
• consistent perturbations over the span of interpolated 3D blades based on dominant modes from the data-driven
analysis.

CONTENTS: 1
G2Aero, Release v0.1.1

2 CONTENTS:
CHAPTER

ONE

ORGANIZATION

Documentation is currently organized into three main categories:


• How To Guides: User guides covering basic topics and use cases for the G2Aero software
• Explanation: Information and research sources for basic concepts used in G2Aero
• Technical Reference: Programming details on the G2Aero API and functions
New users may find it helpful to review the Getting started materials first.

3
G2Aero, Release v0.1.1

4 Chapter 1. Organization
CHAPTER

TWO

CITATIONS

If you use this software in your research or publications, please using the following BibTeX citations:

@inproceedings{grassmannian2022,
title={Grassmannian Shape Representations for Aerodynamic Applications},
author={Olga Doronina and Zachary Grey and Andrew Glaws},
booktitle={AAAI 2022 Workshop on AI for Design and Manufacturing (ADAM)},
year={2022},
url={https://openreview.net/forum?id=1RRU6ud9YC}
}

2.1 How To Guides

2.1.1 Getting started

The below will help you quickly install G2Aero.

Requirements

You will need a working Python 3.x installation; You will also need to install the following packages:
• numpy
• scipy
• PyYAML

Installing via conda-forge

conda install -c conda-forge g2aero

5
G2Aero, Release v0.1.1

Install from source

Alternatively, you can install the latest version directly from the most up-to-date version of the source code by
cloning/forking the GitHub repository

git clone https://github.com/NREL/G2Aero.git

Once you have the source, you can build G2Aero (and add it to your environment) by executing

python setup.py install

or

pip install -e .

in the top-level directory. The required Python packages will automatically be installed as well.
You can test your installation by looking for the g2aero executable built by the installation

which g2aero

and by importing the g2aero Python frontend in Python

import g2aero

Testing

To test that the package is working correctly, run

pytest

from the root directory of the package. This will run a basic test problem.

2.1.2 Defining a Data-Driven Domain of Shapes

Input to define domain:


• Dataset of 2D shapes (cross sections) with consistent landmarks both in number and reparametrization—i.e.,
each discrete shape is represented by the same number of landmarks generated by a consistent reparametrization
over the shape
• Dependencies detailed below

[1]: # Python
import os
import numpy as np
# G2Aero
from g2aero.PGA import PGAspace, Dataset
from g2aero import Grassmann as gr
#Plotting
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

6 Chapter 2. Citations
G2Aero, Release v0.1.1

Read airfoils data from data subdirectory

We begin by loading a saved dataset of consistently reparametrized discrete airfoil shapes stored in one of the .npz files
in data/airfoils/. These specific airfoils correspond to thousands of perturbations to CST expansions used in wind
turbine blade design with variations in the trailing edge (TE) gap size.

[2]: shapes_folder = os.path.join(os.getcwd(), '../../data/airfoils/', )


shapes = np.load(os.path.join(shapes_folder, 'CST_shapes_TE_gap.npz'))['shapes']
print("Dataset:")
print(f"Shape of data = {shapes.shape}")
print(f"N_shapes = {shapes.shape[0]}")
print(f"n_landmarks in every shape = {shapes.shape[1]}")
Dataset:
Shape of data = (13000, 401, 2)
N_shapes = 13000
n_landmarks in every shape = 401

Build PGA space and get coordinates

Next, we build the PGA space from the dataset of airfoil shapes. The PGAspace.create_from_dataset() method
returns the PGAspace object and an array of normal coordinates T spanning a subspace at the tangent space of the
Karcher mean defining a section through the Grassmannian.

[3]: # compute Karcher mean and run PGA to define coordinates


pga, T = PGAspace.create_from_dataset(shapes)
Karcher mean convergence:
||V||_F = 0.10235806971182138
||V||_F = 0.0001552688989775973
||V||_F = 2.711475818136911e-07
||V||_F = 5.985411637820536e-10

To visualize this PGA space, we plot the first four out of 2*(n_landmarks - 2) ordered normal coordinates over the
Grassmannian as scatterplots and histograms representing marginal slices through the data.

[4]: coord_names = ['$t_1$', '$t_2$', '$t_3$', '$t_4$']


df_all = pd.DataFrame(data=T[:, :4], columns=coord_names)
sns_plot = sns.pairplot(df_all, x_vars=coord_names, y_vars=coord_names,
diag_kind='kde', corner=True, plot_kws=dict(s=15))

2.1. How To Guides 7


G2Aero, Release v0.1.1

nbsphinx-code-borderwhite

Low-dimensional shape reconstruction

Let’s try reconstructing a shape using fewer normal coordinates (low-dimensional PGA space) and compare it to the
original shape. Here we set the reduced number of parameters (the dimension of PGA space) to r=4 and choose the
shape with index j=1 for demonstration. We also need to calculate LA standardization. LA standardized shapes and
corresponding affine transformation is stored in the data object of Dataset class.

[5]: # assign r as the dimension of the PGA shape


r = 4 # should always be less than or equal to 2*(n_landmarks - 2)
# pick a shape based on index from the dataset
j = 1 # should be less than or equal to N_shapes-1
# save LA standardized shapes and corresponding affine transformation
data = Dataset(shapes)

First, we demonstrate that this is a distinct element of the Grassmannian. Using the Grassmannian distance (square
root of the sum of squared principal anlges between representative shape elements), we emphasize that we are moving
along a different (reduced dimension) section of the Grassmannian given a non-zero Grassmannian distance.

[6]: # transform from PGA space to element on Grassmann


shape_gr_new = pga.PGA2gr_shape(T[j,:r], original_shape_gr=data.shapes_gr[j])
# compute Grassmannian distance error
(continues on next page)

8 Chapter 2. Citations
G2Aero, Release v0.1.1

(continued from previous page)


err_gr = gr.distance(shape_gr_new, data.shapes_gr[j])
print(f'Grassmannian distance: {err_gr}')
Grassmannian distance: 0.03607142623673442

Then, we compute a worst-case Euclidean error (maximum over Euclidean distances between row-wise pairs of shape
landmarks) to offer a human interpretable notion of error in the reconstructed image of the shape in the plane. This
reconstruction utilizes a consistent right inverse with nominal M[j] and b[j] defined by the LA standardization of the
original shape.

[7]: def norm_inf2(sh1, sh2):


d = np.max(np.linalg.norm(sh1-sh2, ord=2, axis=1))
return d

# transform from PGA space to physical scales


phys_shape = pga.PGA2shape(T[j,:r], M=data.M[j], b=data.b[j],
original_shape_gr=data.shapes_gr[j])
# compute worst-case Euclidean error in row-wise landmarks
err_inf2 = norm_inf2(phys_shape, shapes[j])
print(f'Worst-case Euclidean error: {err_inf2}')
Worst-case Euclidean error: 0.004075096848162013

With only four dimensions, the error in the reconstruction of this particular example shape is quite small. Finally, we
overlay the low-dimensional shape with the original shape to visualze the magnitude of the mis-match over physically
relevant scales.

[8]: # plot the low-dimensional shape and the original shape


fig, ax = plt.subplots(1, 1, figsize=(12, 5))
plt.plot(phys_shape[:,0], phys_shape[:,1],linewidth=2.5, label='low-dimensional shape')
plt.plot(shapes[j,:,0], shapes[j,:,1],'--',linewidth=2.5, label='original shape')
# formatting
plt.axis('off')
ax.axis('equal')
ax.legend(fontsize='x-large')
[8]: <matplotlib.legend.Legend at 0x7f035d7fbeb8>

nbsphinx-code-borderwhite

2.1. How To Guides 9


G2Aero, Release v0.1.1

2.1.3 Grassmannian Interpolation

Input to create interpolator:


• 2D shapes (cross sections)
• normalized locations of these shapes along the 3rd direction
Requirements:
• 2D shapes parallel to each other
• Equal number of landmarks (points defining a shape in 2D)
• Locations along the 3rd direction are mormalized from 0 to 1

Interpolation for a blade defined as .yaml file

Usually, a wind turbine blade definition is provided by a .yaml file which contains cross-sectional airfoils (normalized
by chord size), their location along the blade span, and profiles of pitch axis (axis of twist), pitch angle (twist), scaling,
and shift.
Our routine for blade interpolation consists of the following steps:
1. Read the blade definition from the .yaml file and reparametrize given cross-section airfoils so they have an equal
number of landmarks.
2. Interpolate shapes between given cross-sections with the unit chord.
3. Apply affine transformation to the interpolated shapes to scale, rotate, shift and bend (out-of-plane rotation) the
blade according to the profiles provided in .yaml file.
This example also can be found as a script in G2Aero/examples/blade_interpolation.py

[1]: import os
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

from g2aero.yaml_info import YamlInfo


from g2aero.Grassmann_interpolation import GrassmannInterpolator
from g2aero.transform import TransformBlade, global_blade_coordinates

1. Reading blade definition from .yaml file

We first create a class object Blade with information from .yaml file and save Blade.xy_landmarks (2D cross-
sectional airfoils reparametrized to have the same number of landmarks) and Blade.eta_nominal (locations from 0
to 1 of these cross sections along the normalized blade span) to use as input for the interpolator. Note that the number
of landmarks is defined by the user and set to n_landmarks=401 in this example.

[2]: # shapes_filename = os.path.join(os.getcwd(), "../../data/blades_yamls/nrel5mw_ofpolars.


˓→yaml")

shapes_filename = os.path.join(os.getcwd(), "../../data/blades_yamls/IEA-15-240-RWT.yaml


˓→")

Blade = YamlInfo(shapes_filename, n_landmarks=401)


xy_nominal = Blade.xy_landmarks
eta_nominal = Blade.eta_nominal
(continues on next page)

10 Chapter 2. Citations
G2Aero, Release v0.1.1

(continued from previous page)

fig, ax = plt.subplots(1, 1)
for i, xy in enumerate(xy_nominal):
ax.plot(xy[:, 0], xy[:, 1])
ax.axis('equal')
ax.set_xlabel(r'$x^{loc}$', fontsize=15)
ax.set_ylabel(r'$y^{loc}$', fontsize=15)
ax.set_title('Given nominal shapes')
[2]: Text(0.5, 1.0, 'Given nominal shapes')

nbsphinx-code-borderwhite

2. Interpolation

Then we create the interpolator GrInterp with given 2D cross-sections and their spanwise location as input parameters.

[3]: GrInterp = GrassmannInterpolator(eta_nominal, xy_nominal)

Now we need to define an array of spanwise locations where we want to get new interpolated cross-sections. We can
provide any desired locations, e.g. 500 locations uniform along the blade span

[4]: eta_span = np.linspace(0, 1, 200)

or generate locations using the interpolator method. It distributes locations according to the Grassmann distance be-
tween given shapes. Note that this method also has arguments n_hub, n_tip, n_end, which can help specify locations
near the hub and near the tip (see Technical Reference for method details)
[5]: eta_span = GrInterp.sample_eta(n_samples=200)

Next, we pass the array of desired locations to the interpolator to get interpolated shapes. phys_crosssections
contains 2D interpolated shapes parallel to each other and with the unit chord.
[6]: phys_crosssections, gr_crosssections = GrInterp(eta_span, grassmann=True)

fig, ax = plt.subplots(1, 1, figsize=(6, 4))


(continues on next page)

2.1. How To Guides 11


G2Aero, Release v0.1.1

(continued from previous page)


for i, xy in enumerate(phys_crosssections):
ax.plot(xy[:, 0], xy[:, 1])
ax.axis('equal')
ax.set_title("Interpolated shapes")
[6]: Text(0.5, 1.0, 'Interpolated shapes')

nbsphinx-code-borderwhite

3. Apply affine transformation

Finally, we apply affine transformation to the interpolated shapes to scale, rotate, shift and bend (out-of-plane rotation)
the blade according to the profiles provided in .yaml file.

[7]: M_yaml = Blade.M_yaml_interpolator


b_yaml = Blade.b_yaml_interpolator
b_pitch = Blade.pitch_axis
M = GrInterp.interpolator_M
b = GrInterp.interpolator_b

Transform = TransformBlade(M_yaml, b_yaml, b_pitch, M, b)


xyz_local = Transform.grassmann_to_phys(gr_crosssections, eta_span)
xyz_global = global_blade_coordinates(xyz_local)

Now we can visualize interpolated blade. Black cross-sections are cross -sections provided by .yaml file

[8]: import sys


sys.path.insert(0, '../')
from plot_helpfunctions import plot_3d_blade

nominal_shapes = global_blade_coordinates(Transform.grassmann_to_phys(GrInterp.xy_
˓→grassmann, eta_nominal))

plot_3d_blade(xyz_global, nominal_shapes)
nbsphinx-code-borderwhite

Data type cannot be displayed: application/vnd.plotly.v1+json, text/html


nbsphinx-code-borderwhite

12 Chapter 2. Citations
G2Aero, Release v0.1.1

2.2 Explanation

2.3 Technical Reference

2.3.1 Grassmann module

2.3.2 Grassmann_interpolation module

2.3.3 SPD module

2.3.4 PGA module

2.3.5 reparametrization module

2.3.6 transform module

2.3.7 yaml_info module

2.3.8 geometry_gmsh module

2.3.9 utils module

2.4 Feedback, Support, and Contributions

Contributions are always welcome! To contribute to G2aero, report an issue, or seek support, please initiate a pull
request or issue through the project github.

2.2. Explanation 13
G2Aero, Release v0.1.1

14 Chapter 2. Citations
CHAPTER

THREE

INDICES AND TABLES

• genindex
• modindex
• search

15

You might also like