Build discipline from a MATLAB function

As explained in Interfacing simulation software, GEMSEO can interface any simulation software through the MDODiscipline class. When dealing with a MATLAB program given as a function, a generic interface is created in order to facilitate this task. It is described here.

We first start the explanation with a simple example which handles scalar inputs and outputs. Then, we explain how to use the vector inputs and outputs as well as the ability to compute and return output Jacobian matrices.

Note

Building and executing a MATLAB discipline requires that a MATLAB engine as well as its Python API are installed. To make sure that MATLAB works fine through the Python API, start a Python interpreter and check that there is no error when executing import matlab. See MATLAB requirements for more information.

A first simple example with scalar inputs and outputs

Assume that we have a MATLAB file simple_scalar_func.m that contains the following function definition:

function [z1, z2] = simple_scalar_func(x, y)
z1 = x^2;
z2 = 3*cos(y);
end

Note

It is reminded that in MATLAB, the .m file must have the same name than the function, i.e. simple_scalar_func here. GEMSEO raises an error if it is not the case.

Create the discipline instance

A very simple and convenient way that enables to build a discipline from the previous function is to use the GEMSEO API:

from gemseo.api import create_discipline

disc = create_discipline("MatlabDiscipline",
                         matlab_fct="simple_scalar_func.m")

where the first parameter allows to select which kind of discipline one wants to build and the second is the name of the MATLAB function that must be wrapped.

Execute the discipline

Executing the previous MATLAB discipline is also straightforward:

from numpy import array

output = disc.execute({"x": array([2]), "y": array([0.])})
print(output)

which gives:

{'x': array([2.]), 'y': array([0.]), 'z1': array([4.]), 'z2': array([3.])}

Handling input and output vectors

If the discipline involves any vector as input and/or output, it is quite the same as the previous example but one have to be careful with sizes consistency.

Assume for example that the following MATLAB function is defined in file simple_vector_func.m:

function [z1, z2] = simple_vector_func(x, y)
z1(1) = x(1)^2;
z1(2) = 2*x(2);
z2 = 3*cos(y);
end

Thus, inputs must match the right size when executing the discipline:

from gemseo.api import create_discipline

disc_vec = create_discipline("MatlabDiscipline",
                             matlab_fct="simple_vector_func.m")

output = disc_vec.execute({"x": array([2, 3]), "y": array([0.])})

print(output)

and the result is:

{'x': array([2., 3.]), 'y': array([0.]), 'z1': array([4., 6.]), 'z2': array([3.])}

Note

If the discipline is executed with inputs that have the wrong size, an error is raised.

Note

It is reminded that in MATLAB, vector indices start from 1, not from 0 as in Python.

Returning Jacobian matrices

For gradient-based optimization, it is usually convenient to get access to gradients. If gradients are computed inside the MATLAB function, the GEMSEO discipline can take them into account: they just need to be returned properly.

Note

Currently, the computation of gradients must be in the same MATLAB function as the function itself.

More generally, if the basis function takes an input vector \(\bf{x}\) and returns an output vector \(\bf{y}\), the total derivatives denoted \(\frac{d\bf{f}}{d\bf{x}}\) is called the Jacobian matrix as explained in Coupled derivatives computation.

If Jacobian matrices are returned by the MATLAB function, the GEMSEO discipline can take them into account by prescribing the argument is_jac_returned_by_func=True.

Let’s take a simple example and assume that the MATLAB file jac_fun.m contains the following function:

function [ysca, yvec, jac_dysca_dxsca, jac_dysca_dxvec, jac_dyvec_dxsca, jac_dyvec_dxvec] = jac_func(xsca, xvec)

ysca = xsca + 2*xvec(1) + 3*xvec(2);

yvec(1) = 4*xsca + 5*xvec(1) + 6*xvec(2);
yvec(2) = 7*xsca + 8*xvec(1) + 9*xvec(2);

jac_dysca_dxsca = 4;

jac_dysca_dxvec = [2, 3];

jac_dyvec_dxsca = [4; 7];

jac_dyvec_dxvec = [[5, 6]; [8, 9]];

end

Create the discipline instance

Building the discipline is still very simple using the API, we just need to add the boolean argument is_jac_returned_by_func in this case:

from gemseo.api import create_discipline

disc = create_discipline("MatlabDiscipline",
                         matlab_fct="jac_func.m",
                         is_jac_returned_by_func=True)

Executing the discipline

We can execute the discipline in the same way as previously:

output = disc.execute({"xsca": array([1]), "xvec": array([2, 3])})

which gives:

{'xsca': array([1.]), 'xvec': array([2., 3.]), 'ysca': array([14.]), 'yvec': array([32., 50.])}

