Converting an existing script into a Generative Function
Overview
This guide will show step-by-step how to transform an existing script into a Generative Function.
We’ll use a script which generates and analyses an airfoil as an example to work from, but the same technique can be used to convert any script you have which generates and analyses a single design or set of data of any kind.
You can find the code before and after the conversion here:
Original Script
An existing script
Generative Function
Result of converting the script to a Generative Function
Airfoil background
This guide is showing how to convert any script to a Generative Function. As a concrete example, it uses a script which generates airfoils.
In the example original script, airfoil outlines (cross-sections of a wing) are generated using the PARSEC parameterisation technique described in this publication. From the initial parameterisation, coordinates of a few hundred points along the airfoil perimeter are calculated. These coordinates are used to plot the airfoil using matplotlib. The Neural Foil Python package is then used to estimate the performance of the airfoil using neural networks, for example its Lift-Drag ratio.
Converting the script into a Generative Function
Scripts take inputs and present outputs in many ways, for example reading and writing to the command line or files. In the example original script here, the inputs were defined as constants at the top of the script, the numeric outputs are printed to the command line, and the visual output (plot) is displayed on the screen.
To convert a script into a Generative Function,
first all the inputs and outputs of the script are collected into Generative Types,
using the generative.core
package.
Then a Generative Function is created using those Generative Types and the rest of the logic defined in the script.
Setup
Make sure your setup is complete,
and you’ve initialised a repository with the generative.core
and generative.server
packages installed.
Wrapping inputs and outputs in Generative Types
The inputs and outputs to the Generative Function define what you’ll be able to interact with from the app.
It’s often useful to wrap most of the inputs and outputs in GenerativeTypes
to group related parameters together.
Inputs
All the variables defined at the top of the script are wrapped in Generative Types (classes which subclass GenerativeType
).
Field
from pydantic is used to store the descriptions,
and to add some bounds to each input where we already know some sensible limits.
The inputs are grouped into airfoil parameters and air flow parameters.
Default values are set, either using normal Python syntax, angle_of_attack: float = 0.0
,
or using Field
, upper_crest_x_location: float = Field(..., default=0.2966)
.
Keyword arguments ge
and le
are used passed to the Field
functions to provide greater-than-or-equal and less-than-or-equal bounds where appropriate.
These aren’t mandatory, but are recommend to make sure the Generative Function operates within reasonable parameter ranges.
These bounds and defaults are used by the Generative Engineering Platform when running the Generative Function.
Outputs
Outputs are defined similarly for each of the items which are printed, plotted or saved to file in the original script. An Asset, which is the way to store digital assets (e.g. files) and pass them to the app where they can be visualised, is used for the plot.
Turn code blocks into Python functions
The Generative Function will need to execute each step in the script. To make this possible, each of the three code blocks which create the airfoil coordinates, plot it, and analyse its performance are turned into normal Python functions.
For example, for analysing the airfoil, the code block in the original script below gets turned into a Python function,
taking as input some airfoil coordinates and the flow conditions,
and returning the numeric output values stored in the GenerativeType
named AnalysisOutputs
.
If any of the coefficients like "CL"
aren’t in the dictionary aero
,
then the get()
function will return None
, and an Exception will be thrown.
To make debugging easier, this exception could be caught and dealt with.
Create Assets from files
Any files which should be visible from the app are turned into Assets,
For example, the original code which plots the airfoil
is converted into function which saves the plot as a FileAsset
(which is a type of Asset
):
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.
These Python functions aren’t typed, but to keep the code clear and allow type checkers to find errors before runtime, it would be beneficial to add typing to them too.
For example, the signature for the above function could become def plot_airfoil(airfoil_coords: list[tuple[float, float]]) -> Asset:
Create the Generative Function
Once all steps from the script have been converted into function, create a function that performs all intended steps.
Add the @generative_function
decorator to turn it into a Generative Function.
The function signature (def airfoil(airfoil_params: ParsecParams, conditions: FlowConditions, config: Config) -> Outputs:
)
defines the type of each input and the output. For example, variable airfoil_params
has the type ParsecParams
.
This is required to allow the Generative Engineering Platform to understand the inputs and outputs of the function.
A new function has also been added, check_surface_overlap()
, which throws an exception if the airfoil upper and lower surfaces overlap each other.
This ensures that no computation is wasted plotting and analysing any non-physical airfoils.
Start generating designs
Great! Now you have finished converting your script to 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.
You should now have a project open in the app connected to your Generative Function on the Experiment page. Set up the parameters you want to explore in your Experiment and press Generate!
Finished project
Congratulations, you’ve converted your script to a Generative Function!
If you didn’t follow along, but want to see the finished Generative Function in action:
- Go to the app
- Open a copy of the tutorial project ‘Airfoil’
- Navigate to the ‘Discover’ tab