Note
Go to the end to download the full example code.
Create a discipline from a Python function#
There is a simplified and straightforward way of integrating a discipline from a Python function that:
returns variables, e.g.
return x
orreturn x, y
, but no expression likereturn a+b
orreturn a+b, y
,must have a default value per argument if the
AutoPyDiscipline
is used by anMDA
(deriving fromBaseMDA
), as in the case ofMDF
andBiLevel
formulations.
from __future__ import annotations
from numpy import array
from gemseo import configure_logger
from gemseo import create_discipline
configure_logger()
<RootLogger root (INFO)>
In this example, we will illustrate this feature using the Python function:
def f(x, y=0.0):
"""A simple Python function taking float numbers and returning a float number."""
z = x + 2 * y
return z
Warning
Note that the Python function must return one or more variables. The following Python function would not be suitable as it returns an expression instead of a variable:
def g(x, y=0.):
"""A simple Python function returning an expression."""
return x + 2*y
Note
Note also that by default,
the arguments and the returned variables of the Python function
are supposed to be either float
numbers
or NumPy arrays with dimensions greater than 1.
At the end of the example, we will see how to use other types.
Then, we can consider the
AutoPyDiscipline
class
to wrap this Python function into a Discipline
.
For that,
we can use the create_discipline()
high-level function
with the string "AutoPyDiscipline"
as first argument:
discipline = create_discipline("AutoPyDiscipline", py_func=f)
WARNING - 02:41:18: The Args section is missing.
The input variables of the discipline are the arguments of the Python function f
:
discipline.io.input_grammar.names
KeysView(Grammar name: f_discipline_input
Required elements:
x:
Type: array
Items:
Type: number
y:
Type: array
Items:
Type: number
Optional elements:)
the output variables of the discipline are the variables returned by f
:
discipline.io.output_grammar.names
KeysView(Grammar name: f_discipline_output
Required elements:
z:
Type: array
Items:
Type: number
Optional elements:)
and the default input values of the discipline
are the default values of the arguments of f
:
discipline.io.input_grammar.defaults
{'y': array([0.])}
Note
The argument x
of the Python function f
shall have a default value
when the discipline is used by an MDA
(deriving from BaseMDA
),
as in the case of MDF
and BiLevel
formulations,
in presence of strong couplings.
This is not the case in this example.
Execute the discipline#
Then, we can execute this discipline easily, either considering default input values:
discipline.execute({"x": array([1.0])})
{'x': array([1.]), 'y': array([0.]), 'z': array([1.])}
or custom ones:
discipline.execute({"x": array([1.0]), "y": array([-3.2])})
{'x': array([1.]), 'y': array([-3.2]), 'z': array([-5.4])}
Warning
You may have noticed that
the input data are passed to the AutoPyDiscipline
as NumPy arrays
even if the Python function f
is expecting float
numbers.
Define the Jacobian function#
Here is an example of a Python function returning the Jacobian matrix as a 2D NumPy array:
def df(x, y):
"""Function returning the Jacobian of z=f(x,y)"""
return array([[1.0, 2.0]])
We can create a new AutoPyDiscipline
from f
and df
:
discipline = create_discipline("AutoPyDiscipline", py_func=f, py_jac=df)
WARNING - 02:41:18: The Args section is missing.
and compute its Jacobian at {"x": array([1.0]), "y": array([0.0])}
:
discipline.linearize(input_data={"x": array([1.0])}, compute_all_jacobians=True)
discipline.jac
# Use custom types
# ----------------
# By default,
# the :class:`.AutoPyDiscipline` assumes that
# the arguments and the returned variables of the Python function are
# either ``float`` numbers or NumPy arrays with dimensions greater than 1.
# This behaviour can be changed in two different ways.
#
# NumPy arrays
# ~~~~~~~~~~~~
# We can force :class:`.AutoPyDiscipline`
# to consider all arguments and variables as NumPy arrays
# by setting the option ``use_arrays`` to ``True``,
# as illustrated here:
def copy_array(a):
a_copy = a.copy()
return a_copy
discipline = create_discipline("AutoPyDiscipline", py_func=copy_array, use_arrays=True)
discipline.execute({"a": array([1.0])})
# User types
# ~~~~~~~~~~
# We can also define specific types for each argument and return variable.
#
# .. warning::
# If you forget to annotate an argument or a return variable,
# all the types you have specified will be ignored.
#
# As a very simple example,
# we can consider a Python function which replicates a string *n* times:
def replicate_string(string: str = "a", n: int = 3) -> str:
final_string = string * n
return final_string
Then, we create the discipline:
discipline = create_discipline("AutoPyDiscipline", py_func=replicate_string)
execute it with its default input values:
discipline.execute()
{'string': 'a', 'n': 3, 'final_string': 'aaa'}
and with custom ones:
discipline.execute({"string": "ab", "n": 5})
{'string': 'ab', 'n': 5, 'final_string': 'ababababab'}
Total running time of the script: (0 minutes 0.017 seconds)