One can see that the Jacobian outputs are not included in the returned values. Since the argument is_jac_returned_by_func has been activated, the Jacobian matrices values are stored in the MDODiscipline.jac attributes. Thus printing MDODiscipline.jac in a pretty way gives:

Out: ysca / In: xsca
[[4.]]

Out: ysca / In: xvec
[[2. 3.]]

Out: yvec / In: xsca
[[4.]
[7.]]

Out: yvec / In: xvec
[[5. 6.]
[8. 9.]]

Naming convention

As one can see, the Jacobian matrices must be added to the outputs in order to be returned by the MATLAB function. These outputs must follow a naming convention: assuming an input x and output y, the corresponding Jacobian must be returned as jac_dy_dx.

Jacobian matrix dimension

As explained in the section Overloading the MDODiscipline._compute_jacobian() method, GEMSEO always manipulates the Jacobian terms inside 2D arrays even if the Jacobian is reduced to a scalar value, row-vector or column-vector values.

In order to be consistent with the Jacobian definition, the Jacobian output returned by the MATLAB function must have the right dimension:

  • it is a scalar if y is a scalar and x is a scalar;

  • it is a row vector if y is a scalar and x is a vector;

  • it is a column vector if y is a vector and x is a scalar;

  • it is a matrix if y is a vector and x is a vector.

Some important optional arguments

Many others optional parameters can be added when building a MATLAB discipline. They are all listed in the description of MatlabDiscipline but we give some information here about the most important ones.

Files location: search_file

In the previous simple examples, we assumed that the MATLAB .m file is located in the current working directory where GEMSEO is executed.

When dealing with more complex programs that have specific location which could not be changed and/or that contains several files, it is more convenient to give a directory where the MATLAB function is looked for.

The root directory where a MATLAB function is searched can be prescribed with the argument search_file and if the argument add_subfold_path is set to True then all the sub-directories will be added to the MATLAB search paths. An example is:

from gemseo.api import create_discipline

disc = create_discipline("MatlabDiscipline",
                         matlab_fct="simple_scalar_func.m",
                         search_file="matlab_files",
                         add_subfold_path=True)

Initialize data from a MATLAB file: matlab_data_file

It is possible to initialize the input and/or output values of the discipline from a MATLAB data file with the .mat extension. The .mat file can be passed to the GEMSEO API through the matlab_data_file argument. Any input and/or output variables found in this file will be initialized with the provided value. An example is:

from gemseo.api import create_discipline

disc = create_discipline("MatlabDiscipline",
                         matlab_fct="simple_scalar_func.m",
                         matlab_data_file="data_file.mat")

Aliasing input and output names

The arguments input_data_list and output_data_list enable to change the name of the input and/or output variables when using the discipline. As an example, in the previous simple scalar case, the inputs and outputs are respectively denoted x, y, z1 and z2 in the MATLAB function:

from gemseo.api import create_discipline

disc = create_discipline("MatlabDiscipline",
                         matlab_fct="simple_scalar_func.m",
                         input_data_list=["in1, in2"],
                         output_data_list=["out1, out2"])

from numpy import array

disc.execute({"in1": array([2]), "in2": array([0])})

which gives the following result:

{'in1': array([2.]), 'in2': array([0.]), 'out1': array([4.]), 'out2': array([3.])}

Engine name: matlab_engine_name

Note

The current section is mostly for advanced users and should not be considered for simple applications.

When building a MATLAB discipline, the MATLAB Python API launches a MATLAB workspace that will be used in order to execute the MATLAB function that is wrapped. MATLAB workspace handling is done through the __MatlabEngine class. Since this class is private, it cannot be imported directly form the module. An instance of this class is rather obtained through the function get_matlab_engine() which acts like a singleton. This means that calling get_matlab_engine() with the same input argument (the workspace name), returns exactly the same instance. Therefore, if one builds two disciplines, they will be executed in a unique MATLAB workspace. This is indeed what a MATLAB user do when working with MATLAB: run MATLAB once and execute any function inside the same environment.

The uniqueness of the __MatlabEngine instance depends more precisely on the workspace name that is passed to the function get_matlab_engine(): when getting two engines, if the names are the same then the instance is unique, otherwise they are not. Let’s see the following simple example with three engines, two based on the same name and the third based on a different one:

from gemseo.wrappers.matlab.engine.engine import get_matlab_engine

eng1 = get_matlab_engine("workspace_1")
eng2 = get_matlab_engine("workspace_1")
eng3 = get_matlab_engine("workspace_2")

Checking that eng1 is eng2 equals True whereas eng1 is eng3 equals False.

This workspace_name string that is passed to the get_matlab_engine() can be controlled with the argument matlab_engine_name when building the MATLAB discipline from GEMSEO API. By default, this argument is set to "matlab" and should not be changed except for very specific use.