Generative Types
Overview
As seen in the previous section,
inputs to and outputs from Generative Functions can have primitive types such as int
, float
or str
.
For more complex problems or functions, Generative Types provide a way to group related fields, apply validation and limits, and add descriptions.
A Generative Type is a special Python class, created by subclassing GenerativeType
,
that organizes data in an immutable structure, meaning values can’t be changed once they’re set.
These are similar to Python’s dataclasses in that they’re designed for data storage, but with additional capabilities, like supporting files through the use of Assets and performing validation through Pydantic’s BaseModel.
Generative Types can include nested fields, enabling hierarchical structures.
The Generative Engineering Platform uses Generative Types to organize inputs and outputs for Generative Functions, ensuring the correct data types are used and that each field has a clear name and purpose.
Defining Generative Types
Here is an example of a Generative Function for a cantilever analysis, where Generative Types are defined for its inputs and outputs.
First, GenerativeType
is imported from generative.core
,
then the Generative Types are defined by subclassing GenerativeType
.
Inputs and outputs are grouped logically, improving usability in the app by organizing data for each Generative Function.
This is similar to the example in the Generative Functions section of the docs, except now the cantilever is hollow, which allows a bigger design space to be explored, and it’s slope is also calculated.
Instead of returning multiple values, we return a CantileverDeflectionOutputs
Generative Type,
ensuring that each field is clearly labeled and always present in the output.
Syntax rules
Generative Types shouldn’t have any methods that modify their data (because they’re immutable),
and in particular the __init__
method is defined automatically and shouldn’t be overridden.
To create an instance of a Generative Type, use keyword arguments, e.g.
CantileverDeflectionOutputs(deflection_m=1, slope_radians=2)
.
If you try to provide a field that wasn’t originally defined in the Generative Type,
an error will occur.
Default values
Default values can be specified for fields, like force_location_proportion = 1
.
If a value for a field isn’t passed to the Generative Type when instantiating,
then its default value is used (if one is defined).
For example, StructuralMaterialProperties()
would create an instance of the class with the default value of 7e9
used for the field youngs_modulus_pa
,
but CantileverDeflectionOutputs(deflection_m=1)
would throw an error,
because field slope_radians
is missing and has no default value.
These defaults are also used by the app, unless specific values are provided there.
Only set default values on Generative Types which will be used as inputs, and not those used as outputs. Setting a default value on a field used as an output will prevent you from setting constraints or targets on that field from within the app.
Typing
Type hints, like float
, are required in Generative Types.
The app uses them to check compatibility in experiments, helping to prevent errors.
If a default is provided but no type hint, then the type of that argument is inferred from the type of the default.
Validation
It’s useful to define validation on GenerativeType
inputs
so that the GenerativeFunction
isn’t run with inputs we already know are invalid, preventing wasted computation.
GenerativeType
is a subclass of Pydantic’s BaseModel
,
allowing all of Pydantic’s type-validation capabilities to be used in Generative Types.
In practice, only the Field
validator is recommended to be used,
but refer to Pydantic docs for more information on other validators.
If an instance of a Generative Type is created and the validation is failed, then an error is raised and, unless you catch the error, the Generative Function evaluation fails and no results will be visible in the app. For this reason, it’s often easier not to use validation on Generative Types that are going to be outputs, because it’s easier to let the value be assigned regardless and then spot the error in the data in the app.
Field
Pydantic’s Field
function can be used to apply things like bounds and defaults to fields of a Generative Type.
Field
should be imported from Pydantic, which will be installed automatically along with the generative.core
package.
For example, here is one of the cantilever Generative Types extended as such:
Extra information about the field is passed into Field(...)
by keyword.
All the commonly used Field
arguments when creating Generative Functions are used in the example.
- Defaults are provided. The behaviour is the same as when defaults were provided in Defining Generative Types
- Some bounds are provided using
ge=..., le=...
andgt=...
.Field
hasgt
for “greater-than”,ge
for “greater-than-or-equal-to”.lt
andle
are similar for “less-than”. Any bounds provided will be recognised by the app and used appropriately.
If preferred, Field
can be used with Annotated
from Python’s typing
module
to allow standard Python setting of default values.
See Pydantic’s docs for implementation.
Handling Complex Data
Often more complex problems will involve inputting or outputting more complex data.
For inputs, integers can be used to represent different options, for example of material or component types, which can then be taken from a dictionary or list defined in the Python code:
Visual outputs (e.g. images, 3D models) can also be returned as Assets, which we’ll cover in the next section on Assets.