Exploring tradeoffs and optimisations
In this tutorial, a Generative Function will be built-up step-by-step for exploring tradeoffs. An airfoil will be used as an example, taking the flow conditions and shape parameters as inputs, and outputting an image of the resultant airfoil and its performance (e.g. lift and drag).
The tutorial will focus on writing and using a Generative Function, rather than the details of creating and analysing airfoils.
Final outcome of the tutorial.
You can also see the code defining the Generative Function and the finished project in the app.
Setup
Prerequisites: Python + an IDE (a code editor)
Optionally, to more easily manage Python packages, you’ll need a Python package manager.
See the Setup page if you’re missing any of these.
Initialising a code repository
To create and run a Generative Function you need somewhere to store and run the code. This is simply a folder anywhere on your computer that has access to Python.
Create an empty folder
If you are using an IDE (code editor), you can probably do this step there.
We recommend you do not create a venv, which some IDEs will do by default when making a new project, unless you’re already comfortable with virtual environments.
If you want to manage different Python packages per repository, we recommend you use a package manager - see the ‘Advanced’ track in the Setup guide for how to set one up.
Install the Generative packages
Install Generative packages generative-core
and generative-server
from pypi.generative.vision.
Open a terminal window, navigate to the folder you created, and run:
If this is successful, the version of generative.server
will be printed.
If you’re not using a virtual environment (venv), this installs Generative packages globally - so you’d only need to do this once per machine. If you are using a venv then you will need to install generative server inside the venv.
Create an empty folder
If you are using an IDE (code editor), you can probably do this step there.
We recommend you do not create a venv, which some IDEs will do by default when making a new project, unless you’re already comfortable with virtual environments.
If you want to manage different Python packages per repository, we recommend you use a package manager - see the ‘Advanced’ track in the Setup guide for how to set one up.
Install the Generative packages
Install Generative packages generative-core
and generative-server
from pypi.generative.vision.
Open a terminal window, navigate to the folder you created, and run:
If this is successful, the version of generative.server
will be printed.
If you’re not using a virtual environment (venv), this installs Generative packages globally - so you’d only need to do this once per machine. If you are using a venv then you will need to install generative server inside the venv.
This initialisation method assumes you have a Python package manager set up - see the ‘Advanced’ track in the Setup guide for how to set one up.
The steps given are for UV or Poetry, but will be similar for whichever package manager you’re using.
Create an empty folder
If you are using an IDE (code editor), you can probably manage this step there.
Initialise package manager
In the same folder, run:
Follow the instructions to initialise an empty repository.
Add the Generative packages
Run the following to install the generative-core
and generative-server
Python packages as dependencies:
Installing other dependencies
This tutorial uses Python packages numpy, Matplotlib and Neural Foil. Install them by running in your terminal:
Writing the Generative Function
In this section, the Generative Function will be defined in Python code.
You can see the complete code which we’ll build up through this section.
Defining input and output Generative types
Generative Types are Python classes subclassing GenerativeType
, which is imported from generative.core
.
They let you structure your data with clearly defined fields, types, and validation.
Here, several Generative Types are defined that describe both the input parameters that will be varied and the output results that will be returned.
Create a Python file in your folder. We’ll call the file airfoil.py
, but it can be anything you like.
Copy the below code to that file.
Inputs are grouped together into three Generative Types, one for the airfoil parameterisation, another for the flow conditions and a final one for the configuration. The airfoil parameterisation uses the PARSEC method, which defines the airfoil’s shape using 11 parameters.
All outputs are collected into one Generative Type, Outputs
.
The plot
field in this class has an Asset type annotation,
which allows non-numeric data (such as videos, 3D geometry, or images) to be passed to the app.
Outputs
also contains a field AnalysisOutputs
,
which is another Generative Type containing the analysis results.
Setting defaults and bounds
Default values and bounds are set for inputs using Pydantic’s Field
.
For example:
leading_edge_radius: float = Field(ge=0.001, le=0.999, default=0.0155)
means that the value forleading_edge_radius
must be greater-than-or-equal-to 0.001, less-than-or-equal-to 0.999, and has a default value of 0.0155.mach: float = Field(gt=0.0, default=0.4)
means that the value formach
must be greater than 0, and has a default of 0.4.
These defaults and bounds not mandatory, but they help to make sure the Generative Function operates within reasonable ranges and provide a starting point for each parameter - you’ll see later exactly how the app treats these, or you can learn more in the concepts section.
Generative Function definition
With the Generative Types defined,
it’s now possible to write a Generative Function using the @generative_function
decorator,
which is imported from generative.core
.
Add the function below to your Python file.
It takes three of the Generative Types we defined earlier as inputs and returns the Outputs
Generative Type.
Internally the code uses other normal Python functions for validation and calculations.
We’ll define those functions in the next few steps.
The generative function has been called airfoil
, which is the name that will appear in the app.
Its inputs are three variables of the input types, which were defined above.
Each field of the input Generative Types can be accessed in the function.
For example config.n_points_per_side
gives the integer value input for the number of discrete points to use when generating the airfoil shape.
On the last line, an instance of Outputs
is created, passing in all its fields by keyword
(Outputs(plot=plot, analysis=analysis_output)
), and then returned.
Generating the airfoil
The Generative Function calls a function parsec_airfoil()
,
which should create a list of coordinates defining the upper and lower surfaces of the airfoil using the PARSEC method.
This function is normal Python, it has no special Generative properties, and should be defined as follows.
Validation
Validation of input data is useful to prevent calculation of the outputs when it’s already clear that the design isn’t viable. We’ve already defined some validation using bounds, but due to the complex nature of the PARSEC parameterisation these bounds on individual parameters don’t guarantee that the resulting airfoil in valid. In particular, it’s possible for the upper and lower surfaces of the airfoil to overlap, producing an invalid geometry.
To catch and reject these malformed airfoils, add the following function to your file. This raises an Exception if the upper and lower airfoil surfaces overlap. Any Exceptions raised during the evaluation of a Generative Function will be caught and logged, and the app will show a failed design which has inputs but no outputs.
Creating an Asset
A function plot_airfoil()
needs to be defined which returns an image of the airfoil as an Asset.
This will allow us to see a plot of the airfoil’s upper and lower surfaces in the app.
The function creates a plot of the airfoil using Matplotlib
then, in the highlighted lines at the end of the function, saves this plot as an Asset
.
The image is a file, rather than a digital asset at a URL, so the type FileAsset
must be used (from generative.core
).
First a FileAsset
instance is created - asset = FileAsset(extension="png")
-
which makes an empty temporary file with extension .png
.
Then the path of the asset is converted to a string and passed to Matplotlib’s function to save the figure.
Tools that rely on global state, like Matplotlib’s pyplot
,
can cause memory leaks and crashes when handling used in Generative Functions.
Make sure an alternative is used.
For example for Matplotlib,
initialise each figure with Figure()
to ensure each Generative Function evaluation gets an isolated, reliable plot without interfering with others.
FileAsset
is a subclass of Asset
, so type of the plot
field in the Generative Type Outputs
should still be Asset
.
Learn more in the concepts section.
Airfoil analysis
Finally, add the following function to perform airfoil analysis.
This uses Neural Foil to estimate various coefficients from the airfoil’s shape and flow conditions,
and returns those coefficients in Generative Type AnalysisOutputs
.
Type Hints for input and output parameters are only used for the Generative Function, where they are required, however Type Hints on the other functions could also be used to increase clarity and help catch errors earlier
That's all the coding done!
You can see the complete Generative Function here.
Connecting to the app
Great! Now you have finished writing a Generative Function, the next step is to connect to the app to start generating designs.
Start up a local function server
Run the following command from a terminal at the root of your repository or in the folder your Generative Function is in:
When you make changes to your function, manually stop the server (e.g. Ctrl-C in Powershell) and run the above command again.
If you want the server to automatically restart when you make changes to your function, you can remove the --no-reload
option or use --reload
,
but on Windows this is known to not work as expected so we recommend using --no-reload
.
To learn more, see the concepts section.
Login to the app
Account is required to proceed. If you haven’t already, sign up at generative.vision.
Open app
Create an empty project
Create and navigate to an empty project. Under the ‘Experiment’ tab, your local function server will be automatically detected if it’s running on port 3000.
If your function server is not detected, check the logs in your terminal where you’re running the server. If the server started correctly, you should see a link, which will take you the server’s API docs page.
Generating designs
You should now have a project open in the app connected to your Generative Function on the Experiment page.
If you haven’t got the Generative Function running on a Function Server, you can go to the app and open a copy of the tutorial project ''.
From here you can use the Generative Function running in our cloud to continue with the rest of the tutorial.
Set up Experiment parameters
Follow the below steps to add parameters to the Experiment and set values for them.
Experiment with parameters set up
Add parameters
- Add the parameters from the image above to the Experiment by clicking
Add
and selecting them. Dismiss the selector box by clicking outside of it. - You’ll now see the input parameters you selected on the left hand side of the screen, and the outputs on the right.
Any inputs not selected will be kept constant at their default value set in the Python code. Any outputs not selected will still be calculated.
Set input parameter values from the image above
- The airfoil shape parameters will be varied between the defined ranges, whilst the Mach number will be held at 0.4 and the angle of attack will be held at 2 degrees.
- These input values were chosen as they allow a reasonable variation on each parameter around the default values, which are of a common basic airfoil shape - NACA0012.
Ranges are inclusive, so setting a range from 0 - 1 means .
Set output parameter values from the image above
- The output values set mean the exploration will aim to find designs with an analysis confidence greater than 0.95, and a moment coefficient between -0.15 and 0.15, whilst maximising lift-to-drag ratio, minimising drag.
Adjust the Experiment settings
The exploration algorithm is automatically configured for optimization when targets are set for output parameters.
Ensure the algorithm settings are as follows:
- Algorithm: Particle swarm
- Designs: 500
- Particles: 50
Adjust algorithm settings
Increasing the number of particles from the default value enables the algorithm to explore a broader range of the design space before focusing on a specific region. As a rule of thumb, the number of designs should be at least 10x the number of particles to provide the algorithm with enough iterations to explore the design space.
These adjustments are particularly beneficial for the airfoil, as its parameters are highly interdependent.
Click Generate
The Experiment is now set up and the Generate button should be activated. Press Generate and watch designs being generated!
Failed designs
With these inputs, a few of the designs fail the validation we wrote. These designs appear with warning triangles showing that they failed to generate outputs.
Viewing results
Navigate to the ‘Discover’ tab to start viewing results.
If you copied the existing tutorial project from within the app rather than making your own, an Insight tab will already exist. Click the plus at the bottom of the page to create a new Insight tab to follow the rest of the tutorial.
Optimisation results
From the ‘Discover’ tab, follow the prompts on screen to add a visualisation of the latest experiment with parameters ‘lift-to-drag ratio’ and ‘analysis confidence’.
Then add another view by clicking on the plus icon +
on the right of the screen.
Add parameter ‘plot’ to that view, and choose selected design from the dropdown.
Select a design furthest to the right in the graph to view the airfoil with the highest lift-to-drag ratio.
Optimisation results on Discover page
You can see on the left side of the graph some of the early designs generated whilst the algorithm was widely exploring the design space. These include some rather strange looking airfoils.
The algorithm soon identifies some more promising geometries and gradually starts generating designs further towards the top right of the graph - ones with higher lift-to-drag ratio and higher analysis confidence.
The exploration breifly explores the less good areas of the designs space
The algorithm starts with randomly chosen designs and iteratively refines them to meet the specified targets. It doesn’t guarantee that the most optimal design for the given input criteria has been found, and if you run the same exploration twice, you’ll get slightly different results.
Exploring Tradeoffs
Add a new Insight tab by clicking on the +
button next to the Insight tabs at the bottom of the page.
Add a visualisation of the latest experiment with parameters ‘lift-to-drag ratio’, ‘moment coefficient’ and ‘drag coefficient’.
As before, add another view by clicking on the plus icon +
on the right of the screen.
Add parameters ‘plot’ and ‘analysis confidence’ to that view, and choose selected design from the dropdown.
Zooming in on the data and rotating the 3D plot to look at ‘lift-to-drag ratio’ and ‘drag coefficient’, you can see that there’s a tradeoff between these parameters - designs which have higher lift-to-drag ratio tend to also have a slightly higher drag coefficient, with the best designs furthest towards the bottom right hand corner of the plot.
A high lift-to-drag ratio increases aerodynamic efficiency, while a low drag coefficient lowers overall energy consumption. Designers must balance these two factors to achieve optimal performance.
Tradeoffs on Discover page - the orange dashed line superimposed on the image shows the tradeoff curve.
Rotating the plot, you can see that most of the designs have met the target for the moment coefficient to be between -0.15 and 0.15.
A moment coefficient close to 0 is desirable as it is directly correlated with the size of the horizontal stabiliser, often a tail wing, that is required.
In the right hand view you see the analysis confidence of the selected design to verify that it’s high enough.
Further Experiments
If you want to experiment further, you could try calculating the internal area of the airfoil in the Python code for the Generative Function and add that to the Experiment.
This would allow you to explore further design tradeoffs by setting a Range constraint on the internal area to ensure that it remains above a certain size. Airfoils need to have sufficient internal area to accommodate fuel storage and structural supports.
Finished project
Congratulations, you finished the tutorial!
To see the finished project:
- Go to the app
- Open a copy of the tutorial project ''
- Navigate to the ‘Discover’ tab to view generated designs