######################################
BladeDesinger One-On-One: Fundamentals
######################################
This tutorial is intended for developers who wish to implement their own
specific turbomachine components. For a Users Guide read
:ref:`v2-users-index`.
**********
Fist steps
**********
This step by step tutorial aims to address the question: How do I implement a
an analytical camber line? This question is answered with an examplary
simplified NACA 6 series camber line.
This chapter is divided into four steps:
#. :ref:`base-class-label`
#. :ref:`init-label`
#. :ref:`properties-label`
#. :ref:`demanded-methods-label`
The equation for the simplified 6-series camber line is the following:
.. math::
f := \left\{
\begin{array}{l}
[0, 1] \rightarrow \mathbb{R} \cup \{-\infty, +\infty\}\\
x \mapsto -\frac{C_{L0}}{4\pi}[(1-x)\ln|1-x|+x\ln|x|]\\
\end{array} \right.
To build an arbitrary turbomachine component you have the perform these four
steps. In the second tutorial we will show you how to build a custom profile.
.. _base-class-label:
Choosing the right base class
-----------------------------
The BladeDesigner provides a base class for every component. This tutorial
covers how to implement a simplified naca 6-series camber line, so we choose an
AnalyticalCamberLine class as base class (line 3). It's located in the
bladedesigner.baseclasses package (line 1).
The AnalyticalCamberLine and its base classes provides some basic functionality
like:
- attribute checking
- NURBS interpolation (not implemented yet)
- calculating the angle of inflow
- plotting
- etc.
For more information about the functionality of the AnalyticalCamberLine,
CamberLine and Component class see here, here and here.
.. code-block:: python
:emphasize-lines: 1,3,4
:linenos:
import bladedesigner.baseclasses as bcls
class N6SCamberLine(bcls.AnalyticalCamberLine):
pass
.. _init-label:
Initialization of attributes
----------------------------
The simplified naca 6-series camber has one parameter the lift coefficient.
But first things first. We have to initialize the base classes (line 7)
because the child class overwrites the inherited ``__init__`` method.
For that purpose ``super`` accesses all parent classes and initializes their
attributes via ``.__init__()``.
In the previous section we mentioned that the base class provides a method
which checks if the user attributes are initialized. We want to check the new
user attribute self._lift_coefficient as well. To do this two steps are
necessary:
- Assign to self._lift_coefficient an Uninitialized instance (line 8).
Uninitialized is a class without logic. We use them to categorize initialized
and uninitialized attributes. If you call an Uninitialized instance it
returns the name of the attribute. Uninitialized is located in the
bladedesigner.foundation package (line 2).
- Append 'lift_coefficient' to self._properties (line 9).
All attributes contained in self._properties are categorized in
initialized or uninitialized attributes. The convention is to append the
attribute name without leading underscore. The idea behind the missing
underscore is: We want to refer to a property and not a variable. More about
properties in :ref:`properties-label`.
.. code-block:: python
:emphasize-lines: 2,6,7,8,9
:linenos:
import bladedesigner.baseclasses as bcls
import bladedesigner.foundation as fdn
class N6SCamberLine(bcls.AnalyticalCamberLine):
def __init__(self):
super(N6SCamberLine, self).__init__()
self._lift_coefficient = fdn.Uninitialized('lift_coefficient')
self._properties.append('lift_coefficient')
.. _properties-label:
Implementation of properties
----------------------------
If an attribute changes the state of it's instance, the attribute must have
a getter and a setter method. Why is that? Because we have to do some clean
up if the state
changes. We are hiding the getter and setter method with a decorator named
property [#f1]_. The python documentation recommends properties instead of the
direct use of getter and setter methods. The advantage is that attributes with
and without getter and setter methods look the same.
The naming convention for properties and attributes are:
- the attribute name starts always with an underscore plus a proper name, e.g.
``_attribute_name``
- the property name is the attribute name without leading underscore, e.g.
``attribute_name``
Back to our camber line example.
In :ref:`init-label` we identified the attribute lift coefficient. It's a
state changing attribute (as opposed to an attribute which does not change the
state of the turbomachine, such as a name of an attribute),
hence we need a property. In agreement with our naming
convention we named the lift coefficient attribute ``_lift_coefficient`` and
the property ``lift_coefficient`` (line 16, 17, 20 and 22)
The only task of the getter method (line 16 and 17) is to return the value of
``_lift_coefficient`` (line 18).
The setter method (line 20 and 22) has three main tasks:
- checking if the current and new value are the same (line 23)
If so the hole body of the if statement will be skipped.
- applying the the new value to ``_lift_coefficient`` (line 24)
text
- doing some cleaning work (line 25)
text
.. code-block:: python
:emphasize-lines: 1,6,16,17,18,19,20,21,22,23,24,25
:linenos:
import bladedesigner as bd
import bladedesigner.baseclasses as bcls
import bladedesigner.foundation as fdn
rigor = bd.config.get('supervisor', 'rigor')
class N6SCamberLine(bcls.AnalyticalCamberLine):
def __init__(self):
super(N6SCamberLine, self).__init__()
self._lift_coefficient = fdn.Uninitialized('lift_coefficient')
self._properties.append('lift_coefficient')
@property
def lift_coefficient(self):
return self._lift_coefficient
@lift_coefficient.setter
@fdn.requires(lift_coefficient=(int, float), rigor=rigor)
def lift_coefficient(self, lift_coefficient):
if self._lift_coefficient != lift_coefficient:
self._lift_coefficient = lift_coefficient
self.update()
.. rubric:: Footnotes
.. [#f1] If you're not familiar with python decorators have a look in
`PEP 318 `_. For more
information about the property decorater click
`here `_.
.. _demanded-methods-label:
demanded methods
----------------
.. code-block:: python
:emphasize-lines: 27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44
:linenos:
import bladedesigner as bd
import bladedesigner.baseclasses as bcls
import bladedesigner.foundation as fdn
rigor = bd.config.get('supervisor', 'rigor')
class N6SCamberLine(bcls.AnalyticalCamberLine):
def __init__(self):
super(N6SCamberLine, self).__init__()
self._lift_coefficient = fdn.Uninitialized('lift_coefficient')
self._properties.append('lift_coefficient')
@property
def lift_coefficient(self):
return self._lift_coefficient
@lift_coefficient.setter
@fdn.requires(lift_coefficient=(int, float), rigor=rigor)
def lift_coefficient(self, lift_coefficient):
if self._lift_coefficient != lift_coefficient:
self._lift_coefficient = lift_coefficient
self.update()
@fdn.cached_property
def slopes(self):
self._check_initialization()
self._cached = True
x = self.distribution(self.sample_rate)
c_l = self.lift_coefficient
return -c_l / 4. / np.pi * (np.log(x) - np.log(1 - x))
@fdn.memoized
def as_array(self):
self._check_initialization()
self._cached = True
x = self.distribution(self.sample_rate)
c_l = self.lift_coefficient
y = -c_l / 4. / np.pi * ((1 - x) * np.log(1 - x) + x * np.log(x))
y[0] = y[-1] = 0
return np.reshape(np.append(x, y), (-1, 2), "F")