From f1c872892568a7f2dd68f71d7ba98dfe2be4db44 Mon Sep 17 00:00:00 2001 From: Sam Friedman Date: Tue, 26 Jun 2018 21:51:36 -0500 Subject: [PATCH] Move Matlab documentation to main Sphinx RTD website. (#36) * Move Matlab documentation to main Sphinx RTD website. Update main README.md * Fix the order to have "set up model" come before "set initial values" * add link to Python installation and clarify Matlab setup --- README.md | 2 +- doc/index.rst | 7 +- doc/tutorials.rst | 1 + doc/tutorials/matlab.rst | 288 +++++++++++++++++++++++++++++++++++++++ matlab/Matlab_README.md | 179 ------------------------ 5 files changed, 295 insertions(+), 182 deletions(-) create mode 100644 doc/tutorials/matlab.rst delete mode 100644 matlab/Matlab_README.md diff --git a/README.md b/README.md index 6d855a369..156d776cc 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If you wish to examine the code in more depth, see `run_classes.py` and the meth ## Notes -This current version of this repository has grown past the previous Matlab implementation. If you are looking for a Matlab-capable version, please see https://github.com/samtx/OpenAeroStruct for the latest version. +A tutorial on how to implement OpenAeroStruct in Matlab is available in the [documentation](https://openaerostruct.readthedocs.io/en/latest/tutorials/matlab.html). ## Known Issues diff --git a/doc/index.rst b/doc/index.rst index a574788e7..51d78555e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -10,6 +10,8 @@ The analysis and optimization results can be visualized using included tools, pr .. image:: ../example.png +.. _installation: + Installation ----------------- @@ -20,7 +22,7 @@ Next, clone this repository: .. code-block:: bash git clone https://github.com/mdolab/OpenAeroStruct.git - + If you are using MacOS, move the `config-macOS.mk` file to the `config/` folder and rename it to `config.mk` to replace the existing `config.mk` file. The default `config.mk` file works for Ubuntu users. Unfortunately, there is no documented Windows support for the Fortran code. If you're on Mac or Ubuntu, run this command from within the OpenAeroStruct folder to make the Fortran files: @@ -65,7 +67,8 @@ If you wish to examine the code in more depth, see `run_classes.py` and the meth Notes ----- -This current version of this repository has grown past the previous Matlab implementation. If you are looking for a Matlab-capable version, please see https://github.com/samtx/OpenAeroStruct for the latest version. +A tutorial on how to implement OpenAeroStruct in :ref:`Matlab ` is available in the +Tutorials menu. Known Issues ------------ diff --git a/doc/tutorials.rst b/doc/tutorials.rst index 3193cd72c..bd0156c2c 100644 --- a/doc/tutorials.rst +++ b/doc/tutorials.rst @@ -13,3 +13,4 @@ Tutorials tutorials/struct.rst tutorials/aerostruct.rst tutorials/common_pitfalls.rst + tutorials/matlab.rst diff --git a/doc/tutorials/matlab.rst b/doc/tutorials/matlab.rst new file mode 100644 index 000000000..6ef96f54b --- /dev/null +++ b/doc/tutorials/matlab.rst @@ -0,0 +1,288 @@ +.. _Matlab: + +Matlab Implementation +================================================= + +Requirements +------------ + - Matlab 2014b or newer + +Setup +------------ +Calling OpenAeroStruct from a Matlab script or function requires a few steps in succession. +The OpenAeroStruct modules must first be downloaded and configured as +described :ref:`here `. +Then add the directory ``OpenAeroStruct/matlab`` to the Matlab path to access the +example scripts, test scripts, and utility functions *mat2np()* and *np2mat()*. +After that the following steps will set up your Matlab console to call the Python-based +OpenAeroStruct modules. You can include these steps at the beginning of a script +to execute each time the script is run. + +| **1. Set DL open flags (Unix only).** +| If you are using a Unix or Linux system, then you must additionally set a library variable + before loading the Python interpreter into Matlab. + This variable affects how Python opens shared object libraries in Unix. + This step can be made environment-agnostic with a try/catch block. + +.. code-block:: matlab + + try + % On Unix/Linux systems this setting is required otherwise Matlab crashes + py.sys.setdlopenflags(int32(10)); % Set RTLD_NOW and RTLD_DEEPBIND + catch + end + +| **2. Load the Python interpreter using pyversion.** +| If you are using a virtual environment to contain the dependencies of OpenAeroStruct, + then you need to specify the virtual copy of the Python executable. + If you are using `virtualenv `_ on Linux stored in a directory named *venv*, then the command is + + .. code-block:: matlab + + pyversion /OpenAeroStruct/venv/bin/python + +| **3. Add the OpenAeroStruct directory to the PYTHONPATH.** +| Check if OpenAeroStruct is on your PYTHONPATH and add it if it isn't with the statements: + +.. code-block:: matlab + + OAS_PATH = full/path/to/OpenAeroStruct/directory; + if count(py.sys.path,OAS_PATH) == 0 + insert(py.sys.path,int32(0),OAS_PATH); + end + + + +**References** + +- `System requirements for calling Python functions from Matlab `_ +- `Create Python object in Matlab `_ +- `Set DL open flags (Unix only) `_ +- `pyversion `_ +- `virtualenv `_ +- `PYTHONPATH `_ + + +Test +----- +Test the OpenAeroStruct setup in Matlab by running the *test_suite.m* script in +the *matlab* directory. + +Usage +------- +There are several ways to run the OpenAeroStruct models from Matlab and there +are several ways to retrieve data after the model is run. See *run_vlm.m*, +*run_spatialbeam.m*, and *run_aerostruct.m* for the equivalent Matlab scripts to +the Python examples in the parent directory. + +| **1. Create prob_dict for the OASProblem object** +| Use a Matlab struct variable. + +.. code-block:: matlab + + prob_dict = struct; + prob_dict.type = 'aerostruct'; + +| **2. Create OASProblem object from run_classes.py** +| Use the *prob_dict* struct variable from before to instantiate the OASProblem + with the command + +.. code-block:: matlab + + OAS_prob = py.OpenAeroStruct.run_classes.OASProblem(prob_dict) + +| **3. Add surfaces to OAS_prob with a struct variable** + +.. code-block:: matlab + + surf_dict = struct; + surf_dict.num_y = 7; + surf_dict.num_x = 2; + surf_dict.wing_type = 'CRM'; + surf_dict.num_twist_cp = 2; + surf_dict.num_thickness_cp = 2; + OAS_prob.add_surface(surf_dict); + +| **4. Add design variables, constraints, and objectives** +| For an optimization problem, add variable bounds and options if necessary. + To run a model analysis, only the design variable names are needed. +| For optimization: + +.. code-block:: matlab + + OAS_prob.add_desvar('alpha', pyargs('lower',-10., 'upper',10.)); + OAS_prob.add_desvar('wing.twist_cp', pyargs('lower',-15.,'upper',15.)); + OAS_prob.add_desvar('wing.thickness_cp', pyargs('lower',0.01, + OAS_prob.add_constraint('wing_perf.failure',pyargs('lower',0.01, 'upper',0.5, 'scaler',1e2)); + OAS_prob.add_constraint('wing_perf.thickness_intersects', pyargs('upper',0.)); + OAS_prob.add_constraint('L_equals_W', pyargs('equals', 0.)); + OAS_prob.add_objective('fuelburn', pyargs('scaler', 1e-5)); + 'upper',0.5, 'scaler',1e2)); + +For analysis only: + +.. code-block:: matlab + + OAS_prob.add_desvar('alpha'); + OAS_prob.add_desvar('wing.twist_cp'); + OAS_prob.add_desvar('wing.thickness_cp'); + +| **5. Set up the model** + + .. code-block:: matlab + + OAS_prob.setup(); + +| **6. Set initial points for design variables or inputs for analysis** +| Use the *OASProblem.set_var()* function to do this in Matlab. + +.. code-block:: matlab + + OAS_prob.set_var('alpha', 2.5); + OAS_prob.set_var('wing.twist_cp', [-12.0, -3.0]); + OAS_prob.set_var('wing.thickness_cp', [0.03, 0.07]); + +| **7. Run the model** + +.. code-block:: matlab + + OAS_prob.run(); + +You can also use a cell array to set the design variable values when the model +is run. Use the pattern *{name1, value1, name2, value2, ...}*. + +.. code-block:: matlab + + OAS_prob.run(pyargs('alpha',2.5,'wing.twist_cp',[-12.0,-3.0],'wing.thickness_cp',[0.03,0.07])); + % alternatively + input = {'alpha',2.5,'wing.twist_cp',[-12.0,-3.0],'wing.thickness_cp',[0.03,0.07]}; + OAS_prob.run(pyargs(input{:})); + +To return a struct variable with values for design variables, constraints, and +objectives. Include the keyword input *matlab = true* to format the output +dictionary to be converted to a Matlab struct. This converts the periods in +variable names to underscores. See below for information on using *pyargs()* for +keyword arguments. + +.. code-block:: matlab + + output = struct(OAS_prob.run(pyargs('matlab', true))); + +| **8. Get values for design variables, constraints, and objectives** + +.. code-block:: matlab + + fuelburn = OAS_prob.get_var('fuelburn'); + alpha = OAS_prob.get_var('alpha'); + twist_cp = OAS_prob.get_var('wing.twist_cp'); + thickness_cp = OAS_prob.get_var('wing.thickness_cp'); + +If using an output struct from the model run: + +.. code-block:: matlab + + fuelburn = output.fuelburn; + alpha = output.alpha; + twist_cp = output.wing_twist_cp; + thickness_cp = output.wing_thickness_cp; + +Passing variable data between Matlab and Python +------------------------------------------------ +Matlab automatically converts some native Matlab variable classes to native +Python types, and some Python variables can be converted to Matlab variables +with built-in Matlab functions. Matlab supports passing 1-dimensional arrays to +Python and OpenAeroStruct has built-in input validation that will automatically +convert the appropriate parameters from floats to integers, so no additional +effort is required in Matlab for to submit variables that are 1-dimensional +arrays, scalar floats, or scalar integers. + +Multidimensional Arrays +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For multidimensional arrays, the Matlab variable must be converted to a Numpy +array with the utility function *mat2np()* before being passed to Python. For +an example, see the how the *loads* data is added to the wing surface in +*run_spatialbeam.m*. + +.. code-block:: matlab + + num_y = 11; + loads = zeros(floor((num_y+1)/2), 6); + loads(:,2) = 1e5; % This is a Matlab double (6,6) array + loads = mat2np(loads); % Now it is a Numpy (6,6) ndarray + +When returning data from OpenAeroStruct, Matlab will automatically convert +Python scalar floats and integers to native Matlab classes. All array variables +remain as a `Numpy ndarray `_. +The utility function *np2mat()* converts the Numpy array to a native Matlab array of +double precision floats. This function works for scalar values as well as +1-dimensional and multi-dimensional arrays. For example, to return the twist +control points for the wing lifting surface for use in Matlab, enter + +.. code-block:: matlab + + wing_twist_cp = OAS_prob.get_var('wing.twist_cp'); % This is a Numpy ndarray + wing_twist_cp = np2mat(wing_twist_cp); % Now it is a Matlab double array + +Python keyword arguments +^^^^^^^^^^^^^^^^^^^^^^^^^ +The OpenAeroStruct functions *add_desvar()*, *add_constraint()*, +*add_objective()*, and *run()* can accept optional Python keyword arguments. +Matlab scripts can pass these keyword arguments to Python functions with the +Matlab *pyargs()* function, which groups these keyword arguments together. The +function accepts pairs of inputs in the style *(key1, value1, key2, value2, ...)*. +The order of the keyword pairs does not matter. + +For example, to add *wing.thickness_cp* as a design variable with bounds +*[0.001, 0.5]* and scaling *1e2*, the keyword arguments to +*OASProblem.add_desvar()* are + +.. code-block:: python + + lower = 0.001 + upper = 0.5 + scaler = 1e2 + +and entered in Python with the command + +.. code-block:: python + + OASProblem.add_desvar('wing.thickness_cp', lower=0.001, upper=0.5, scaler=1e2) + +In Matlab, use the *pyargs()* function to group the keyword arguments together. + +.. code-block:: matlab + + OASProblem.add_desvar('wing.thickness_cp', pyargs('lower',0.001,'upper',0.5,'scaler',1e2)); + +Matlab struct and Python dictionary +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The *OASProblem.run()* returns a Python dict with the values for the +problem design variables, constraints, objectives, and other parameters. To use +this data in Matlab, the Python dictionary needs to be converted to a Matlab +struct variable. In order for the conversion to work, *run()* must be called +with the keyword argument *matlab = true* so that the dictionary keys are +compatibile Matlab struct fieldnames. The dict variable can then be converted +to a struct with the Matlab *struct()* function. The values of the struct +fields are converted to Matlab variables if they are scalar but arrays remain as +Numpy ndarrays. These ndarrays have to be converted to Matlab arrays by the +*np2mat()* function in order to be manipulated in locally in Matlab. + +.. code-block:: matlab + + out = OAS_prob.run(pyargs('matlab',true)); % This is a Python dict + out = struct(out); % Now this is a Matlab struct + wing_twist_cp = out.wing_twist_cp; % This is a Numpy ndarray + wing_twist_cp = np2mat(wing_twist_cp); % Now this is a Matlab double array + +**References:** + - `Numpy ndarray `_ + - `Pass data to Python from Matlab `_ + - `Pass data to Matlab from Python `_ + - `pyargs: Python keyword arguments `_ + - `Convert Python dict to Matlab struct `_ + +Troubleshooting +------------------ +Many problems can be resolved by viewing the Mathworks documentation for calling +Python libraries from Matlab here: `Getting Started with Python `_. diff --git a/matlab/Matlab_README.md b/matlab/Matlab_README.md deleted file mode 100644 index d30d6d395..000000000 --- a/matlab/Matlab_README.md +++ /dev/null @@ -1,179 +0,0 @@ -# OpenAeroStruct Matlab Implementation - - -## Requirements -- Matlab 2014b or newer - -## Setup -Calling OpenAeroStruct from a Matlab script or function requires a few steps in succession: - -1. If you are using a Unix or Linux system, then you must additionally set a library variable before loading the Python interpreter into Matlab. This variable affects how Python opens shared object libraries in Unix. This step can be made environment-agnostic with a `try/catch/end` statement. -```matlab -try - % On Unix/Linux systems this setting is required otherwise Matlab crashes - py.sys.setdlopenflags(int32(10)); % Set RTLD_NOW and RTLD_DEEPBIND -catch -end -``` -2. Load the Python interpreter using `pyversion`. If you are using a virtual environment to contain the dependencies of OpenAeroStruct, then you need to specify the virtual copy of the Python executable. If you are using [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv) on Linux stored in a directory named `venv`, then the command is `pyversion /OpenAeroStruct/venv/bin/python` -3. Add the OpenAeroStruct directory to the PYTHONPATH. -You can add a check if OpenAeroStruct is on your PYTHONPATH and add it if it isn't with the statements: -```matlab -OAS_PATH = full/path/to/OpenAeroStruct/directory; -if count(py.sys.path,OAS_PATH) == 0 - insert(py.sys.path,int32(0),OAS_PATH); -end -``` - -#### References -- [System requirements for calling Python functions from Matlab](https://www.mathworks.com/help/matlab/matlab_external/system-and-configuration-requirements.html) -- [Create Python object in Matlab](https://www.mathworks.com/help/matlab/matlab_external/create-object-from-python-class.html) -- [Set DL open flags (Unix only)](https://docs.python.org/2/library/sys.html#sys.setdlopenflags) -- [pyversion](https://www.mathworks.com/help/matlab/ref/pyversion.html?s_tid=doc_ta) -- [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/#lower-level-virtualenv) -- [PYTHONPATH](https://docs.python.org/2/using/cmdline.html#envvar-PYTHONPATH) - - -## Test -Test the OpenAeroStruct setup in Matlab by running the `test_suite.m` script in the `matlab` directory. - -## Usage -There are several ways to run the OpenAeroStruct models from Matlab and there are several ways to retrieve data after the model is run. See `run_vlm.m`, `run_spatialbeam.m`, and `run_aerostruct.m` for the equivalent Matlab scripts to the Python examples in the parent directory. - - -1. Create `prob_dict` for the OASProblem object with a Matlab `struct` variable. -```Matlab -prob_dict = struct; -prob_dict.type = 'aerostruct'; -``` -2. Create `OASProblem` object from `run_classes.py`. Use the `prob_dict` struct variable from before to instantiate the OASProblem with the command -```Matlab -OAS_prob = py.OpenAeroStruct.run_classes.OASProblem(prob_dict) -``` -3. Add surfaces to OAS_prob with a `struct` variable. -```Matlab -surf_dict = struct; -surf_dict.num_y = 7; -surf_dict.num_x = 2; -surf_dict.wing_type = 'CRM'; -surf_dict.num_twist_cp = 2; -surf_dict.num_thickness_cp = 2; -OAS_prob.add_surface(surf_dict); -``` -4. Add design variables, constraints, and objectives for an optimization problem. If you only want to run a model analysis, you only need to add design variables but don't need optional design parameters. -For optimization: -```Matlab -OAS_prob.add_desvar('alpha', pyargs('lower',-10., 'upper',10.)); -OAS_prob.add_desvar('wing.twist_cp', pyargs('lower',-15.,'upper',15.)); -OAS_prob.add_desvar('wing.thickness_cp', pyargs('lower',0.01, -OAS_prob.add_constraint('wing_perf.failure',pyargs('lower',0.01, 'upper',0.5, 'scaler',1e2)); -OAS_prob.add_constraint('wing_perf.thickness_intersects', pyargs('upper',0.)); -OAS_prob.add_constraint('L_equals_W', pyargs('equals', 0.)); -OAS_prob.add_objective('fuelburn', pyargs('scaler', 1e-5)); -'upper',0.5, 'scaler',1e2)); -``` -For analysis only: -```matlab -OAS_prob.add_desvar('alpha'); -OAS_prob.add_desvar('wing.twist_cp'); -OAS_prob.add_desvar('wing.thickness_cp'); -``` -5. Set initial points for design variables or inputs for analysis. Use the `OASProblem.set_var()` function to do this in Matlab. -```matlab -OAS_prob.set_var('alpha', 2.5); -OAS_prob.set_var('wing.twist_cp', [-12.0, -3.0]); -OAS_prob.set_var('wing.thickness_cp', [0.03, 0.07]); -``` -6. Set up the model. -```Matlab -OAS_prob.setup(); -``` -6. Run the model. -```Matlab -OAS_prob.run(); -``` -You can also use a cell array to set the design variable values when the model is run. Use the pattern `{name1, value1, name2, value2, ...}`. -```matlab -OAS_prob.run(pyargs(... - 'alpha', 2.5,... - 'wing.twist_cp', [-12.0, -3.0],... - 'wing.thickness_cp', [0.03, 0.07])); -% alternatively -input = {'alpha', 2.5,... - 'wing.twist_cp', [-12.0, -3.0],... - 'wing.thickness_cp', [0.03, 0.07]}; -OAS_prob.run(pyargs(input{:})); -``` -To return a struct variable with values for design variables, constraints, and objectives. Include the keyword input `{'matlab', true}` to format the output dictionary to be converted to a Matlab struct. This converts the periods in variable names to underscores. See below for information on using `pyargs()` for keyword arguments. -```Matlab -output = struct(OAS_prob.run(pyargs('matlab', true))); -``` -7. Get values for design variables, constraints, and objectives. -```Matlab -fuelburn = OAS_prob.get_var('fuelburn'); -alpha = OAS_prob.get_var('alpha'); -twist_cp = OAS_prob.get_var('wing.twist_cp'); -thickness_cp = OAS_prob.get_var('wing.thickness_cp'); -``` -If using an output struct from the model run: -```Matlab -fuelburn = output.fuelburn; -alpha = output.alpha; -twist_cp = output.wing_twist_cp; -thickness_cp = output.wing_thickness_cp; -``` - -## Passing variable data between Matlab and Python -Matlab automatically converts some native Matlab variable classes to native Python types, and some Python variables can be converted to Matlab variables with built-in Matlab functions. Matlab supports passing 1-dimensional arrays to Python and OpenAeroStruct has built-in input validation that will automatically convert the appropriate parameters from floats to integers, so no additional effort is required in Matlab for to submit variables that are 1-dimensional arrays, scalar floats, or scalar integers. - -#### Multidimensional Arrays -For multidimensional arrays, the Matlab variable must be converted to a Numpy array with the utility function `mat2np()` before being passed to Python. For an example, see the how the `loads` data is added to the wing surface in `run_spatialbeam.m`. -```matlab -num_y = 11; -loads = zeros(floor((num_y+1)/2), 6); -loads(:,2) = 1e5; % This is a Matlab double (6,6) array -loads = mat2np(loads); % Now it is a Numpy (6,6) ndarray -``` -When returning data from OpenAeroStruct, Matlab will automatically convert Python scalar floats and integers to native Matlab classes. All array variables remain as a [Numpy ndarray](https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html). The utility function `np2mat()` converts the Numpy array to a native Matlab array of double precision floats. This function works for scalar values as well as 1-dimensional and multi-dimensional arrays. -For example, to return the twist control points for the wing lifting surface for use in Matlab, enter -```Matlab -wing_twist_cp = OAS_prob.get_var('wing.twist_cp'); % This is a Numpy ndarray -wing_twist_cp = np2mat(wing_twist_cp); % Now it is a Matlab double array -``` - -#### Python keyword arguments -The OpenAeroStruct functions `add_desvar()`, `add_constraint()`, `add_objective()`, and `run()` can accept optional Python keyword arguments. Matlab scripts can pass these keyword arguments to Python functions with the Matlab `pyargs()` function, which groups these keyword arguments together. The function accepts pairs of inputs in the style `(key1, value1, key2, value2, ...)`. The order of the keyword pairs does not matter. - -For example, to add `wing.thickness_cp` as a design variable with bounds `[0.001, 0.5]` and scaling `1e2`, the keyword arguments to `OASProblem.add_desvar()` are -```python -lower = 0.001 -upper = 0.5 -scaler = 1e2 -``` -and entered in Python with the command -```Python -OASProblem.add_desvar('wing.thickness_cp', lower=0.001, upper=0.5, scaler=1e2) -``` -In Matlab, use the `pyargs()` function to group the keyword arguments together. -```Matlab -OASProblem.add_desvar('wing.thickness_cp', pyargs('lower',0.001,'upper',0.5,'scaler',1e2)); -``` - -#### Matlab struct and Python dictionary -The `run()` method to `OASProblem` returns a Python `dict` with the values for the problem design variables, constraints, objectives, and other parameters. To use this data in Matlab, the Python dictionary needs to be converted to a Matlab struct variable. In order for the conversion to work, `run()` must be called with the keyword argument `matlab = true` so that the dictionary keys are compatibile Matlab struct fieldnames. The `dict` variable can then be converted to a `struct` with the Matlab `struct()` function. The values of the struct fields are converted to Matlab variables if they are scalar but arrays remain as Numpy `ndarrays`. These `ndarrays` have to be converted to Matlab arrays by the `np2mat()` function in order to be manipulated in locally in Matlab. -```matlab -out = OAS_prob.run(pyargs('matlab',true)); % This is a Python dict -out = struct(out); % Now this is a Matlab struct -wing_twist_cp = out.wing_twist_cp; % This is a Numpy ndarray -wing_twist_cp = np2mat(wing_twist_cp); % Now this is a Matlab double array -``` - -#### References: -- [Numpy ndarray](https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html) -- [Pass data to Python from Matlab](https://www.mathworks.com/help/matlab/matlab_external/handle-data-returned-from-matlab-to-python.html) -- [Pass data to Matlab from Python ](https://www.mathworks.com/help/matlab/matlab_external/pass-data-to-matlab-from-python.html) -- [pyargs: Python keyword arguments](https://www.mathworks.com/help/matlab/ref/pyargs.html?s_tid=doc_ta) -- [Convert Python dict to Matlab struct](https://www.mathworks.com/help/matlab/matlab_external/convert-python-dict-type-to-matlab-structure.html) - -## Troubleshooting -Many problems can be resolved by viewing the Mathworks documentation for calling Python libraries from Matlab here: [Getting Started with Python](https://www.mathworks.com/help/matlab/getting-started-with-python.html).