├── .gitattributes
├── .gitignore
├── .readthedocs.yaml
├── LICENSE.md
├── README.md
├── docs
├── Makefile
├── README.md
├── conf.py
├── index.md
├── make.bat
└── src
│ ├── api.md
│ ├── background.md
│ ├── examples.md
│ ├── examples
│ ├── advanced.md
│ └── basic.md
│ ├── getting_started.md
│ ├── images
│ ├── lsdolab.png
│ └── lsdolab_website.png
│ ├── references.bib
│ ├── tutorials.md
│ ├── tutorials
│ ├── advanced.md
│ └── basic.md
│ └── welcome.md
├── examples
├── __init__.py
├── aeroelasticity_vlm
│ ├── dynamic
│ │ └── run_aeroelasticity_dynamic.py
│ ├── evtol_wing_mesh
│ │ ├── eVTOL_wing_half_tri_107695_136686.h5
│ │ ├── eVTOL_wing_half_tri_107695_136686.xdmf
│ │ ├── evtol_wing_vlm_mesh.npy
│ │ ├── vlm_mesh.vtk
│ │ ├── vlm_mesh_mirrored.vtk
│ │ ├── vlm_mesh_nx2_ny10.npy
│ │ └── vlm_mesh_nx6_ny30.npy
│ └── static
│ │ ├── csdl_with_feedback
│ │ └── run_aeroelasticity_static_w_feedback.py
│ │ └── csdl_without_feedback
│ │ └── run_aeroelasticity_static_wo_feedback.py
├── aeroelasticity_vpm
│ └── run_aeroelasticity_vpm.py
├── beam_thickness_opt
│ └── run_thickness_opt_cantilever_beam.py
├── beam_topo_opt
│ ├── pre_processor
│ │ ├── __init__.py
│ │ └── general_filter_model.py
│ └── run_topo_opt_cantilever_beam.py
├── em_motor_opt
│ ├── __init__.py
│ ├── motor_data
│ │ ├── motor_data_coarse
│ │ │ ├── edge_coord_deltas_coarse_1.txt
│ │ │ ├── init_edge_coords_coarse_1.txt
│ │ │ ├── motor_mesh_test_1.msh
│ │ │ ├── motor_mesh_test_1_association_table.ini
│ │ │ ├── motor_mesh_test_1_boundaries.h5
│ │ │ ├── motor_mesh_test_1_boundaries.xdmf
│ │ │ ├── motor_mesh_test_1_domain.h5
│ │ │ └── motor_mesh_test_1_domain.xdmf
│ │ ├── motor_data_fine
│ │ │ ├── edge_coord_deltas_1.txt
│ │ │ ├── init_edge_coords_1.txt
│ │ │ ├── motor_mesh_1.msh
│ │ │ ├── motor_mesh_1_association_table.ini
│ │ │ ├── motor_mesh_1_boundaries.h5
│ │ │ ├── motor_mesh_1_boundaries.xdmf
│ │ │ ├── motor_mesh_1_domain.h5
│ │ │ └── motor_mesh_1_domain.xdmf
│ │ └── motor_data_test
│ │ │ ├── motor_mesh_1.msh
│ │ │ ├── motor_mesh_1_1.msh
│ │ │ ├── motor_mesh_1_association_table.ini
│ │ │ ├── motor_mesh_1_boundaries.h5
│ │ │ ├── motor_mesh_1_boundaries.xdmf
│ │ │ ├── motor_mesh_1_domain.h5
│ │ │ └── motor_mesh_1_domain.xdmf
│ ├── motor_pde.py
│ ├── permeability
│ │ ├── Magnetic alloy, silicon core iron C.tab
│ │ ├── __init__.py
│ │ └── piecewise_permeability.py
│ ├── postprocessor
│ │ ├── __init__.py
│ │ └── power_loss_model.py
│ ├── preprocessor
│ │ ├── __init__.py
│ │ ├── boundary_input_model.py
│ │ └── ffd_model.py
│ └── run_motor_opt.py
├── nonlinear_poisson_opt
│ └── run_nonlinear_poisson_opt.py
├── ongoing
│ └── shape_opt
│ │ ├── run_shape_opt_roof.py
│ │ └── ufl_shape_derivatives.py
├── poisson_opt
│ └── run_poisson_opt.py
└── test_shell_m3l
│ ├── dash_pav.py
│ ├── dash_pav_ml.py
│ ├── old_run_scripts
│ ├── run_pav_aeroelastic.py
│ ├── run_pav_v2_aeroelastic_3g.py
│ ├── run_pav_v2_aeroelastic_3g_sifr_forces.py
│ ├── run_pav_v2_aeroelastic_3g_sifr_forces_ml.py
│ ├── run_pav_v2_aeroelastic_3g_visualizaiton.py
│ ├── run_pav_v2_aeroelastic_SI.py
│ ├── run_pav_v2_aeroelastic_SI_paneled.py
│ ├── run_pav_v2_aeroelastic_opt.py
│ ├── run_pav_v2_aeroelastic_opt_paneled.py
│ ├── run_pav_v2_aeroelastic_opt_paneled_sifr_ml.py
│ └── run_pav_v2_aeroelastic_opt_viz.py
│ ├── pav_wing
│ ├── pav.stp
│ ├── pav_SI.stp
│ ├── pav_new_SI.stp
│ ├── pav_v2_gmsh_6492.msh
│ ├── pav_wing_6rib_caddee_mesh_2374_quad.h5
│ ├── pav_wing_6rib_caddee_mesh_2374_quad.xdmf
│ ├── pav_wing_6rib_caddee_mesh_4862_quad.h5
│ ├── pav_wing_6rib_caddee_mesh_4862_quad.xdmf
│ ├── pav_wing_v2_3g_left_wing.iges
│ ├── pav_wing_v2_caddee_mesh_SI_2303_quad.h5
│ ├── pav_wing_v2_caddee_mesh_SI_2303_quad.xdmf
│ ├── pav_wing_v2_caddee_mesh_SI_6307_quad.h5
│ ├── pav_wing_v2_caddee_mesh_SI_6307_quad.xdmf
│ ├── pav_wing_v2_paneled_mesh_data_2303.pickle
│ ├── pav_wing_v2_paneled_mesh_data_6307.pickle
│ └── pav_wing_v2_structue.iges
│ ├── run_pav_shell.py
│ ├── run_pav_shell_6ribs.py
│ ├── run_pav_shell_ml.py
│ ├── run_pav_shell_modal.py
│ ├── shell_dynamic_pde.py
│ ├── shell_module.py
│ └── shell_pde.py
├── femo
├── __init__.py
├── csdl_opt
│ ├── __init__.py
│ ├── fea_model.py
│ ├── output_model.py
│ └── state_model.py
└── fea
│ ├── __init__.py
│ ├── fea_dolfinx.py
│ └── utils_dolfinx.py
├── requirements.txt
└── setup.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.xdmf filter=lfs diff=lfs merge=lfs -text
2 | *.h5 filter=lfs diff=lfs merge=lfs -text
3 | *.msh filter=lfs diff=lfs merge=lfs -text
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *.DS_Store
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # output files
11 | *.vtu
12 | *.pvd
13 | *records*
14 | *solutions*
15 | *.zip
16 | *SUMMARY_GRAPH.txt
17 |
18 | # profile files
19 | profile*
20 | *.html
21 |
22 | # Optimization results files
23 | *.out
24 |
25 | # Distribution / packaging
26 | .Python
27 | build/
28 | develop-eggs/
29 | dist/
30 | downloads/
31 | eggs/
32 | .eggs/
33 | lib/
34 | lib64/
35 | parts/
36 | sdist/
37 | var/
38 | wheels/
39 | pip-wheel-metadata/
40 | share/python-wheels/
41 | *.egg-info/
42 | .installed.cfg
43 | *.egg
44 | MANIFEST
45 |
46 | # PyInstaller
47 | # Usually these files are written by a python script from a template
48 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
49 | *.manifest
50 | *.spec
51 |
52 | # Installer logs
53 | pip-log.txt
54 | pip-delete-this-directory.txt
55 |
56 | # Unit test / coverage reports
57 | htmlcov/
58 | .tox/
59 | .nox/
60 | .coverage
61 | .coverage.*
62 | .cache
63 | nosetests.xml
64 | coverage.xml
65 | *.cover
66 | *.py,cover
67 | .hypothesis/
68 | .pytest_cache/
69 |
70 | # Translations
71 | *.mo
72 | *.pot
73 |
74 | # Django stuff:
75 | *.log
76 | local_settings.py
77 | db.sqlite3
78 | db.sqlite3-journal
79 |
80 | # Flask stuff:
81 | instance/
82 | .webassets-cache
83 |
84 | # Scrapy stuff:
85 | .scrapy
86 |
87 | # Sphinx documentation
88 | docs/_build/
89 |
90 | # PyBuilder
91 | target/
92 |
93 | # Jupyter Notebook
94 | .ipynb_checkpoints
95 |
96 | # IPython
97 | profile_default/
98 | ipython_config.py
99 |
100 | # pyenv
101 | .python-version
102 |
103 | # pipenv
104 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
105 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
106 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
107 | # install all needed dependencies.
108 | #Pipfile.lock
109 |
110 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
111 | __pypackages__/
112 |
113 | # Celery stuff
114 | celerybeat-schedule
115 | celerybeat.pid
116 |
117 | # SageMath parsed files
118 | *.sage.py
119 |
120 | # Environments
121 | .env
122 | .venv
123 | env/
124 | venv/
125 | ENV/
126 | env.bak/
127 | venv.bak/
128 |
129 | # Spyder project settings
130 | .spyderproject
131 | .spyproject
132 |
133 | # Rope project settings
134 | .ropeproject
135 |
136 | # mkdocs documentation
137 | /site
138 |
139 | # mypy
140 | .mypy_cache/
141 | .dmypy.json
142 | dmypy.json
143 |
144 | # Pyre type checker
145 | .pyre/
146 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.11"
13 | # You can also specify other tool versions:
14 | # nodejs: "19"
15 | # rust: "1.64"
16 | # golang: "1.19"
17 |
18 | # Build documentation in the docs/ directory with Sphinx
19 | # To specify the path to the conf.py file, relative to the root of the project,
20 | sphinx:
21 | configuration: docs/conf.py
22 | # Can also specify the builder type for the Sphinx documentation, and turn warnings into errors
23 | # builder: html
24 | # fail_on_warning: true
25 |
26 | # If using Sphinx, optionally build your docs in additional formats such as PDF
27 | formats:
28 | - pdf
29 | - epub
30 | - htmlzip
31 |
32 | # Optionally declare the Python requirements required to build your docs
33 | python:
34 | install:
35 | - requirements: requirements.txt
36 | - method: pip
37 | path: .
38 |
39 | # If using a conda environment,
40 | # add the path to the Conda environment file, relative to the root of the project.
41 | # conda:
42 | # environment: environment.yml
43 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # femo
2 | **femo** is a general framework for using **F**inite **E**lement in PDE-constrained **M**ultidisciplinary **O**ptimization problems. It relies on [FEniCSx](https://fenicsproject.org/) to provide solutions and partial derivatives of the PDE residuals, and uses [CSDL](https://github.com/LSDOlab/csdl) as the umbrella for coupling and mathematical modeling of the whole problem. The code is still under active developement and we expect it to be available to the public for their research applications by 2023 Winter.
3 |
4 | For modeling and simulation, you need to install `FEniCSx`, `CSDL` and the Python-based backend of `CSDL` - [python_csdl_backend](https://github.com/LSDOlab/python_csdl_backend); for optimization, you will also need [ModOpt](https://github.com/LSDOlab/modopt) on top of them for the black-box optimizers.
5 |
6 | ## Installation
7 |
8 | It's recommended to use conda for installing the module and its dependencies.
9 |
10 | - Create a conda environment for FEniCSx with a specific Python version (Python 3.9) that is compatible with all of the dependencies
11 | ```
12 | conda create -n fenicsx python=3.9.10
13 | ```
14 | (Python 3.9.7 also works if Python 3.9.10 is unavailable in your conda)
15 | - Activate the conda enviroment
16 | ```
17 | conda activate fenicsx
18 | ```
19 | - Install FEniCSx
20 | ```
21 | conda install -c conda-forge fenics-dolfinx=0.5.1
22 | ```
23 | - Git clone and install [CSDL](https://github.com/LSDOlab/csdl), and [python_csdl_backend](https://github.com/LSDOlab/python_csdl_backend) by `pip`
24 | - Git clone and install [femo](https://github.com/RuruX/femo) by `pip`
25 | - (optional) Install [SNOPT](https://ccom.ucsd.edu/~optimizers/solvers/snopt/) for optimization (licence required)
26 | - (optional) Install [ModOpt](https://github.com/LSDOlab/modopt) by `pip` and test with `modopt/modopt/external_packages/csdl/test_scaler.py`
27 |
28 |
29 | ## Cite us
30 | ```
31 | @misc{xiang2024,
32 | author = "Xiang, Ru
33 | and van Schie, Sebastiaan P.C.
34 | and Scotzniovsky, Luca
35 | and Yan, Jiayao
36 | and Kamensky, David
37 | and Hwang, John T.",
38 | title = "Automating adjoint sensitivity analysis for multidisciplinary models involving partial differential equations",
39 | howpublished = {Jul 2024, Preprint available at \url{https://doi.org/10.21203/rs.3.rs-4265983/v1}}
40 | }
41 |
42 | @misc{scotzniovsky2024,
43 | author = "Scotzniovsky, Luca
44 | and Xiang, Ru
45 | and Cheng, Zeyu
46 | and Rodriguez, Gabriel
47 | and Kamensky, David
48 | and Mi, Chris
49 | and Hwang, John T.",
50 | title = "Geometric Design of Electric Motors Using Adjoint-based Shape Optimization",
51 | howpublished = {Feb 2024, Preprint available at \url{https://doi.org/10.21203/rs.3.rs-3941981/v1}}
52 | }
53 | ```
54 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Documentation
3 |
4 | If you are not interested in using this repository as a template but only want to use the documentation template,
5 | you can just copy the `/docs` directory and the `.readthedocs.yaml` file into your package root.
6 | However, make sure you have all the dependencies mentioned in the `setup.py` file installed before you build your
7 | documentation.
8 |
9 | ## Writing
10 | Start by modifying the documentation pages by editing `.md` files in the `/src` directory.
11 | Customize/add/remove pages from the template according to your package's requirements.
12 |
13 | For automatically generated API references, add docstrings to your modules, classes, functions, etc., and
14 | then edit the list of directories containing files with docstrings intended for automatic API generation.
15 | This can be done by editing the line `autoapi_dirs = ["../../lsdo_project_template/core"]`
16 | in `conf.py` in the `/src` directory.
17 |
18 | Add Python files for examples and Jupyter notebooks for tutorials into the main project repository.
19 | Filenames for examples should start with'ex_'.
20 | Add your examples and tutorials to the toctrees in `examples.md` and `tutorials.md` respectively.
21 |
22 | ## Building
23 | Once you have all the source code written for your documentation, on the terminal/command line, run `make html`.
24 | This will build all the html pages locally and you can verify if the documentation was built as intended by
25 | opening the `docs/_build/html/welcome.html` on your browser.
26 |
27 | ## Hosting
28 | On your/lsdolab *Read the Docs* account, **import** your project **manually** from github repository, and link the `/docs` directory.
29 | Make sure to edit `requirements.txt` with dependencies for *Read the Docs* to build the documentation exactly
30 | as in your local build.
31 | Optionally, edit the `.readthedocs.yml` in the project root directory for building with specific operating systems or versions of Python.
32 | After you commit and push, *Read the Docs* will build your package on its servers and once its complete,
33 | you will see your documentation online.
34 | The default website address will be generated based on your *Read the Docs* project name as `https://.readthedocs.io/`.
35 | You can also customize the URL on *Read the Docs*, if needed.
36 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 |
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('../lsdo_project_template/core')) # for autodoc
16 |
17 | # -- Project information -----------------------------------------------------
18 |
19 | project = 'FEMO'
20 | copyright = '2023, Ru Xiang'
21 | author = 'Ru Xiang'
22 | version = '0.1'
23 | # release = 0.1.0rtc
24 |
25 |
26 | # -- General configuration ---------------------------------------------------
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = [
32 | "sphinx_rtd_theme",
33 | "autoapi.extension",
34 | "numpydoc",
35 | "sphinx_copybutton", # allows copying code embedded in the docs rendered from .md or .ipynb files
36 | "myst_nb", # renders .md, .myst, .ipynb files
37 | "sphinx.ext.viewcode", # adds the source code for classes and functions in auto generated api ref
38 | # "sphinxcontrib.collections", # adds files from outside src and executes functions before Sphinx builds
39 | "sphinxcontrib.bibtex", # for references and citations
40 | ]
41 |
42 | # sphinxcontrib.bibtex options
43 | bibtex_bibfiles = ['src/references.bib']
44 |
45 | myst_title_to_header = True
46 | myst_enable_extensions = ["dollarmath", "amsmath", "tasklist"]
47 | nb_execution_mode = 'off'
48 |
49 | # autodoc options
50 | # autodoc_typehints = 'description'
51 |
52 | # autoapi options
53 | autoapi_dirs = ["../femo"]
54 | autoapi_root = 'src/autoapi'
55 | autoapi_type = 'python'
56 | autoapi_file_patterns = ['*.py', '*.pyi']
57 | autoapi_options = [ 'members', 'undoc-members', 'private-members', 'show-inheritance',
58 | 'show-module-summary', 'special-members', 'imported-members', ]
59 | autoapi_add_toctree_entry = False
60 | autoapi_member_order = 'groupwise'
61 | autoapi_python_class_content = 'class' # 'both' or '__init'
62 |
63 | root_doc = 'index'
64 |
65 | # source_suffix = {
66 | # '.rst': 'restructuredtext',
67 | # '.md': 'myst-nb',
68 | # '.myst': 'myst-nb',
69 | # '.ipynb': 'myst-nb',
70 | # }
71 |
72 | # source_parsers = {'.md': 'myst-nb',
73 | # '.ipynb': 'myst-nb',
74 | # }
75 |
76 | # Add any paths that contain templates here, relative to this directory.
77 | templates_path = ['_templates']
78 |
79 | # List of patterns, relative to source directory, that match files and
80 | # directories to ignore when looking for source files.
81 | # This pattern also affects html_static_path and html_extra_path.
82 | exclude_patterns = ['README.md', '_build', 'Thumbs.db', '.DS_Store', 'src/welcome.md']
83 |
84 |
85 | # -- Options for HTML output -------------------------------------------------
86 |
87 | # The theme to use for HTML and HTML Help pages. See the documentation for
88 | # a list of builtin themes.
89 | html_theme = 'sphinx_rtd_theme' # other theme options: 'sphinx_book_theme', 'sphinx_rtd_theme',
90 | # 'alabaster', 'classic', 'sphinxdoc', 'nature', 'bizstyle', ...
91 |
92 | # html_theme_options for sphinx_rtd_theme
93 | html_theme_options = {
94 | 'logo_only': False,
95 | 'display_version': True,
96 | 'prev_next_buttons_location': 'bottom',
97 | 'style_external_links': False,
98 | 'vcs_pageview_mode': '',
99 | 'style_nav_header_background': '#2980B9', # other valid colors: 'white', ...
100 | # toc options
101 | 'collapse_navigation': False, # default: True
102 | 'sticky_navigation': True,
103 | 'navigation_depth': 4,
104 | 'includehidden': True,
105 | 'titles_only': True # default: False
106 | }
107 |
108 | # Add any paths that contain custom static files (such as style sheets) here,
109 | # relative to this directory. They are copied after the builtin static files,
110 | # so a file named "default.css" will overwrite the builtin "default.css".
111 | # html_static_path = ['_static']
112 |
113 |
114 | import glob
115 | # Function used by collections for converting .py files from examples
116 | # to .md and writing those into `_temp/target/` directory before Sphinx builds
117 | def py2md(config):
118 | # root_dir needs a trailing slash (i.e. /root/dir/)
119 | for ex in glob.iglob(config['target'] + '**/ex_*.py', recursive=True):
120 | with open(ex) as f:
121 | code = f.read()
122 | no_line_breaks = ' '.join(code.splitlines())
123 |
124 | if code[0:3] == "'''":
125 | title, desc = split_first_string_between_quotes(no_line_breaks, "'")
126 | elif code[0:3] == '"""':
127 | title, desc = split_first_string_between_quotes(no_line_breaks, '"')
128 | else:
129 | raise SyntaxError('Docstring for title and description is not declared correctly')
130 |
131 | with open(ex[:-3]+'.md', 'w') as g:
132 | g.write('# ' + title + '\n')
133 | g.write(desc + '\n\n')
134 | g.write('```python\n')
135 | g.write(code)
136 | g.write('\n```')
137 |
138 | return
139 |
140 | import re
141 |
142 | def split_first_string_between_quotes(code_string, quotes):
143 | if quotes == "'":
144 | check = re.search("'''(.+?)'''", code_string)
145 | elif quotes == '"':
146 | check = re.search('"""(.+?)"""', code_string)
147 |
148 | if check:
149 | docstring = check.group(1)
150 | out_strings = docstring.split(':', 1)
151 | if len(out_strings)==2:
152 | title, desc = out_strings[0].strip(), out_strings[1].strip()
153 | else:
154 | title, desc = out_strings[0].strip(), ''
155 |
156 | return title, desc
157 |
158 | else:
159 | raise SyntaxError('Docstring for title and description is not declared correctly')
160 |
161 | collections = {
162 |
163 | # copy_tutorials collection copies the contents inside `/tutorials`
164 | # directory into `/src/_temp/tutorials`
165 | 'copy_tutorials': {
166 | 'driver': 'copy_folder',
167 | 'source': '../tutorials', # source relative to path of makefile, not wrt /src
168 | 'target': 'tutorials/',
169 | 'ignore': [],
170 | # 'active': True, # default: True. If False, this collection is ignored during doc build.
171 | # 'safe': True, # default: True. If True, any problem will raise an exception and stops the build.
172 | 'clean': True, # default: True. If False, no cleanup is done before collections get executed.
173 | 'final_clean': True, # default: True. If True, a final cleanup is done at the end of a Sphinx build.
174 | # 'tags': ['my_collection', 'dummy'], # List of tags, which trigger an activation of the collection.
175 | # Should be used together with active set to False,
176 | # otherwise the collection gets always executed.
177 | # Use -t tag option of sphinx-build command to trigger related collections.
178 | # e.g. : `sphinx-build -b html -t dummy . _build/html`
179 | },
180 |
181 | 'copy_examples': {
182 | 'driver': 'copy_folder',
183 | 'source': '../examples', # source relative to path of makefile, not wrt /src
184 | 'target': 'examples/',
185 | 'ignore': [],
186 | 'clean': True, # default: True. If False, no cleanup is done before collections get executed.
187 | 'final_clean': True, # default: True. If True, a final cleanup is done at the end of a Sphinx build.
188 | },
189 |
190 | # convert_examples collection converts all .py files to .md files recursively inside `_temp/examples`
191 | # directory and also extracts the docstrings from the .py files to generate title and descriptions
192 | # for those examples
193 | 'convert_examples': {
194 | 'driver': 'writer_function', # uses custom WriterFunctionDriver written by Anugrah
195 | 'from' : '_temp/examples/', # source relative to path of makefile, not wrt /src
196 | 'source': py2md, # custom function written above in `conf.py`
197 | 'target': 'examples/', # target was a file for original FunctionDriver, e.g., 'target': 'examples/temp.txt'
198 | # the original FunctionDriver was supposed to write only 1 file.
199 | 'clean': True,
200 | 'final_clean': True,
201 | # 'write_result': True, # this prevents original FunctionDriver from writing to the target file
202 | },
203 | }
204 |
205 | collections_target = 'src/_temp' # default : '_collections', the default storage location for all collections
206 | collections_clean = True # default : True, all configured target locations get wiped out at the beginning
207 | # can be overwritten for individual collection by setting value for the 'clean' key
208 | collections_final_clean = True # default : True, all collections start their clean-up routine after a Sphinx build is done
209 | # can be overwritten for individual collection by setting value for the 'final_clean' key
210 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ```{include} src/welcome.md
2 | ```
3 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/src/api.md:
--------------------------------------------------------------------------------
1 | # API reference
2 | This section contains auto-generated API reference for the package.
3 |
4 | ```{toctree}
5 | :maxdepth: 1
6 |
7 | autoapi/vector/index
8 | autoapi/matrix/index
9 | ```
--------------------------------------------------------------------------------
/docs/src/background.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Background
3 | ---
4 |
5 | This page is intended to provide the reader with any theoretical
6 | knowledge or other concepts that form the basis of your package.
7 | This page can include equations, figures, flowcharts, etc. for a better understanding of the theory behind
8 | the package along with any code snippets necessary to explain the software design.
9 |
10 | ## Referencing using bib files
11 |
12 | You can add references in the `references.bib` file and cite them
13 | in the page like this {cite:p}`perez2011python`.
14 | You can also include a list of references cited at the end as shown below.
15 |
16 | ## Bibliography
17 |
18 | ```{bibliography} references.bib
19 | ```
--------------------------------------------------------------------------------
/docs/src/examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | Unlike tutorials, examples can also be generated from Python files (.py) in addition
4 | to Jupyter notebooks (.ipynb),
5 | and is more of a collection of run scripts for reference/benchmarking.
6 | Examples may contain documentation in the form of comments.
7 | However, outputs/visualization from running the script is excluded if you are using Python files.
8 |
9 | If no classification of examples is required, just remove the subpages for examples
10 | and add the example files directly into the toctree of this main examples page.
11 |
12 | ```{toctree}
13 | :maxdepth: 2
14 | :caption: List of examples
15 | :titlesonly:
16 | :numbered:
17 | :includehidden:
18 |
19 | examples/basic
20 | examples/advanced
21 | ```
--------------------------------------------------------------------------------
/docs/src/examples/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced examples
2 |
3 | These pages for each category of examples are optional
4 | and are required only if you plan to classify your examples
5 | into different classes such as basic, intermediate, advanced, etc.
6 | or any other classification suitable for your package.
7 | If no classification is required, just remove these pages
8 | and add the example files directly into the toctree of the main
9 | examples page.
10 |
11 | ```{toctree}
12 | :maxdepth: 1
13 |
14 | ../_temp/examples/advanced_examples/ex_3quartic_opt_modopt
15 | ../_temp/examples/advanced_examples/ex_5modularity_NewtonLagrange
16 | ```
--------------------------------------------------------------------------------
/docs/src/examples/basic.md:
--------------------------------------------------------------------------------
1 | # Basic examples
2 |
3 | These pages for each category of examples are optional
4 | and are required only if you plan to classify your examples
5 | into different classes such as basic, intermediate, advanced, etc.
6 | or any other classification suitable for your package.
7 | If no classification is required, just remove these pages
8 | and add the example files directly into the toctree of the main
9 | examples page.
10 |
11 | ```{toctree}
12 | :maxdepth: 1
13 |
14 | ../_temp/examples/basic_examples/ex_1quartic_opt_csdl
15 | ../_temp/examples/basic_examples/ex_2quartic_opt_modopt
16 | ../_temp/examples/basic_examples/ex_4simple_example
17 | ```
--------------------------------------------------------------------------------
/docs/src/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 | This page provides instructions for installing your package
3 | and running a minimal example.
4 |
5 | ## Installation
6 |
7 | ### Installation instructions for users
8 | For direct installation with all dependencies, run on the terminal or command line
9 | ```sh
10 | $ pip install git+https://github.com/LSDOlab/lsdo_project_template.git
11 | ```
12 | If you want users to install a specific branch, run
13 | ```sh
14 | $ pip install git+https://github.com/LSDOlab/lsdo_project_template.git@branch
15 | ```
16 |
17 | **Enabled by**: Copying the `setup.py` file, changing your repository name and version,
18 | and adding all your dependencies into the list `install_requires`.
19 |
20 | ### Installation instructions for developers
21 | To install `lsdo_project_template`, first clone the repository and install using pip.
22 | On the terminal or command line, run
23 | ```sh
24 | $ git clone https://github.com/LSDOlab/lsdo_project_template.git
25 | $ pip install -e ./lsdo_project_template
26 | ```
27 | **Enabled by**: Copying the setup.py file, and changing your repository name and version.
28 |
29 | ## Setting up Documentation
30 |
31 | If you are not interested in using this repository as a template but only want to use the documentation template,
32 | just copy the `/docs` directory and the `.readthedocs.yaml` file into your package root.
33 | However, make sure you have all the dependencies mentioned in the `setup.py` file installed before you build your
34 | documentation.
35 |
36 | ### Writing
37 | Start by modifying the documentation pages by editing `.md` files in the `/src` directory.
38 | Customize/add/remove pages from the template according to your package's requirements.
39 |
40 | For automatically generated API references, add docstrings to your modules, classes, functions, etc., and
41 | then edit the list of directories containing files with docstrings intended for automatic API generation.
42 | This can be done by editing the line `autoapi_dirs = ["../../lsdo_project_template/core"]`
43 | in `conf.py` in the `/src` directory.
44 |
45 | Add Python files for examples and Jupyter notebooks for tutorials into the main project repository.
46 | Filenames for examples should start with'ex_'.
47 | Add your examples and tutorials to the toctrees in `examples.md` and `tutorials.md` respectively.
48 |
49 | ### Building
50 | Once you have all the source code written for your documentation, on the terminal/command line, run `make html`.
51 | This will build all the html pages locally and you can verify if the documentation was built as intended by
52 | opening the `docs/_build/html/welcome.html` on your browser.
53 |
54 | ### Hosting
55 | On your *Read the Docs* account, **import** your project **manually** from github repository, and link the `/docs` directory.
56 | Make sure to edit `requirements.txt` with dependencies for *Read the Docs* to build the documentation exactly
57 | as in your local build.
58 | Optionally, edit the `.readthedocs.yml` in the project root directory for building with specific operating systems or versions of Python.
59 | After you commit and push, *Read the Docs* will build your package on its servers and once its complete,
60 | you will see your documentation online.
61 | The default website address will be generated based on your *Read the Docs* project name as `https://.readthedocs.io/`.
62 | You can also customize the URL on *Read the Docs*, if needed.
63 |
64 | ## Setting up Testing
65 |
--------------------------------------------------------------------------------
/docs/src/images/lsdolab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/docs/src/images/lsdolab.png
--------------------------------------------------------------------------------
/docs/src/images/lsdolab_website.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/docs/src/images/lsdolab_website.png
--------------------------------------------------------------------------------
/docs/src/references.bib:
--------------------------------------------------------------------------------
1 | @article{perez2011python,
2 | title = {Python: An ecosystem for scientific computing},
3 | author = {Perez, Fernando and Granger, Brian E and Hunter, John D},
4 | journal = {Computing in Science & Engineering},
5 | volume = {13},
6 | number = {2},
7 | pages = {13--21},
8 | year = {2011},
9 | publisher = {AIP Publishing}
10 | }
11 |
12 | @book{1987:nelson,
13 | author = {Edward Nelson},
14 | title = {Radically Elementary Probability Theory},
15 | publisher = {Princeton University Press},
16 | year = {1987}
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/tutorials.md:
--------------------------------------------------------------------------------
1 | # Tutorials
2 |
3 | These tutorials are generated from Jupyter notebooks (.ipynb) and
4 | walks the reader through Python scripts providing detailed
5 | step-by-step instructions.
6 | Each tutorial contains mix of code and text that shows the
7 | functionality of your package.
8 | Tutorials are intended for explaining the fundamental capabilities of the package
9 | and they get progressively more complicated.
10 | It is recommeded that the tutorials also include mathematical equations and outputs/visualization
11 | from running the script for better understanding for the users.
12 |
13 | If no classification of tutorials is required, just remove the subpages for tutorials
14 | and add the tutorial files directly into the toctree of this main tutorials page.
15 |
16 | ```{toctree}
17 | :maxdepth: 2
18 | :caption: List of tutorials
19 | :titlesonly:
20 | :numbered: 1
21 | :includehidden:
22 |
23 | tutorials/basic
24 | tutorials/advanced
25 | ```
--------------------------------------------------------------------------------
/docs/src/tutorials/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced tutorials
2 |
3 | These pages for each category of tutorials are optional
4 | and are required only if you plan to classify your tutorials
5 | into different classes such as basic, intermediate, advanced, etc.
6 | or any other classification suitable for your package.
7 | If no classification is required, just remove these pages
8 | and add the tutorial files directly into the toctree of the main
9 | tutorials page.
10 |
11 | ```{toctree}
12 | :maxdepth: 1
13 |
14 | ../_temp/tutorials/advanced_tutorials/modopt_tutorial
15 | ```
--------------------------------------------------------------------------------
/docs/src/tutorials/basic.md:
--------------------------------------------------------------------------------
1 | # Basic tutorials
2 |
3 | These pages for each category of tutorials are optional
4 | and are required only if you plan to classify your tutorials
5 | into different classes such as basic, intermediate, advanced, etc.
6 | or any other classification suitable for your package.
7 | If no classification is required, just remove these pages
8 | and add the tutorial files directly into the toctree of the main
9 | tutorials page.
10 |
11 | ```{toctree}
12 | :maxdepth: 1
13 |
14 | ../_temp/tutorials/basic_tutorials/jupyter_tutorial
15 | ```
--------------------------------------------------------------------------------
/docs/src/welcome.md:
--------------------------------------------------------------------------------
1 | # Welcome to FEMO
2 |
3 | 
4 |
5 | This page describes conceptually the purpose of your package at a high-level.
6 | Start with a one sentence description of your package.
7 | For example, "This repository serves as a template for all LSDOlab projects with regard to documentation, testing and hosting of open-source code."
8 | Include figures from the relevant paper and citation.
9 |
10 | # Cite us
11 | ```none
12 | @article{lsdo2023,
13 | Author = { Author 1, Author 2, and Author 3},
14 | Journal = {Name of the Journal},
15 | Title = {Title of your paper},
16 | pages = {203},
17 | year = {2023},
18 | issn = {0123-4567},
19 | doi = {https://doi.org/}
20 | }
21 | ```
22 |
23 |
24 |
25 | ```{toctree}
26 | :maxdepth: 1
27 | :hidden:
28 |
29 | src/getting_started
30 | src/background
31 | src/tutorials
32 | src/examples
33 | src/api
34 | ```
35 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/__init__.py
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/eVTOL_wing_half_tri_107695_136686.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f0808f9e193e6c779136921e15f1e3331b7320ccb15209402d88915d030e6fd4
3 | size 2812101
4 |
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/eVTOL_wing_half_tri_107695_136686.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:4f640cdb0b2cb81fc881e371383008dce21b7580f7ed5b1784b3f80dc4ea0ab2
3 | size 458
4 |
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/evtol_wing_vlm_mesh.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/aeroelasticity_vlm/evtol_wing_mesh/evtol_wing_vlm_mesh.npy
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh.vtk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh.vtk
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_mirrored.vtk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_mirrored.vtk
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_nx2_ny10.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_nx2_ny10.npy
--------------------------------------------------------------------------------
/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_nx6_ny30.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/aeroelasticity_vlm/evtol_wing_mesh/vlm_mesh_nx6_ny30.npy
--------------------------------------------------------------------------------
/examples/beam_thickness_opt/run_thickness_opt_cantilever_beam.py:
--------------------------------------------------------------------------------
1 | ##test beam
2 |
3 | '''
4 | Thickness optimization of 1D Cantilever Beam with a rectangular cross section
5 | this example uses Euler-Bernoulli ("classical") beam theory
6 | '''
7 |
8 |
9 | # from femo.fea.fea_dolfinx import FEA
10 | from femo.fea.fea_dolfinx import *
11 | from femo.csdl_opt.fea_model import FEAModel
12 | from femo.csdl_opt.state_model import StateModel
13 | from femo.csdl_opt.output_model import OutputModel
14 | from femo.csdl_opt.pre_processor.general_filter_model \
15 | import GeneralFilterModel
16 |
17 | from dolfinx.mesh import locate_entities_boundary,create_interval
18 | import numpy as np
19 | import csdl
20 |
21 | import ufl
22 | from csdl import Model
23 | from python_csdl_backend import Simulator
24 | from matplotlib import pyplot as plt
25 | import argparse
26 | import basix
27 |
28 |
29 | from mpi4py import MPI
30 | from modopt import CSDLProblem
31 | from modopt import SLSQP
32 | '''
33 | 1. Define the mesh
34 | '''
35 | # parser = argparse.ArgumentParser()
36 | # parser.add_argument('--nel',dest='nel',default='50',
37 | # help='Number of elements')
38 | #
39 | # args = parser.parse_args()
40 |
41 | # Geometric inputs and material properties
42 | E = 1.
43 | L = 1.
44 | b = 0.1
45 | h = 0.1
46 | volume = 0.01
47 | # NOTE: floats must be converted to dolfin constants on domain below
48 | # before being used in the variational form
49 |
50 |
51 | # Construct beam mesh
52 | # nel = int(args.nel)
53 | nel = 50
54 | mesh = create_interval(MPI.COMM_WORLD, nel, [0., L])
55 |
56 | x = ufl.SpatialCoordinate(mesh)
57 | # width = ufl.Constant(mesh,b)
58 | # E = ufl.Constant(mesh,E)
59 | width = b
60 |
61 |
62 | '''
63 | 2. Set up the PDE problem
64 | '''
65 |
66 | '''
67 | 2.1. Define variational forms for PDE residual and outputs
68 | '''
69 |
70 | # Define Moment expression
71 | def M(u, t, E, width):
72 | t3 = t**3
73 | a = width*t**3
74 | EI = (E*width*t**3)/12
75 | return EI*div(grad(u))
76 |
77 | def pdeRes(u, v, t, f, dss, E, width):
78 | res = inner(div(grad(v)),M(u,t,E,width))*dx - dot(f,v)*dss
79 | return res
80 |
81 | def volume(t, width, L):
82 | return t*width*L*dx
83 |
84 | def compliance(u, f, dss=ufl.ds):
85 | return dot(f,u)*dss
86 |
87 | '''
88 | 2.2. Create function spaces for the input and the state variables
89 | '''
90 |
91 | fea = FEA(mesh)
92 | # Add input to the PDE problem:
93 | input_name = 'thickness'
94 | input_function_space = FunctionSpace(mesh, ('DG', 0))
95 | input_function = Function(input_function_space)
96 |
97 |
98 | # Add state to the PDE problem:
99 | # Create Hermite order 3 on a interval (for more informations see:
100 | # https://defelement.com/elements/examples/interval-Hermite-3.html )
101 | beam_element = basix.ufl_wrapper.create_element(basix.ElementFamily.Hermite,
102 | basix.CellType.interval, 3)
103 | state_name = 'displacements'
104 | state_function_space = FunctionSpace(mesh, beam_element)
105 | state_function = Function(state_function_space)
106 | v = TestFunction(state_function_space)
107 |
108 | # Add the same point load at the endpoint as the OpenMDAO example
109 | # https://github.com/OpenMDAO/OpenMDAO/blob/304d45169b4b2a20e7d0e5441f81d9c072d7af09/openmdao/test_suite/test_examples/beam_optimization/beam_group.py#L29
110 |
111 | f = Constant(mesh, -1.)
112 |
113 | # Get DOF of the endpoint
114 | DOLFIN_EPS = 3E-16
115 | def Endpoint(x):
116 | return np.isclose(abs(x[0] - L), DOLFIN_EPS*1e5)
117 |
118 | fdim = mesh.topology.dim - 1
119 | endpoint_node = locate_entities_boundary(mesh,fdim,Endpoint)
120 | facet_tag = meshtags(mesh, fdim, endpoint_node,
121 | np.full(len(endpoint_node),100,dtype=np.int32))
122 | # Define measures of the endpoint
123 | metadata = {"quadrature_degree":4}
124 | ds_ = ufl.Measure('ds',domain=mesh,subdomain_data=facet_tag,metadata=metadata)
125 |
126 |
127 | residual_form = pdeRes(state_function,
128 | v,
129 | input_function,
130 | f, ds_(100),
131 | E, width)
132 |
133 | # Add outputs to the PDE problem:
134 | output_name_1 = 'compliance'
135 | output_form_1 = compliance(state_function,f,ds_(100))
136 | output_name_2 = 'volume'
137 | output_form_2 = volume(input_function,width,L)
138 |
139 | fea.add_input(input_name, input_function)
140 | fea.add_state(name=state_name,
141 | function=state_function,
142 | residual_form=residual_form,
143 | arguments=[input_name])
144 | fea.add_output(name=output_name_1,
145 | type='scalar',
146 | form=output_form_1,
147 | arguments=[input_name, state_name])
148 | fea.add_output(name=output_name_2,
149 | type='scalar',
150 | form=output_form_2,
151 | arguments=[input_name])
152 |
153 | '''
154 | 2.3. Define the boundary conditions
155 | '''
156 |
157 | ubc = Function(state_function_space)
158 | ubc.vector.set(0.0)
159 | startpt = locate_entities_boundary(mesh,0,lambda x : np.isclose(x[0], 0))
160 | locate_BC1 = locate_dofs_topological(state_function_space,0,startpt)
161 | locate_BC_list = [locate_BC1[0], locate_BC1[1]]
162 | fea.add_strong_bc(ubc, locate_BC_list)
163 | # Turn off the log info for the Newton solver
164 | fea.REPORT = False
165 |
166 |
167 | '''
168 | 3. Set up the CSDL model and run simulation
169 | '''
170 | fea_model = FEAModel(fea=[fea])
171 |
172 | fea_model.create_input("{}".format('thickness'),
173 | shape=nel,
174 | val=h) # initializing with constant thickness
175 |
176 | fea_model.add_design_variable('thickness', upper=10., lower=1e-2)
177 | fea_model.add_objective('compliance')
178 | fea_model.add_constraint('volume', equals=b*h*L)
179 | sim = Simulator(fea_model,analytics=False)
180 | # Run the simulation
181 | sim.run()
182 |
183 | # Check the derivatives
184 | # sim.check_totals(compact_print=True)
185 | #
186 | '''
187 | 4. Set up and run the optimization problem
188 | '''
189 | # Run the optimization with modOpt
190 |
191 | prob = CSDLProblem(
192 | problem_name='beam_thickness_opt',
193 | simulator=sim,
194 | )
195 |
196 | optimizer = SLSQP(prob, maxiter=1000, ftol=1e-9)
197 |
198 | # from modopt import SNOPT
199 | # optimizer = SNOPT(prob,
200 | # Major_iterations = 1000,
201 | # Major_optimality = 1e-9,
202 | # append2file=False)
203 | # Solve your optimization problem
204 | optimizer.solve()
205 | print("="*40)
206 | optimizer.print_results()
207 |
208 | '''
209 | 5. Postprocessing
210 | '''
211 |
212 | # NOTE: The solution uh contains both the rotation and the displacement solutions
213 | #The rotation and displacment solutions can be separated as follows:
214 | #TODO: there is likely a much easier way to separate these DOFs and do so in a
215 |
216 | disp = np.empty(0)
217 | rot = np.empty(0)
218 | for i,x in enumerate(state_function.x.array):
219 | if i % 2 != 0:
220 | rot = np.append(rot,x)
221 | else:
222 | disp = np.append(disp,x)
223 |
224 | print("Number of DOFs: %d" % state_function_space.dofmap.index_map.size_global)
225 | print("Number of elements (intervals): %d" % nel)
226 | print("Number of nodes: %d" % (nel+1))
227 | print("Maximum magnitude displacement (cantilever FEM solution) is: %e"
228 | % np.min(disp))
229 | print("Compliance value: ", sim['compliance'])
230 |
231 | # Print out the matrices of partial derivatives
232 | print("-"*40)
233 | print("PDE residual w.r.t. Displacements:")
234 | dR_du = assemble_partials(of=residual_form, wrt=state_function, dim=2) # =pRpy
235 | print(dR_du)
236 | print("-"*40)
237 | print("PDE residual w.r.t. Thicknesses:")
238 | dR_df = assemble_partials(of=residual_form, wrt=input_function, dim=2) # =pRpx
239 | print(dR_df)
240 | print("-"*40)
241 | print("Objective(compliance) w.r.t. Displacements:")
242 | dF_du = assemble_partials(of=output_form_1, wrt=state_function, dim=1) # =pFpy
243 | print(dF_du)
244 | print("-"*40)
245 | print("Objective(compliance) w.r.t. Thicknesses:")
246 | dF_df = assemble_partials(of=output_form_1, wrt=input_function, dim=1) # =pFpx
247 | print(dF_df)
248 | print("-"*40)
249 |
250 | # Reference optimized thickness distribution from the OpenMDAO example
251 | # https://openmdao.org/newdocs/versions/latest/examples/beam_optimization_example.html#implementation-optimization-script
252 | thick_ref = [
253 | 0.14915754, 0.14764328, 0.14611321, 0.14456715, 0.14300421, 0.14142417,
254 | 0.13982611, 0.13820976, 0.13657406, 0.13491866, 0.13324268, 0.13154528,
255 | 0.12982575, 0.12808305, 0.12631658, 0.12452477, 0.12270701, 0.12086183,
256 | 0.11898809, 0.11708424, 0.11514904, 0.11318072, 0.11117762, 0.10913764,
257 | 0.10705891, 0.10493903, 0.10277539, 0.10056526, 0.09830546, 0.09599246,
258 | 0.09362243, 0.09119084, 0.08869265, 0.08612198, 0.08347229, 0.08073573,
259 | 0.07790323, 0.07496382, 0.07190453, 0.06870925, 0.0653583, 0.06182632,
260 | 0.05808044, 0.05407658, 0.04975295, 0.0450185, 0.03972912, 0.03363155,
261 | 0.02620192, 0.01610863]
262 |
263 | fig, ax = plt.subplots()
264 | ax.plot(np.linspace(0.0,L,50), thick_ref, "b-o",
265 | label="OpenMDAO results")
266 | ax.plot(np.linspace(0.0,L,nel), sim['thickness'], "r-o",
267 | label="FEniCS+CSDL results")
268 | ax.set_xlabel("x")
269 | ax.set_ylabel("optimized thickness distribution")
270 | ax.legend(loc="best")
271 | plt.show()
272 | fig.savefig("beam_thickness_distribution.png", dpi=150)
273 |
--------------------------------------------------------------------------------
/examples/beam_topo_opt/pre_processor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/beam_topo_opt/pre_processor/__init__.py
--------------------------------------------------------------------------------
/examples/beam_topo_opt/pre_processor/general_filter_model.py:
--------------------------------------------------------------------------------
1 | from csdl import Model, CustomExplicitOperation
2 | import csdl
3 | import numpy as np
4 | import scipy.sparse
5 | from scipy import spatial
6 |
7 |
8 | class GeneralFilterModel(Model):
9 |
10 | def initialize(self):
11 | self.parameters.declare('nel')
12 | self.parameters.declare('beta', default=2.)
13 | self.parameters.declare('coordinates')
14 | self.parameters.declare('h_avg')
15 |
16 | def define(self):
17 | nel = self.parameters['nel']
18 | beta = self.parameters['beta']
19 | coordinates = self.parameters['coordinates']
20 | h_avg = self.parameters['h_avg']
21 | density_unfiltered = self.declare_variable('density_unfiltered',
22 | shape=(nel,),
23 | val=1.0)
24 |
25 | e = GeneralFilterOperation(nel=nel,
26 | beta=beta,
27 | coordinates=coordinates,
28 | h_avg=h_avg)
29 | output = csdl.custom(density_unfiltered, op=e)
30 | self.register_output('density', output)
31 |
32 |
33 | class GeneralFilterOperation(CustomExplicitOperation):
34 | """
35 | input: unfiltered density
36 | output: filtered density
37 | """
38 | def initialize(self):
39 | self.parameters.declare('nel')
40 | self.parameters.declare('beta', default=2.)
41 | self.parameters.declare('coordinates')
42 | self.parameters.declare('h_avg')
43 |
44 | def define(self):
45 | nel = self.parameters['nel']
46 | beta = self.parameters['beta']
47 | coords = self.parameters['coordinates']
48 | h_avg = self.parameters['h_avg']
49 |
50 | self.add_input('density_unfiltered',
51 | shape=(nel,),
52 | val=0.0)
53 | self.add_output('density',
54 | shape=(nel,))
55 | weight_ij, rows, cols = self.compute_weight_mat(coords, h_avg, beta, nel)
56 | self.weight_mtx = scipy.sparse.csr_matrix((weight_ij,
57 | (rows, cols)),
58 | shape=(nel, nel))
59 | self.declare_derivatives('density', 'density_unfiltered',
60 | rows=rows,
61 | cols=cols,
62 | val=weight_ij)
63 |
64 | def compute(self, inputs, outputs):
65 | outputs['density'] = self.weight_mtx.dot(inputs['density_unfiltered'])
66 |
67 | def compute_weight_mat(self, coords, h_avg, beta, nel):
68 | radius = beta * h_avg
69 |
70 | weight_ij = []
71 | col = []
72 | row = []
73 |
74 | for i in range(nel):
75 | current_point = coords[i,:]
76 | points_selection = coords
77 | tree = spatial.cKDTree(points_selection)
78 | idx = tree.query_ball_point(list(current_point), radius)
79 | nearest_points = points_selection[idx]
80 | di = np.linalg.norm(current_point - nearest_points,axis = 1)
81 | weight_sum = sum(radius - di)
82 |
83 | for j in idx:
84 | dj = np.linalg.norm(current_point - points_selection[j])
85 | weight = (radius - dj)/weight_sum
86 | row.append(i)
87 | col.append(j)
88 | weight_ij.append(weight)
89 |
90 | return weight_ij, row, col
91 |
--------------------------------------------------------------------------------
/examples/beam_topo_opt/run_topo_opt_cantilever_beam.py:
--------------------------------------------------------------------------------
1 |
2 | from curses import resize_term
3 | from femo.fea.fea_dolfinx import *
4 | from femo.csdl_opt.fea_model import FEAModel
5 | from femo.csdl_opt.state_model import StateModel
6 | from femo.csdl_opt.output_model import OutputModel
7 | from pre_processor.general_filter_model import GeneralFilterModel
8 | import numpy as np
9 | import csdl
10 | from csdl import Model
11 | from python_csdl_backend import Simulator as Simulator
12 | from matplotlib import pyplot as plt
13 | import argparse
14 |
15 | '''
16 | 1. Define the mesh
17 | '''
18 |
19 | parser = argparse.ArgumentParser()
20 | parser.add_argument('--nelx',dest='nelx',default='80',
21 | help='Number of elements in x direction')
22 | parser.add_argument('--nely',dest='nely',default='40',
23 | help='Number of elements in x direction')
24 |
25 | args = parser.parse_args()
26 | num_el_x = int(args.nelx)
27 | num_el_y = int(args.nely)
28 |
29 | LENGTH_X = 160.
30 | LENGTH_Y = 80.
31 | mesh = createRectangleMesh(np.array([0.0,0.0]),
32 | np.array([LENGTH_X, LENGTH_Y]),
33 | num_el_x,
34 | num_el_y)
35 |
36 | '''
37 | 2. Set up the PDE problem
38 | '''
39 |
40 | '''
41 | 2.1 Define the traction boundary for the source term
42 | '''
43 | #### Getting facets of the bottom edge that will come in contact ####
44 | DOLFIN_EPS = 3E-16
45 | def TractionBoundary(x):
46 | return np.logical_and(abs(x[1] - LENGTH_Y/2) < LENGTH_Y/num_el_y + DOLFIN_EPS*1e10,
47 | abs(x[0] - LENGTH_X) < DOLFIN_EPS*1e10)
48 |
49 | fdim = mesh.topology.dim - 1
50 | traction_facets = locate_entities_boundary(mesh,fdim,TractionBoundary)
51 | facet_tag = meshtags(mesh, fdim, traction_facets,
52 | np.full(len(traction_facets),100,dtype=np.int32))
53 |
54 | #### Defining measures ####
55 | metadata = {"quadrature_degree":4}
56 | import ufl
57 | ds_ = ufl.Measure('ds',domain=mesh,subdomain_data=facet_tag,metadata=metadata)
58 |
59 | '''
60 | 2.2 Define variational forms for PDE residual and outputs
61 | '''
62 | def pdeRes(u, v, rho_e, f, E = 1, dss = ds, method='SIMP'):
63 | if method =='SIMP':
64 | C = rho_e**3
65 | else:
66 | C = rho_e/(1 + 8. * (1. - rho_e))
67 | E = 1. * C # C is the design variable, its values is from 0 to 1
68 | nu = 0.3 # Poisson's ratio
69 | lambda_ = E * nu/(1. + nu)/(1 - 2 * nu)
70 | mu = E / 2 / (1 + nu) #lame's parameters
71 |
72 | w_ij = 0.5 * (grad(u) + grad(u).T)
73 | v_ij = 0.5 * (grad(v) + grad(v).T)
74 | d = len(u)
75 | sigm = lambda_*div(u)*Identity(d) + 2*mu*w_ij
76 | res = inner(sigm, v_ij) * dx - dot(f, v) * dss
77 | return res
78 |
79 | def averageFunc(func):
80 | volume = assemble(Constant(mesh,1.0)*dx)
81 | func1 = Function(func.function_space)
82 | func1.vector.set(1/volume)
83 | return inner(func,func1)*dx
84 |
85 | def compliance(u, f, dss=ds):
86 | return dot(u,f)*dss
87 | ###################################################
88 |
89 | fea = FEA(mesh)
90 | # Add input to the PDE problem:
91 | input_name = 'density'
92 | input_function_space = FunctionSpace(mesh, ('DG', 0))
93 | input_function = Function(input_function_space)
94 | gradient_function = Function(input_function_space)
95 | # Add state to the PDE problem:
96 | state_name = 'displacements'
97 | state_function_space = VectorFunctionSpace(mesh, ('CG', 1))
98 | state_function = Function(state_function_space)
99 | v = TestFunction(state_function_space)
100 | method = 'SIMP'
101 | f = Constant(mesh, (0,-1/4))
102 | residual_form = pdeRes(state_function,
103 | v,
104 | input_function,
105 | f,
106 | dss=ds_(100),
107 | method=method)
108 |
109 | # Add output to the PDE problem:
110 | output_name_1 = 'avg_density'
111 | output_form_1 = averageFunc(input_function)
112 | output_name_2 = 'compliance'
113 | output_form_2 = compliance(state_function,
114 | f,
115 | dss=ds_(100))
116 |
117 |
118 |
119 | fea.record = True
120 | fea.add_input(input_name, input_function)
121 | fea.add_state(name=state_name,
122 | function=state_function,
123 | residual_form=residual_form,
124 | arguments=[input_name])
125 | fea.add_output(name=output_name_1,
126 | type='scalar',
127 | form=output_form_1,
128 | arguments=[input_name])
129 | fea.add_output(name=output_name_2,
130 | type='scalar',
131 | form=output_form_2,
132 | arguments=[state_name])
133 |
134 | '''
135 | 2.3. Define the boundary conditions
136 | '''
137 |
138 | ############ Strongly enforced boundary conditions #############
139 | ubc = Function(state_function_space)
140 | ubc.vector.set(0.0)
141 | locate_BC1 = locate_dofs_geometrical((state_function_space, state_function_space),
142 | lambda x: np.isclose(x[0], 0. ,atol=1e-6))
143 | locate_BC_list = [locate_BC1]
144 | fea.add_strong_bc(ubc, locate_BC_list, state_function_space)
145 |
146 | ############ Weakly enforced boundary conditions #############
147 | ############### Unsymmetric Nitsche's method #################
148 | # residual_form = pdeRes(state_function, v, input_function,
149 | # u_exact=u_ex, weak_bc=True, sym=False)
150 | ##############################################################
151 |
152 |
153 |
154 |
155 | '''
156 | 4. Set up the CSDL model
157 | '''
158 | fea_model = FEAModel(fea=[fea])
159 |
160 |
161 | pre_processor_name = 'general_filter_model'
162 |
163 | coords = input_function_space.tabulate_dof_coordinates()
164 | tdim = mesh.topology.dim
165 | num_cells = mesh.topology.index_map(tdim).size_local
166 | h = dolfinx.cpp.mesh.h(mesh, tdim, range(num_cells))
167 | h_avg = (h.max() + h.min())/2
168 | nel = mesh.topology.index_map(mesh.topology.dim).size_local
169 | # Case-to-case preprocessor model
170 | pre_processor_model = GeneralFilterModel(nel=nel,
171 | coordinates=coords,
172 | h_avg=h_avg)
173 | fea_model.add(pre_processor_model, name=pre_processor_name)
174 |
175 | np.random.seed(0)
176 | fea_model.create_input("{}".format('density_unfiltered'),
177 | shape=nel,
178 | val=np.random.random(nel) * 0.86)
179 |
180 | fea_model.add_design_variable('density_unfiltered', upper=1.0, lower=1e-4)
181 | fea_model.add_objective('compliance')
182 | fea_model.add_constraint('avg_density', upper=0.40)
183 | sim = Simulator(fea_model,analytics=True)
184 | ########### Test the forward solve ##############
185 | sim.run()
186 |
187 | ########### Generate the N2 diagram #############
188 | # sim.visualize_implementation()
189 |
190 |
191 | ############# Check the derivatives #############
192 | # sim.check_totals(of=['displacements'], wrt=['density_unfiltered'],
193 | # compact_print=True)
194 | # derivative_dict = sim.compute_totals(of=['compliance'], wrt=['density'])
195 | # dCdRho = derivative_dict[('compliance', 'density')].flatten()
196 | # gradient_function.vector.setArray(dCdRho)
197 | '''
198 | 5. Set up the optimization problem
199 | '''
200 | # ############# Run the optimization with pyOptSparse #############
201 | # import openmdao.api as om
202 | # ###### Driver = SNOPT #########
203 | # driver = om.pyOptSparseDriver()
204 | # driver.options['optimizer']='SNOPT'
205 | # driver.opt_settings['Verify level'] = 0
206 | #
207 | # driver.opt_settings['Major iterations limit'] = 100000
208 | # driver.opt_settings['Minor iterations limit'] = 100000
209 | # driver.opt_settings['Iterations limit'] = 100000000
210 | # driver.opt_settings['Major step limit'] = 2.0
211 | #
212 | # driver.opt_settings['Major feasibility tolerance'] = 1e-6
213 | # driver.opt_settings['Major optimality tolerance'] = 1e-8
214 | # driver.options['print_results'] = False
215 | #
216 | # sim.prob.driver = driver
217 | # sim.prob.setup()
218 | #
219 | # from timeit import default_timer
220 | # start = default_timer()
221 | #
222 | # sim.prob.run_driver()
223 | #
224 | # stop = default_timer()
225 | # print('Optimization runtime:', str(stop-start), 'seconds')
226 | ############# Run the optimization with modOpt #############
227 | from modopt import CSDLProblem
228 |
229 | prob = CSDLProblem(
230 | problem_name='beam_topo_opt',
231 | simulator=sim,
232 | )
233 |
234 | #### Optional to run optiomization with SNOPT ####
235 | # from modopt import SNOPT
236 |
237 | # optimizer = SNOPT(prob,
238 | # Major_iterations = 100000,
239 | # Major_optimality = 1e-8,
240 | # Major_feasibility=1e-6,
241 | # append2file=True)
242 | # # append2file=False)
243 |
244 |
245 | # # Check first derivatives at the initial guess, if needed
246 | # optimizer.check_first_derivatives(prob.x0)
247 |
248 | # optimizer.solve()
249 |
250 | # optimizer.print_results()
251 |
252 | print("Compliance value: ", sim['compliance'])
253 | print("Constraint value: ", sim['avg_density'])
254 |
255 | penalized_density = Function(input_function_space)
256 | if method =='SIMP':
257 | project(input_function**3, penalized_density)
258 | else:
259 | project(input_function/(1 + 8. * (1. - input_function)),
260 | penalized_density)
261 |
262 | with XDMFFile(MPI.COMM_WORLD, "solutions/"+state_name+".xdmf", "w") as xdmf:
263 | xdmf.write_mesh(fea.mesh)
264 | xdmf.write_function(fea.states_dict[state_name]['function'])
265 | with XDMFFile(MPI.COMM_WORLD, "solutions/penalized_density.xdmf", "w") as xdmf:
266 | xdmf.write_mesh(fea.mesh)
267 | xdmf.write_function(penalized_density)
268 | with XDMFFile(MPI.COMM_WORLD, "solutions/"+input_name+".xdmf", "w") as xdmf:
269 | xdmf.write_mesh(fea.mesh)
270 | xdmf.write_function(fea.inputs_dict[input_name]['function'])
271 | # with XDMFFile(MPI.COMM_WORLD, "solutions/gradient.xdmf", "w") as xdmf:
272 | # xdmf.write_mesh(fea.mesh)
273 | # xdmf.write_function(gradient_function)
274 |
275 | # Plot the traction bc
276 | #with XDMFFile(MPI.COMM_WORLD, "solutions/traction_bc.xdmf", "w") as xdmf:
277 | # xdmf.write_mesh(mesh)
278 | # mesh.topology.create_connectivity(mesh.topology.dim-1,mesh.topology.dim)
279 | # xdmf.write_meshtags(facet_tag)
280 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/em_motor_opt/__init__.py
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1.msh:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7f0454f578e33524c1a5b45ab5246f615c65ab71e37c47f1c3d2ffef0a8ede8d
3 | size 222841
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1_association_table.ini:
--------------------------------------------------------------------------------
1 | [ASSOCIATION TABLE]
2 | all curves = 1000
3 | rotor core = 1
4 | stator core = 2
5 | magnet 1 = 3
6 | magnet 2 = 4
7 | magnet 3 = 5
8 | magnet 4 = 6
9 | magnet 5 = 7
10 | magnet 6 = 8
11 | magnet 7 = 9
12 | magnet 8 = 10
13 | magnet 9 = 11
14 | magnet 10 = 12
15 | magnet 11 = 13
16 | magnet 12 = 14
17 | winding b 1 = 15
18 | winding a 1 = 16
19 | winding c 1 = 17
20 | winding b 2 = 18
21 | winding a 2 = 19
22 | winding c 2 = 20
23 | winding b 3 = 21
24 | winding a 3 = 22
25 | winding c 3 = 23
26 | winding b 4 = 24
27 | winding a 4 = 25
28 | winding c 4 = 26
29 | winding b 5 = 27
30 | winding a 5 = 28
31 | winding c 5 = 29
32 | winding b 6 = 30
33 | winding a 6 = 31
34 | winding c 6 = 32
35 | winding b 7 = 33
36 | winding a 7 = 34
37 | winding c 7 = 35
38 | winding b 8 = 36
39 | winding a 8 = 37
40 | winding c 8 = 38
41 | winding b 9 = 39
42 | winding a 9 = 40
43 | winding c 9 = 41
44 | winding b 10 = 42
45 | winding a 10 = 43
46 | winding c 10 = 44
47 | winding b 11 = 45
48 | winding a 11 = 46
49 | winding c 11 = 47
50 | winding b 12 = 48
51 | winding a 12 = 49
52 | winding c 12 = 50
53 | air gap = 51
54 | air slot low 1 = 52
55 | air slot high 1 = 53
56 | air slot low 2 = 54
57 | air slot high 2 = 55
58 | air slot low 3 = 56
59 | air slot high 3 = 57
60 | air slot low 4 = 58
61 | air slot high 4 = 59
62 | air slot low 5 = 60
63 | air slot high 5 = 61
64 | air slot low 6 = 62
65 | air slot high 6 = 63
66 | air slot low 7 = 64
67 | air slot high 7 = 65
68 | air slot low 8 = 66
69 | air slot high 8 = 67
70 | air slot low 9 = 68
71 | air slot high 9 = 69
72 | air slot low 10 = 70
73 | air slot high 10 = 71
74 | air slot low 11 = 72
75 | air slot high 11 = 73
76 | air slot low 12 = 74
77 | air slot high 12 = 75
78 |
79 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1_boundaries.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0b1005c6d48a354b629328519b2e4246dd814ba19c0c22c0d2f9664276b823fb
3 | size 19725
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1_boundaries.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f0a09e65c80d6a5acda2faf1b4fb683a7857e99a1ad4296a3d2150d3c7eb4545
3 | size 635
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1_domain.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:24d69fc90db85c3acb65354c453d62d0add3f4d8ca177fcf9b255a6c820fbe36
3 | size 31425
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_coarse/motor_mesh_test_1_domain.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:fcd8295ab53f1fb850b7ed828ba3071096cb9387845549408ac6406f097c82f2
3 | size 626
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1.msh:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:e4d55ed3bf455657f516dfe738a90f2b5674b7be4ed26de6062236aaaf8edaf6
3 | size 3590820
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1_association_table.ini:
--------------------------------------------------------------------------------
1 | [ASSOCIATION TABLE]
2 | all curves = 1000
3 | rotor core = 1
4 | stator core = 2
5 | magnet 1 = 3
6 | magnet 2 = 4
7 | magnet 3 = 5
8 | magnet 4 = 6
9 | magnet 5 = 7
10 | magnet 6 = 8
11 | magnet 7 = 9
12 | magnet 8 = 10
13 | magnet 9 = 11
14 | magnet 10 = 12
15 | magnet 11 = 13
16 | magnet 12 = 14
17 | winding b 1 = 15
18 | winding a 1 = 16
19 | winding c 1 = 17
20 | winding b 2 = 18
21 | winding a 2 = 19
22 | winding c 2 = 20
23 | winding b 3 = 21
24 | winding a 3 = 22
25 | winding c 3 = 23
26 | winding b 4 = 24
27 | winding a 4 = 25
28 | winding c 4 = 26
29 | winding b 5 = 27
30 | winding a 5 = 28
31 | winding c 5 = 29
32 | winding b 6 = 30
33 | winding a 6 = 31
34 | winding c 6 = 32
35 | winding b 7 = 33
36 | winding a 7 = 34
37 | winding c 7 = 35
38 | winding b 8 = 36
39 | winding a 8 = 37
40 | winding c 8 = 38
41 | winding b 9 = 39
42 | winding a 9 = 40
43 | winding c 9 = 41
44 | winding b 10 = 42
45 | winding a 10 = 43
46 | winding c 10 = 44
47 | winding b 11 = 45
48 | winding a 11 = 46
49 | winding c 11 = 47
50 | winding b 12 = 48
51 | winding a 12 = 49
52 | winding c 12 = 50
53 | air gap = 51
54 | air slot low 1 = 52
55 | air slot high 1 = 53
56 | air slot low 2 = 54
57 | air slot high 2 = 55
58 | air slot low 3 = 56
59 | air slot high 3 = 57
60 | air slot low 4 = 58
61 | air slot high 4 = 59
62 | air slot low 5 = 60
63 | air slot high 5 = 61
64 | air slot low 6 = 62
65 | air slot high 6 = 63
66 | air slot low 7 = 64
67 | air slot high 7 = 65
68 | air slot low 8 = 66
69 | air slot high 8 = 67
70 | air slot low 9 = 68
71 | air slot high 9 = 69
72 | air slot low 10 = 70
73 | air slot high 10 = 71
74 | air slot low 11 = 72
75 | air slot high 11 = 73
76 | air slot low 12 = 74
77 | air slot high 12 = 75
78 |
79 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1_boundaries.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:80d4a87a1e91bfe493e1976f483d26477ffe8de913e541a8868af796a34adede
3 | size 497846
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1_boundaries.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c641946e72d73bb8fea35fe41b95c971898c922da8c4f99095069b91a48ed56b
3 | size 624
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1_domain.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:b40d8dad052192d014ac32697220e2604f7f81148b274821c4f71369dbd99d99
3 | size 918977
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_fine/motor_mesh_1_domain.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:b04d8767ed70656955379e819d0f87fba5d7d6596a6f933d3ac00d13b5889707
3 | size 615
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1.msh:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:87348609c60170edeee2cf2b89cbd6169b90e32f41c6349b0ff2e0f5977645be
3 | size 1497931
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_1.msh:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:31cd000b9a27458f4c03d233d9c836ea773ecb5bfd8abf8c6b2fc49f30dac01b
3 | size 1498130
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_association_table.ini:
--------------------------------------------------------------------------------
1 | [ASSOCIATION TABLE]
2 | all curves = 1000
3 | rotor core = 1
4 | stator core = 2
5 | magnet 1 = 3
6 | magnet 2 = 4
7 | magnet 3 = 5
8 | magnet 4 = 6
9 | magnet 5 = 7
10 | magnet 6 = 8
11 | magnet 7 = 9
12 | magnet 8 = 10
13 | magnet 9 = 11
14 | magnet 10 = 12
15 | magnet 11 = 13
16 | magnet 12 = 14
17 | winding b 1 = 15
18 | winding a 1 = 16
19 | winding c 1 = 17
20 | winding b 2 = 18
21 | winding a 2 = 19
22 | winding c 2 = 20
23 | winding b 3 = 21
24 | winding a 3 = 22
25 | winding c 3 = 23
26 | winding b 4 = 24
27 | winding a 4 = 25
28 | winding c 4 = 26
29 | winding b 5 = 27
30 | winding a 5 = 28
31 | winding c 5 = 29
32 | winding b 6 = 30
33 | winding a 6 = 31
34 | winding c 6 = 32
35 | winding b 7 = 33
36 | winding a 7 = 34
37 | winding c 7 = 35
38 | winding b 8 = 36
39 | winding a 8 = 37
40 | winding c 8 = 38
41 | winding b 9 = 39
42 | winding a 9 = 40
43 | winding c 9 = 41
44 | winding b 10 = 42
45 | winding a 10 = 43
46 | winding c 10 = 44
47 | winding b 11 = 45
48 | winding a 11 = 46
49 | winding c 11 = 47
50 | winding b 12 = 48
51 | winding a 12 = 49
52 | winding c 12 = 50
53 | air gap = 51
54 | air slot low 1 = 52
55 | air slot high 1 = 53
56 | air slot low 2 = 54
57 | air slot high 2 = 55
58 | air slot low 3 = 56
59 | air slot high 3 = 57
60 | air slot low 4 = 58
61 | air slot high 4 = 59
62 | air slot low 5 = 60
63 | air slot high 5 = 61
64 | air slot low 6 = 62
65 | air slot high 6 = 63
66 | air slot low 7 = 64
67 | air slot high 7 = 65
68 | air slot low 8 = 66
69 | air slot high 8 = 67
70 | air slot low 9 = 68
71 | air slot high 9 = 69
72 | air slot low 10 = 70
73 | air slot high 10 = 71
74 | air slot low 11 = 72
75 | air slot high 11 = 73
76 | air slot low 12 = 74
77 | air slot high 12 = 75
78 |
79 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_boundaries.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:4e3fe334cd28ff58fbec16f83621f54d1eb90211b2ea53768709775de105e3b3
3 | size 198707
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_boundaries.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:d062109030719345dbbd874dede4b98bfbcec46a17d1459581fae4b5fb21f2ce
3 | size 624
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_domain.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:612ae10fda36293139eaa4d464f44bc96415bc4ad90e986be8483e307722d401
3 | size 354660
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_data/motor_data_test/motor_mesh_1_domain.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:47735614607cd3075fdf8e685a6fdc877555d50d7ca8904890af383505417e41
3 | size 615
4 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/motor_pde.py:
--------------------------------------------------------------------------------
1 | """
2 | Definition of the variational form of the motor problem
3 | """
4 | from femo.fea.fea_dolfinx import *
5 | from permeability.piecewise_permeability import *
6 |
7 |
8 | exp_coeff = extractexpDecayCoeff()
9 | cubic_bounds = extractCubicBounds()
10 |
11 | # START NEW PERMEABILITY
12 | def RelativePermeability(subdomain, u, uhat):
13 | gradu = gradx(u,uhat)
14 | if subdomain == 1 or subdomain == 2: # Electrical/Silicon/Laminated Steel
15 | B = as_vector([gradu[1], -gradu[0]])
16 | norm_B = sqrt(dot(B, B) + DOLFIN_EPS)
17 |
18 | mu = conditional(
19 | lt(norm_B, cubic_bounds[0]),
20 | linearPortion(norm_B),
21 | conditional(
22 | lt(norm_B, cubic_bounds[1]),
23 | cubicPortion(norm_B),
24 | (exp_coeff[0] * exp(exp_coeff[1]*norm_B + exp_coeff[2]) + 1)
25 | )
26 | )
27 | elif subdomain >= 3 and subdomain <= 14: # NEODYMIUM
28 | mu = 1.05
29 | elif subdomain >= 15 and subdomain <= 50: # COPPER
30 | mu = 1.00
31 | elif subdomain == 51: # insert value for titanium or shaft material
32 | mu = 1.00
33 | elif subdomain >= 52: # AIR
34 | mu = 1.00
35 | return mu
36 | # END NEW PERMEABILITY
37 |
38 | def compute_i_abc(iq, angle=0.0):
39 | i_abc = as_vector([
40 | iq * np.sin(angle),
41 | iq * np.sin(angle - 2*np.pi/3),
42 | iq * np.sin(angle + 2*np.pi/3),
43 | ])
44 | return i_abc
45 |
46 | def JS(v,uhat,iq,p,s,Hc,angle):
47 | """
48 | The variational form for the source term (current) of the
49 | Maxwell equation
50 | """
51 | Jm = 0.
52 | gradv = gradx(v,uhat)
53 | base_magnet_dir = 2 * np.pi / p / 2
54 | magnet_sweep = 2 * np.pi / p
55 | for i in range(p):
56 | flux_angle = base_magnet_dir + i * magnet_sweep
57 | Hx = (-1)**(i) * Hc * np.cos(flux_angle + angle*2/p)
58 | Hy = (-1)**(i) * Hc * np.sin(flux_angle + angle*2/p)
59 |
60 | H = as_vector([Hx, Hy])
61 |
62 | curl_v = as_vector([gradv[1],-gradv[0]])
63 | Jm += inner(H,curl_v)* J(uhat) *dx(i + 2 + 1)
64 |
65 | num_phases = 3
66 | num_windings = s
67 | coil_per_phase = 2
68 | stator_winding_index_start = p + 2 + 1
69 | stator_winding_index_end = stator_winding_index_start + num_windings
70 | Jw = 0.
71 | i_abc = compute_i_abc(iq, angle)
72 | JA, JB, JC = i_abc[0] + DOLFIN_EPS, i_abc[1] + DOLFIN_EPS, i_abc[2] + DOLFIN_EPS
73 |
74 |
75 | coils_per_pole = 3
76 | for i in range(p): # assigning current densities for each set of poles
77 | coil_start_ind = stator_winding_index_start + i * coils_per_pole
78 | coil_end_ind = coil_start_ind + coils_per_pole
79 |
80 | J_list = [
81 | JB * (-1)**(i+1) * v * J(uhat) * dx(coil_start_ind),
82 | JA * (-1)**(i) * v * J(uhat) * dx(coil_start_ind + 1),
83 | JC * (-1)**(i+1) * v * J(uhat) * dx(coil_start_ind + 2),
84 | ]
85 | Jw += sum(J_list)
86 |
87 | return Jm + Jw
88 |
89 |
90 | def pdeResEM(u,v,uhat,iq,dx,p,s,Hc,vacuum_perm,angle,
91 | g=None,nitsche=False, sym=False, overpenalty=False,ds_=ds):
92 | """
93 | The variational form of the PDE residual for the electromagnetic problem
94 | """
95 | res = 0.
96 | gradu = gradx(u,uhat)
97 | gradv = gradx(v,uhat)
98 |
99 | num_components = 4 * 3 * p + 2 * s
100 | for i in range(num_components):
101 | res += 1./vacuum_perm*(1/RelativePermeability(i + 1, u, uhat))\
102 | *dot(gradu,gradv)*J(uhat)*dx(i + 1)
103 | res -= JS(v,uhat,iq,p,s,Hc,angle)
104 |
105 | mesh = u.function_space.mesh
106 | boundary_res = 0.
107 | if nitsche is True:
108 | beta = 1e4
109 | sgn = 1.0
110 | if sym is not True:
111 | sgn = -1.0
112 | n = FacetNormal(mesh)
113 | # transform normal and area element by Nanson's formula:
114 | dsx_dsy_n_x = J(uhat)*inv(F(uhat).T)*n
115 | norm_dsx_dsy_n_x = ufl.sqrt(ufl.dot(dsx_dsy_n_x, dsx_dsy_n_x))
116 |
117 | h_E = CellDiameter(mesh)
118 | boundary_components = [0,1]
119 | for i in boundary_components:
120 | coeff = 1./vacuum_perm*(1/RelativePermeability(i + 1, u, uhat))
121 | nitsche_term = coeff*(- inner(dot(gradu,dsx_dsy_n_x),v) \
122 | - sgn*inner(dot(gradv,dsx_dsy_n_x),u-g))*ds_
123 | boundary_res += nitsche_term
124 |
125 | penalty_term = beta/h_E*coeff*norm_dsx_dsy_n_x*inner(v,u-g)*ds_
126 | if sym is True or overpenalty is True:
127 | boundary_res += penalty_term
128 |
129 | res += boundary_res
130 | return res
131 |
132 | I = Identity(2)
133 |
134 | def pdeResMM(uhat, duhat, g=None,
135 | nitsche=False, sym=False, overpenalty=False,
136 | dS_=dS, ds_=ds):
137 | """
138 | Formulation of mesh motion as a hyperelastic problem
139 | """
140 | mesh = uhat.function_space.mesh
141 | # Residual for mesh, which satisfies a fictitious elastic problem:
142 | def _F(u):
143 | return grad(u)+I
144 | def _sigma(u):
145 | F = _F(u)
146 | E = 0.5*(F.T*F-I)
147 | m_jac_stiff_pow = 3
148 | # Artificially stiffen the mesh where it is getting crushed:
149 | K = 1/pow(det(F),m_jac_stiff_pow)
150 | mu = 1/pow(det(F),m_jac_stiff_pow)
151 | S = K*tr(E)*I + 2.0*mu*(E - tr(E)*I/3.0)
152 | return S
153 | def P(u):
154 | return _F(u)*_sigma(u)
155 |
156 | F_m = _F(uhat)
157 | S_m = _sigma(uhat)
158 | P_m = P(uhat)
159 | dS_m = _sigma(duhat)
160 | res_m = (inner(P_m,grad(duhat)))*dx
161 |
162 |
163 | if nitsche is True:
164 | beta = 5e3/pow(det(F_m),3)
165 | sgn = 1.0
166 | if sym is not True:
167 | sgn = -1.0
168 | n = FacetNormal(mesh)
169 | h_E = CellDiameter(mesh)
170 | f0 = -div(P(g))
171 | res_m += -dot(f0, duhat)*dx
172 | nitsche_1 = - inner(dot(P_m,n),duhat)
173 | nitsches_term_1 = nitsche_1("+")*dS_ + nitsche_1("-")*dS_ + nitsche_1*ds_
174 | dP = derivative(P_m, uhat, duhat)
175 | nitsche_2 = sgn * inner(dP*n,uhat-g)
176 | nitsches_term_2 = nitsche_2("+")*dS_ + nitsche_2("-")*dS_ + nitsche_2*ds_
177 | penalty = beta/h_E*inner(duhat,uhat-g)
178 | penalty_term = penalty("+")*dS_ + penalty("-")*dS_ + penalty*ds_
179 | res_m += nitsches_term_1
180 | res_m += nitsches_term_2
181 | if sym is True or overpenalty is True:
182 | res_m += penalty_term
183 | return res_m
184 |
185 |
186 | def B_power_form(A_z, uhat, n, dx, subdomains):
187 | """
188 | Return the ufl form of `B**n*dx(subdomains)`
189 | """
190 |
191 | mesh = uhat.function_space.mesh
192 | gradA_z = gradx(A_z,uhat)
193 | B_power_form = 0.
194 | B_magnitude = sqrt(gradA_z[0]**2+gradA_z[1]**2)
195 | for subdomain_id in subdomains:
196 | B_power_form += pow(B_magnitude, n)*J(uhat)*dx(subdomain_id)
197 | return B_power_form
198 |
199 | def area_form(uhat, dx, subdomains):
200 | """
201 | Return the ufl form of `uhat*dx(subdomains)`
202 | """
203 | if type(subdomains) == int:
204 | subdomain_group = [subdomains]
205 | else:
206 | subdomain_group = subdomains
207 | area = 0
208 | for subdomain_id in subdomain_group:
209 | area += J(uhat)*dx(subdomain_id)
210 | return area
211 |
212 | def B(A_z, uhat):
213 | gradA_z = gradx(A_z,uhat)
214 | B_form = as_vector((gradA_z[1], -gradA_z[0]))
215 | # dB_dAz = derivative(B_form, state_function_em)
216 |
217 | mesh = uhat.function_space.mesh
218 | VB = VectorFunctionSpace(mesh,('DG',0))
219 | B = Function(VB)
220 | project(B_form,B)
221 | return B
222 |
223 | def getFuncAverageSubdomain(func, uhat, dx, subdomain):
224 | """
225 | Compute the average function value over a subdomain
226 | """
227 | func_unit = Function(func.function_space)
228 | func_unit.vector.set(1.0)
229 | integral = inner(func, func_unit)*J(uhat)*dx(subdomain)
230 | area = area_form(uhat, dx, subdomain)
231 | print('subdomain:', subdomain)
232 | print('area:', assemble(area))
233 | avg_func = assemble(integral)/assemble(area)
234 | print('avg func over subdomain:', avg_func)
235 | print('avg func over subdomain:', assemble(avg_func))
236 | return avg_func
237 |
238 | # TODO
239 | def getFuncAverageSubdomainDerivatives(func, uhat, dx, subdomain):
240 | '''
241 | Get the partial derivatives of the area-integrated function
242 | w.r.t. A_z and uhat
243 | '''
244 | F = getFuncAverageSubdomain(func, subdomain)
245 | dFdAz = derivative(F, func)
246 | dFdAz_array = assemble(dFdAz).get_local()
247 | dFduhat = derivative(F, uhat)
248 | dFduhat_array = assemble(dFduhat).get_local()
249 |
250 | return dFdAz_array, dFduhat_array
251 |
252 | def calcAreaIntegratedAz(A_z, uhat, dx, slot_subdomains):
253 | """
254 | Compute the average function value over a subdomain
255 | """
256 | A_bar_slot = np.zeros(len(slot_subdomains),)
257 | for i, ind in enumerate(slot_subdomains):
258 | A_bar_slot_ind = getFuncAverageSubdomain(A_z,uhat,dx,ind)
259 | A_bar_slot[i] = A_bar_slot_ind
260 | return A_bar_slot
261 |
262 | # TODO
263 | def calcAreaIntegratedAzDerivatives(A_z, uhat, dx, subdomain):
264 | dFdAz_array, dFduhat_array = getFuncAverageSubdomainDerivatives(A_z, uhat, dx, subdomain)
265 | return dFdAz_array, dFduhat_array
266 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/permeability/Magnetic alloy, silicon core iron C.tab:
--------------------------------------------------------------------------------
1 | "H (A_per_meter)" "B (tesla)"
2 | 0 0
3 | 32.600000000000001 0.1139
4 | 194.30000000000001 1.004
5 | 356.10000000000002 1.2629999999999999
6 | 517.79999999999995 1.3580000000000001
7 | 679.60000000000002 1.4330000000000001
8 | 841.29999999999995 1.4590000000000001
9 | 1003 1.482
10 | 1165 1.504
11 | 1327 1.5209999999999999
12 | 1488 1.53
13 | 1650 1.538
14 | 1812 1.5469999999999999
15 | 1974 1.5549999999999999
16 | 2135 1.5629999999999999
17 | 2297 1.5720000000000001
18 | 2459 1.577
19 | 2620 1.581
20 | 2782 1.585
21 | 2944 1.589
22 | 3106 1.593
23 | 3267 1.597
24 | 3429 1.601
25 | 3591 1.605
26 | 3753 1.609
27 | 3914 1.613
28 | 4076 1.617
29 | 4238 1.6220000000000001
30 | 4400 1.625
31 | 4561 1.6279999999999999
32 | 4723 1.631
33 | 4885 1.6339999999999999
34 | 5047 1.6359999999999999
35 | 5208 1.639
36 | 5370 1.6419999999999999
37 | 5532 1.645
38 | 5694 1.6479999999999999
39 | 5855 1.651
40 | 6017 1.6539999999999999
41 | 6179 1.6559999999999999
42 | 6341 1.659
43 | 6502 1.6619999999999999
44 | 6664 1.665
45 | 6826 1.6679999999999999
46 | 6988 1.671
47 | 7149 1.6739999999999999
48 | 7311 1.6759999999999999
49 | 7473 1.679
50 | 7635 1.6819999999999999
51 | 7796 1.6850000000000001
52 | 7958 1.6879999999999999
53 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/permeability/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/em_motor_opt/permeability/__init__.py
--------------------------------------------------------------------------------
/examples/em_motor_opt/permeability/piecewise_permeability.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from scipy.optimize import curve_fit
4 |
5 | # ------------ GENERAL FUNCTION FORMS ------------
6 | def linfun(x, a, b):
7 | func = a * x + b
8 | return func
9 |
10 | def expfun(x, a, b, c):
11 | func = (a * np.exp((b * x) +c)) + 1
12 | return func
13 |
14 | def cubicfun(x, a, b, c, d):
15 | func = (a * x**3 + b * x**2 + c*x + d)
16 | return func
17 |
18 | def fit_linear():
19 | pass
20 |
21 | def fit_exponential():
22 | pass
23 |
24 | # ---------------------------------------------------------------
25 |
26 | file_name = 'permeability/Magnetic alloy, silicon core iron C.tab'
27 | data = np.genfromtxt(file_name,skip_header = 1, delimiter = '\t')
28 | H_data = data[:,0]
29 | B_data = data[:,1]
30 | mu_data = B_data / H_data / (4e-7*np.pi)
31 |
32 | # LINEAR & EXPONENTIAL FITTING --------------------------------
33 | linear_fit_ind_start = 1
34 | linear_fit_ind_end = 3
35 | exp_fit_ind_start = 4
36 |
37 | B_lin, mu_lin = B_data[linear_fit_ind_start:linear_fit_ind_end], mu_data[linear_fit_ind_start:linear_fit_ind_end]
38 | B_exp, mu_exp = B_data[exp_fit_ind_start:], mu_data[exp_fit_ind_start:]
39 |
40 | popt_lin, pconv_lin = curve_fit(linfun, B_lin, mu_lin)
41 | popt_exp, pconv_exp = curve_fit(expfun, B_exp, mu_exp)
42 |
43 | B_cont = np.linspace(0., 3, 2000)
44 |
45 | mu_lin = linfun(B_cont, popt_lin[0], popt_lin[1])
46 | mu_exp = expfun(B_cont, popt_exp[0], popt_exp[1], popt_exp[2])
47 |
48 | # CUBIC FUNCTION DETERMINATION --------------------------------
49 | # original: x1 = 1.0, x2 = 1.433
50 | x1 = 0.8
51 | x2 = 1.4
52 |
53 | # f: function value
54 | # d: function derivative
55 | mu_lin_f = linfun(x1, popt_lin[0], popt_lin[1])
56 | mu_lin_d = popt_lin[0]
57 | mu_exp_f = expfun(x2, popt_exp[0], popt_exp[1], popt_exp[2])
58 | mu_exp_d = (expfun(x2, popt_exp[0], popt_exp[1], popt_exp[2]) - 1) * popt_exp[1]
59 |
60 | A = np.array([[3*x1**2, 2*x1, 1, 0],
61 | [3*x2**2, 2*x2, 1, 0],
62 | [x1**3, x1**2, x1, 1],
63 | [x2**3, x2**2, x2, 1]])
64 | b = np.array([mu_lin_d, mu_exp_d, mu_lin_f, mu_exp_f])
65 | X_2 = np.linalg.solve(A,b)
66 |
67 | mu_cubic = cubicfun(B_cont, X_2[0], X_2[1], X_2[2], X_2[3])
68 |
69 |
70 | linearA = popt_lin[0]
71 | linearB = popt_lin[1]
72 |
73 | def linearPortion(x):
74 | return linfun(x, linearA, linearB)
75 |
76 | cubicA = X_2[0]
77 | cubicB = X_2[1]
78 | cubicC = X_2[2]
79 | cubicD = X_2[3]
80 |
81 | def cubicPortion(x):
82 | return cubicfun(x, cubicA, cubicB, cubicC, cubicD)
83 |
84 | expA = popt_exp[0]
85 | expB = popt_exp[1]
86 | expC = popt_exp[2]
87 | def expDecayPortion(x):
88 | return expfun(x, expA, expB, expC)
89 |
90 | def extractexpDecayCoeff():
91 | return popt_exp
92 |
93 | def extractCubicBounds():
94 | return x1, x2
95 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/postprocessor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/em_motor_opt/postprocessor/__init__.py
--------------------------------------------------------------------------------
/examples/em_motor_opt/postprocessor/power_loss_model.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import csdl
3 | from csdl import Model
4 | from python_csdl_backend import Simulator as py_simulator
5 |
6 | class LossSumModel(Model):
7 | def define(self):
8 |
9 | eddy_current_loss = self.declare_variable('eddy_current_loss')
10 | hysteresis_loss = self.declare_variable('hysteresis_loss')
11 |
12 | loss_sum = eddy_current_loss + hysteresis_loss
13 |
14 | loss_sum = self.register_output(
15 | name='loss_sum',
16 | var=loss_sum
17 | )
18 |
19 | self.print_var(loss_sum)
20 |
21 |
22 | class PowerLossModel(Model):
23 | def define(self):
24 |
25 | # # COPPER LOSS
26 | # winding_area = self.declare_variable('winding_area')
27 | # num_windings = self.declare_variable('num_windings')
28 | # slot_fill_factor = self.declare_variable('slot_fill_factor')
29 |
30 | # wire_radius = ((winding_area * slot_fill_factor / num_windings) / np.pi) ** 0.5
31 | # wire_radius = self.register_output(
32 | # name='wire_radius',
33 | # var=wire_radius
34 | # )
35 |
36 | # copper_resistivity = self.declare_variable('copper_resistivity')
37 | # copper_permeability = self.declare_variable('copper_permeability', val=1.256629e-6)
38 | frequency = self.declare_variable('frequency', val=1.)
39 | # wire_resistance = self.declare_variable('wire_resistance')
40 | # current_amplitude = self.declare_variable('current_amplitude')
41 |
42 | # wire_skin_depth = (copper_resistivity / (np.pi * frequency * copper_permeability))**(0.5)
43 | # wire_skin_depth = self.register_output('wire_skin_depth', wire_skin_depth)
44 |
45 | # wire_resistance_AC = wire_resistance / ((2*wire_skin_depth/wire_radius) - (wire_skin_depth/wire_radius)**(0.5))
46 | # wire_resistance_AC = self.register_output('wire_resistance_AC', wire_resistance_AC)
47 |
48 | # # copper_loss = 3 * (current_amplitude / np.sqrt(2))**2 * wire_resistance_AC
49 |
50 | # copper_loss = 3 * (current_amplitude / np.sqrt(2))**2 * wire_resistance
51 |
52 | # copper_loss = self.register_output(
53 | # name='copper_loss',
54 | # var=copper_loss
55 | # )
56 |
57 | # EDDY CURRENT LOSS
58 | lamination_thickness = self.declare_variable('lamination_thickness', val=0.2e-3)
59 | avg_flux_influence_ec = self.declare_variable('B_influence_eddy_current')
60 | steel_conductivity = self.declare_variable('steel_conductivity', val=2.17e6) # grain oriented electrical steel
61 | steel_area = self.declare_variable('steel_area')
62 | motor_length = self.declare_variable('motor_length')
63 | # eddy_current_loss = np.pi**2 / 6 * motor_length * avg_flux_influence_ec * frequency**2 * \
64 | # lamination_thickness**2 * steel_conductivity
65 | eddy_current_loss = 2 * np.pi**2 * frequency**2 * motor_length * avg_flux_influence_ec * 0.07
66 |
67 | eddy_current_loss = self.register_output(
68 | name='eddy_current_loss',
69 | var=eddy_current_loss
70 | )
71 |
72 | # HYSTERESIS LOSS
73 | steel_hysteresis_coeff = self.declare_variable('steel_hysteresis_coeff', val=1.91)
74 | avg_flux_influence_h = self.declare_variable('B_influence_hysteresis')
75 | hysteresis_coeff = self.declare_variable('hysteresis_coeff', val=55.)
76 |
77 | # hysteresis_loss = steel_hysteresis_coeff * motor_length * avg_flux_influence_h * frequency
78 | hysteresis_loss = 2*np.pi*frequency*hysteresis_coeff*motor_length*avg_flux_influence_h
79 |
80 | hysteresis_loss = self.register_output(
81 | name='hysteresis_loss',
82 | var=hysteresis_loss
83 | )
84 |
85 | # # WINDAGE LOSSES
86 | # air_density = self.declare_variable('air_density', val=1.204)
87 | # air_viscosity = self.declare_variable('air_viscosity', val=1.825e-5)
88 | # rotor_radius_o = self.declare_variable('rotor_radius_outer', val = 80.e-3)
89 | # stator_radius_i = self.declare_variable('stator_radius_inner', val = 81.e-3)
90 | # omega = self.declare_variable('omega')
91 |
92 | # air_gap_depth = self.register_output(
93 | # name='air_gap_depth',
94 | # var=stator_radius_i - rotor_radius_o
95 | # )
96 |
97 | # azimuthal_vel = self.create_output(
98 | # name='azimuthal_vel',
99 | # shape=(1,)
100 | # )
101 |
102 | # azimuthal_vel[0] = rotor_radius_o**2 * omega / air_gap_depth / (stator_radius_i**2 - rotor_radius_o**2) * \
103 | # (stator_radius_i**2 * csdl.log(stator_radius_i/rotor_radius_o) - 0.5*(stator_radius_i**2 - rotor_radius_o**2))
104 |
105 | # air_gap_Re = air_density * azimuthal_vel * air_gap_depth / air_viscosity
106 | # air_gap_Re = self.register_output(
107 | # name='air_gap_Re',
108 | # var=air_gap_Re
109 | # )
110 |
111 | # air_gap_Ta = air_gap_Re**2 * (air_gap_depth/rotor_radius_o)
112 | # air_gap_Ta = self.register_output(
113 | # name='air_gap_Ta',
114 | # var=air_gap_Ta
115 | # )
116 |
117 | # friction_coeff = 0.0152 / (air_gap_Re)**(0.24)
118 | # friction_coeff = self.register_output(
119 | # name='friction_coeff',
120 | # var=friction_coeff
121 | # )
122 |
123 | # # windage_loss = np.pi * air_density * azimuthal_vel**2 * rotor_radius_o**2 * friction_coeff * motor_length * omega # UNFINISHED
124 | # windage_loss = 2 * np.pi * friction_coeff * air_density * omega**3 * rotor_radius_o**4 * motor_length
125 |
126 | # windage_loss = self.register_output(
127 | # name='windage_loss',
128 | # var=windage_loss
129 | # )
130 |
131 | # # BEARING LOSSES
132 |
133 |
134 | # # STRAY LOSSES
135 | # avg_input_power = self.declare_variable('avg_input_power')
136 | # stray_loss = avg_input_power * 0.01
137 |
138 | # stray_loss = self.register_output(
139 | # name='stray_loss',
140 | # var=stray_loss
141 | # )
142 |
143 |
144 |
145 | if __name__ == '__main__':
146 | aaa = PowerLossModel()
147 | sim = py_simulator(aaa)
148 |
149 | sim.run()
150 |
151 | print(sim['eddy_current_loss'])
152 | sim.check_partials(compact_print=True)
153 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/preprocessor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/em_motor_opt/preprocessor/__init__.py
--------------------------------------------------------------------------------
/examples/em_motor_opt/preprocessor/boundary_input_model.py:
--------------------------------------------------------------------------------
1 | from csdl import Model, CustomExplicitOperation
2 | import csdl
3 | import numpy as np
4 | from python_csdl_backend import Simulator as py_simulator
5 | from scipy.sparse import csr_matrix
6 |
7 | class BoundaryInputModel(Model):
8 | """
9 | input: edge_deltas
10 | output: uhat_bc
11 | """
12 | def initialize(self):
13 |
14 | self.parameters.declare('edge_indices')
15 | self.parameters.declare('output_size', types=int)
16 | def define(self):
17 | edge_indices = self.parameters['edge_indices']
18 | output_size = self.parameters['output_size']
19 | input_size = len(edge_indices)
20 | edge_deltas = self.declare_variable('edge_deltas',
21 | shape=(input_size,),
22 | val=np.zeros(input_size).reshape(input_size,))
23 |
24 | e = LinearMapping(
25 | input_name='edge_deltas',
26 | output_name='uhat_bc',
27 | input_size=input_size,
28 | output_size=output_size,
29 | indices=edge_indices)
30 | output = csdl.custom(edge_deltas, op=e)
31 | self.register_output('uhat_bc', output)
32 |
33 |
34 | class LinearMapping(CustomExplicitOperation):
35 | """
36 | input: array_1
37 | output: array_2
38 | """
39 | def initialize(self):
40 | self.parameters.declare('input_size')
41 | self.parameters.declare('output_size')
42 | self.parameters.declare('indices')
43 | self.parameters.declare('input_name')
44 | self.parameters.declare('output_name')
45 |
46 | def define(self):
47 | self.indices = self.parameters['indices']
48 | self.input_size = self.parameters['input_size']
49 | self.output_size = self.parameters['output_size']
50 | self.input_name = self.parameters['input_name']
51 | self.output_name = self.parameters['output_name']
52 | self.add_input(self.input_name,
53 | shape=(self.input_size,),
54 | val=0.0)
55 | self.add_output(self.output_name,
56 | shape=(self.output_size,),)
57 | self.declare_derivatives('*', '*')
58 |
59 | def compute(self, inputs, outputs):
60 | array_2 = np.zeros(self.output_size)
61 | for i in range(self.input_size):
62 | array_2[self.indices[i]] = inputs[self.input_name][i]
63 | outputs[self.output_name] = array_2
64 |
65 | def compute_derivatives(self, inputs, derivatives):
66 | row_ind = self.indices
67 | col_ind = np.arange(self.input_size)
68 | data = np.ones(self.input_size)
69 | M = csr_matrix((data, (row_ind, col_ind)),
70 | shape=(self.output_size, self.input_size))
71 | derivatives[self.output_name, self.input_name] = M.toarray()
72 |
73 | if __name__ == "__main__":
74 | edge_deltas = np.array([1., 0., 0., 0.1])
75 | edge_indices = np.array([1,2,5,7])
76 | output_size = 10
77 | model = BoundaryInputModel(edge_indices=edge_indices,
78 | output_size=output_size)
79 | sim = py_simulator(model)
80 | sim['edge_deltas'] = edge_deltas
81 | sim.run()
82 | print(sim['uhat_bc'])
83 | sim.check_partials(compact_print=True)
84 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/preprocessor/ffd_model.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import os
3 | import csdl
4 | from python_csdl_backend import Simulator as py_simulator
5 | from electric_motor_mdo.high_fidelity.geometry.motor_mesh_class import MotorMesh
6 | from electric_motor_mdo.high_fidelity.geometry.shape_parameter_model import ShapeParameterModel
7 | from electric_motor_mdo.high_fidelity.geometry.edge_update_model import EdgeUpdateModel
8 |
9 | class ShapeParameterUpdateModel(csdl.Model):
10 | def initialize(self):
11 | self.parameters.declare('unique_shape_parameter_list')
12 |
13 | def define(self):
14 | unique_shape_parameter_list = self.parameters['unique_shape_parameter_list']
15 | '''
16 | COMPUTATION OF MAP BETWEEN DESIGN VARIABLES AND SHAPE PARAMETERS
17 | LIST OF SHAPE PARAMETERS:
18 | - inner_stator_radius_sp
19 | - magnet_position_sp
20 | - magnet_width_sp
21 | - outer_stator_radius_sp
22 | - rotor_radius_sp
23 | - shaft_radius_sp
24 | - stator_tooth_shoe_thickness_sp
25 | - winding_top_radius_sp
26 | - winding_width_sp
27 | '''
28 |
29 | # THE IDEA HERE IS TO REGISTER ALL OF THE SHAPE PARAMETERS WITHIN
30 | # unique_shape_parameter_list AS OUTPUTS TO FEED INTO THE FFD MODELS
31 | # OR WE USE THE SHAPE PARAMETER AS DESIGN VARIABLES
32 | # shaft_radius_dv = self.create_input('shaft_radius_dv')
33 | # shaft_radius_sp = self.register_output(
34 | # 'shaft_radius_sp',
35 | # 1*shaft_radius_dv
36 | # )
37 |
38 | magnet_pos_delta_dv = self.declare_variable('magnet_pos_delta_dv', val=0.)
39 | magnet_position_sp = self.register_output(
40 | 'magnet_position_sp',
41 | -1.e-4*magnet_pos_delta_dv
42 | )
43 | self.print_var(magnet_pos_delta_dv)
44 | magnet_width_dv = self.declare_variable('magnet_width_dv', val=0.)
45 | magnet_width_sp = self.register_output(
46 | 'magnet_width_sp',
47 | 1.e-3*magnet_width_dv
48 | )
49 | self.print_var(magnet_width_dv)
50 | '''
51 | THE FINAL OUTPUTS HERE ARE THE SHAPE PARAMETERS THAT FEED INTO THE
52 | INDIVIDUAL MESH MODELS WITHIN INSTANCE MODELS
53 | '''
54 |
55 | class FFDModel(csdl.Model):
56 | def initialize(self):
57 | self.parameters.declare('parametrization_dict')
58 |
59 | def define(self):
60 |
61 | param_dict = self.parameters['parametrization_dict']
62 | unique_sp_list = sorted(set(param_dict['shape_parameter_list_input']))
63 |
64 | self.add(
65 | ShapeParameterUpdateModel(
66 | unique_shape_parameter_list=unique_sp_list
67 | ),
68 | 'shape_parameter_update_model'
69 | )
70 |
71 | self.add(
72 | ShapeParameterModel(
73 | shape_parameter_list_input=param_dict['shape_parameter_list_input'],
74 | shape_parameter_index_input=param_dict['shape_parameter_index_input'],
75 | shape_parametrization=param_dict['shape_parametrization'],
76 | ),
77 | 'shape_parameter_model'
78 | )
79 |
80 | self.add(
81 | EdgeUpdateModel(
82 | ffd_parametrization=param_dict['ffd_parametrization'][0],
83 | edge_parametrization=param_dict['edge_parametrization'][0],
84 | initial_edge_coords=param_dict['initial_edge_coordinates'][0],
85 | ),
86 | 'edge_update_model'
87 | )
88 |
89 | class MagnetShapeLimitModel(csdl.Model):
90 | def define(self):
91 |
92 | magnet_pos_delta_dv = self.declare_variable('magnet_pos_delta_dv')
93 | magnet_width_dv = self.declare_variable('magnet_width_dv')
94 |
95 | # some linear relationship between the design variables
96 | magnet_shape_limit = magnet_pos_delta_dv + 6*magnet_width_dv
97 |
98 | magnet_shape_limit = self.register_output(
99 | name='magnet_shape_limit',
100 | var=magnet_shape_limit
101 |
102 | )
103 |
104 | self.print_var(magnet_shape_limit)
105 |
--------------------------------------------------------------------------------
/examples/em_motor_opt/run_motor_opt.py:
--------------------------------------------------------------------------------
1 |
2 | from requests import post
3 | from femo.fea.fea_dolfinx import *
4 | from femo.csdl_opt.fea_model import FEAModel
5 | from femo.csdl_opt.state_model import StateModel
6 | from femo.csdl_opt.output_model import OutputModel
7 | import numpy as np
8 | import csdl
9 |
10 | from python_csdl_backend import Simulator
11 | from matplotlib import pyplot as plt
12 | import argparse
13 |
14 | import motor_pde as pde
15 | from postprocessor.power_loss_model import LossSumModel, PowerLossModel
16 | from preprocessor.ffd_model import FFDModel, MotorMesh, MagnetShapeLimitModel
17 | from preprocessor.boundary_input_model import BoundaryInputModel
18 |
19 | ###########################################################
20 | #################### Preprocessing ########################
21 | shift = 15
22 | mech_angles = np.arange(0,30+1,5)
23 | # rotor_rotations = np.pi/180*np.arange(0,30,5)
24 | rotor_rotations = mech_angles[:1]
25 | instances = len(rotor_rotations)
26 |
27 | coarse_test = True
28 |
29 | mm = MotorMesh(
30 | file_name='motor_data/motor_data_test/motor_mesh_1',
31 | popup=False,
32 | rotation_angles=rotor_rotations * np.pi/180,
33 | base_angle=shift*np.pi/180,
34 | )
35 |
36 | mm.baseline_geometry=True
37 | mm.create_motor_mesh()
38 | # dictionary holding parametrization parameters
39 | parametrization_dict = mm.ffd_param_dict
40 | unique_sp_list = sorted(set(parametrization_dict['shape_parameter_list_input']))
41 | # FFD MODEL
42 | ffd_connection_model = FFDModel(
43 | parametrization_dict=parametrization_dict
44 | )
45 |
46 |
47 | '''
48 | 1. Define the mesh
49 | '''
50 | # TODO: write the msh2xdmf convertor in DOLFINx
51 | mesh_name = "motor_mesh_1"
52 | data_path = "motor_data/motor_data_test/"
53 |
54 | mesh_file = data_path + mesh_name
55 | mesh, boundaries_mf, subdomains_mf, association_table = import_mesh(
56 | prefix=mesh_file,
57 | dim=2,
58 | subdomains=True
59 | )
60 |
61 | '''
62 | The boundary movement data
63 | '''
64 | init_edge_coords = parametrization_dict['initial_edge_coordinates'][0].copy()
65 |
66 | dx = Measure('dx', domain=mesh, subdomain_data=subdomains_mf)
67 | dS = Measure('dS', domain=mesh, subdomain_data=boundaries_mf)
68 | ds = Measure('ds', domain=mesh, subdomain_data=boundaries_mf)
69 | winding_id = [15,]
70 | magnet_id = [3,]
71 | steel_id = [1,2]
72 | winding_range = range(15,50+1)
73 |
74 | # Subdomains for calculating power losses
75 | ec_loss_subdomain = [1,2,] # rotor and stator core
76 | hysteresis_loss_subdomain = [1,2,]
77 | pm_loss_subdomain = range(3, 14+1)
78 |
79 |
80 |
81 | ###########################################################
82 | ######################### FEA #############################
83 | '''
84 | 2. Set up the PDE problem
85 | '''
86 | # PROBLEM SPECIFIC PARAMETERS
87 | Hc = 838.e3 # 838 kA/m
88 | p = 12
89 | s = 3 * p
90 | vacuum_perm = 4e-7 * np.pi
91 | angle = 0.
92 | iq = 282.2 / 0.00016231
93 | ##################### mesh motion subproblem ######################
94 | fea_mm = FEA(mesh)
95 |
96 | fea_mm.PDE_SOLVER = 'SNES'
97 | fea_mm.REPORT = True
98 | fea_mm.record = False
99 |
100 |
101 | # inputs for mesh motion subproblem
102 | input_name_mm = 'uhat_bc'
103 | input_function_space_mm = VectorFunctionSpace(mesh, ('CG', 1))
104 | input_function_mm = Function(input_function_space_mm)
105 | edge_indices = locateDOFs(init_edge_coords,input_function_space_mm,input="polar")
106 | boundary_input_model = BoundaryInputModel(edge_indices=edge_indices,
107 | output_size=len(input_function_mm.x.array))
108 | ############ User-defined incremental solver ###########
109 | def getDisplacementSteps(uhat, edge_deltas):
110 | """
111 | Divide the edge movements into steps based on the current mesh size
112 | """
113 |
114 | mesh = uhat.function_space.mesh
115 | STEPS = 2
116 | max_disp = np.max(np.abs(edge_deltas))
117 | h = meshSize(mesh)
118 | move(mesh, uhat)
119 | min_cell_size = h.min()
120 | moveBackward(mesh, uhat)
121 | min_STEPS = 4*round(max_disp/min_cell_size)
122 | if min_STEPS >= STEPS:
123 | STEPS = min_STEPS
124 | increment_deltas = edge_deltas/STEPS
125 | return STEPS, increment_deltas
126 |
127 | def advance(func_old,increment_deltas):
128 | func_old.vector[edge_indices.astype(np.int32)] += \
129 | increment_deltas[edge_indices.astype(np.int32)]
130 |
131 | def solveIncremental(res,func,bc,report=False):
132 | vec = np.copy(input_function_mm.vector.getArray())
133 | print('vec:',vec)
134 | print('vec norm:',np.linalg.norm(vec))
135 | print('func:',func.x.array)
136 | print('func norm:',np.linalg.norm(func.x.array))
137 | nnz_ind = np.nonzero(vec)[0]
138 | func_old = input_function_mm
139 | # Get the relative movements from the previous step
140 | relative_edge_deltas = np.copy(vec)
141 | relative_edge_deltas[edge_indices] -= func.vector[edge_indices.astype(np.int32)]
142 | STEPS, increment_deltas = getDisplacementSteps(func,
143 | relative_edge_deltas)
144 | snes_solver = SNESSolver(res, func, bc, report=report)
145 | func_old.vector[:] = func.vector
146 | # Incrementally set the BCs to increase to `edge_deltas`
147 | if report == True:
148 | print(80*"=")
149 | print(' FEA: total steps for mesh motion:', STEPS)
150 | print(80*"=")
151 | for i in range(STEPS):
152 | if report == True:
153 | print(80*"=")
154 | print(" FEA: Step "+str(i+1)+"/"+str(STEPS)+" of mesh movement")
155 | print(80*"=")
156 | advance(func_old,increment_deltas)
157 | snes_solver.solve(None, func.vector)
158 | input_function_mm.vector.setArray(vec)
159 | if report == True:
160 | print(80*"=")
161 | print(' FEA: L2 error of the mesh motion on the edges:',
162 | np.linalg.norm(func.vector[edge_indices.astype(np.int32)]
163 | - input_function_mm.vector[edge_indices.astype(np.int32)]))
164 | print(80*"=")
165 |
166 | fea_mm.custom_solve = solveIncremental
167 |
168 | # states for mesh motion subproblem
169 | state_name_mm = 'uhat'
170 | state_function_space_mm = VectorFunctionSpace(mesh, ('CG', 1))
171 | state_function_mm = Function(state_function_space_mm)
172 | state_function_mm.vector.set(0.0)
173 | v_mm = TestFunction(state_function_space_mm)
174 |
175 | # Add output to the PDE problem:
176 | output_name_mm_1 = 'winding_area'
177 | output_form_mm_1 = pde.area_form(state_function_mm, dx, winding_id)
178 | output_name_mm_2 = 'magnet_area'
179 | output_form_mm_2 = pde.area_form(state_function_mm, dx, magnet_id)
180 | output_name_mm_3 = 'steel_area'
181 | output_form_mm_3 = pde.area_form(state_function_mm, dx, steel_id)
182 |
183 |
184 | ############ Weakly enforced boundary conditions #############
185 | residual_form_mm = pde.pdeResMM(state_function_mm, v_mm, g=input_function_mm,
186 | nitsche=True, sym=True, overpenalty=False,
187 | dS_=dS(1000),ds_=ds(1000))
188 | fea_mm.add_input(name=input_name_mm,
189 | function=input_function_mm)
190 | fea_mm.add_state(name=state_name_mm,
191 | function=state_function_mm,
192 | residual_form=residual_form_mm,
193 | arguments=[input_name_mm])
194 |
195 |
196 | fea_mm.add_output(name=output_name_mm_1,
197 | type='scalar',
198 | form=output_form_mm_1,
199 | arguments=[state_name_mm])
200 | fea_mm.add_output(name=output_name_mm_2,
201 | type='scalar',
202 | form=output_form_mm_2,
203 | arguments=[state_name_mm])
204 | fea_mm.add_output(name=output_name_mm_3,
205 | type='scalar',
206 | form=output_form_mm_3,
207 | arguments=[state_name_mm])
208 |
209 |
210 | #############################################################
211 | ################### electomagnetic subproblem ###############
212 | fea_em = FEA(mesh)
213 |
214 | fea_em.PDE_SOLVER = 'SNES'
215 | fea_em.REPORT = True
216 | fea_em.record = True
217 |
218 | # Add input to the PDE problem: the inputs as the previous states
219 |
220 | # Add state to the PDE problem:
221 | # states for electromagnetic equation: magnetic potential vector
222 | state_name_em = 'A_z'
223 | state_function_space_em = FunctionSpace(mesh, ('CG', 1))
224 | state_function_em = Function(state_function_space_em)
225 | v_em = TestFunction(state_function_space_em)
226 |
227 |
228 |
229 | ########################### Incremental solve ###########################
230 | ############### much slower, but more accurate ##########################
231 | def solveIncrementalEM(res,func,bc,report=False):
232 | STEPS = 5
233 | # Incrementally set the BCs to increase to `edge_deltas`
234 | if report == True:
235 | print(80*"=")
236 | print(' FEA: total steps for electromagnetic solve:', STEPS)
237 | print(80*"=")
238 | JS_scaler = 1./STEPS
239 | res += pde.JS(v_em,state_function_mm,iq,p,s,Hc,angle)
240 | for i in range(STEPS):
241 | if report == True:
242 | print(80*"=")
243 | print(" FEA: Step "+str(i+1)+"/"+str(STEPS)+" of electromagnetic solve")
244 | print(80*"=")
245 | res -= JS_scaler*pde.JS(v_em,state_function_mm,iq,p,s,Hc,angle)
246 | # print(np.linalg.norm(getFuncArray(func)))
247 | snes_solver = SNESSolver(res, func, bc, report=report)
248 | snes_solver.solve(None, func.vector)
249 |
250 | fea_em.custom_solve = solveIncrementalEM
251 |
252 | #########################################################################
253 |
254 | ############ Strongly enforced boundary conditions #############
255 | # ubc_em = Function(state_function_space_em)
256 | # ubc_em.vector.set(0.0)
257 | # locate_BC1_em = locate_dofs_geometrical(
258 | # (state_function_space_em, state_function_space_em),
259 | # lambda x: np.isclose(x[0]**2+x[1]**2, 0.0144 ,atol=1e-6))
260 | # locate_BC2_em = locate_dofs_geometrical(
261 | # (state_function_space_em, state_function_space_em),
262 | # lambda x: np.isclose(x[0]**2+x[1]**2, 0.0036 ,atol=1e-6))
263 | #
264 | # locate_BC_list_em = [locate_BC1_em, locate_BC2_em,]
265 |
266 | # fea_em.add_strong_bc(ubc_em, locate_BC_list_em, state_function_space_em)
267 | #
268 | # residual_form_em = pde.pdeResEM(state_function_em,v_em,state_function_mm,
269 | # iq,dx,p,s,Hc,vacuum_perm,angle)
270 | #
271 |
272 |
273 | ############ Weakly enforced boundary conditions #############
274 | ubc_em = Function(state_function_space_em)
275 | ubc_em.vector.set(0.0)
276 | residual_form_em = pde.pdeResEM(state_function_em,v_em,state_function_mm,
277 | iq,dx,p,s,Hc,vacuum_perm,angle,
278 | g=ubc_em,nitsche=True, sym=True, overpenalty=False,ds_=ds)
279 |
280 | # Add output to the PDE problem:
281 | output_name_1 = 'B_influence_eddy_current'
282 | exponent_1 = 2
283 | subdomains_1 = [1,2]
284 | output_form_1 = pde.B_power_form(state_function_em, state_function_mm,
285 | exponent_1, dx, subdomains_1)
286 |
287 | output_name_2 = 'B_influence_hysteresis'
288 | exponent_2 = 1.76835 # Material parameter for Hiperco 50
289 | subdomains_2 = [1,2]
290 | output_form_2 = pde.B_power_form(state_function_em, state_function_mm,
291 | exponent_2, dx, subdomains_2)
292 |
293 |
294 | '''
295 | 3. Define the boundary conditions
296 | '''
297 |
298 |
299 | fea_em.add_input(name=state_name_mm,
300 | function=state_function_mm)
301 | fea_em.add_state(name=state_name_em,
302 | function=state_function_em,
303 | residual_form=residual_form_em,
304 | arguments=[state_name_mm])
305 | fea_em.add_output(name=output_name_1,
306 | type='scalar',
307 | form=output_form_1,
308 | arguments=[state_name_em,state_name_mm])
309 | fea_em.add_output(name=output_name_2,
310 | type='scalar',
311 | form=output_form_2,
312 | arguments=[state_name_em,state_name_mm])
313 |
314 |
315 |
316 | '''
317 | 4. Set up the CSDL model
318 | '''
319 | fea_model = FEAModel(fea=[fea_mm,fea_em])
320 | ###########################################################
321 | #################### Postprocessing #######################
322 | # Case-to-case postprocessor model
323 | model = csdl.Model()
324 | power_loss_model = PowerLossModel()
325 | loss_sum_model = LossSumModel()
326 |
327 | ###########################################################
328 | ######################## Connect ##########################
329 |
330 | # python_csdl_backend
331 |
332 | model.add(boundary_input_model, name='boundary_input_model')
333 | model.add(ffd_connection_model, name='ffd_model')
334 | model.add(fea_model, name='fea_model')
335 | model.add(power_loss_model, name='power_loss_model')
336 | model.add(loss_sum_model, name='loss_sum_model')
337 |
338 | # Upper limit of 'magnet_pos_delta_dv' > 60.
339 | model.create_input('magnet_pos_delta_dv', val=0.)
340 | model.create_input('magnet_width_dv', val=0.)
341 | model.create_input('motor_length', val=0.1)
342 | model.create_input('frequency', val=300)
343 | model.create_input('hysteresis_coeff', val=55.)
344 | model.add_design_variable('magnet_pos_delta_dv', lower=-1e-5, upper=50.)
345 | # model.add_design_variable('magnet_width_dv', lower=-15, upper=24.)
346 | # model.add_constraint('magnet_shape_limit', upper=38.)
347 | model.add_objective('loss_sum')
348 |
349 | sim = Simulator(model, analytics=True)
350 | ########### Test the forward solve ##############
351 |
352 | sim.run()
353 |
354 | # print('uhat_bc:',sim['uhat_bc'])
355 | # print('uhat:',sim['uhat'])
356 | # print('magnet_pos_delta_dv',sim['magnet_pos_delta_dv'])
357 | # print('magnet_width_dv',sim['magnet_width_dv'])
358 | # print('edge_deltas',sim['edge_deltas'])
359 | # exit()
360 | # sim.check_totals(of=['loss_sum'], wrt=['magnet_pos_delta_dv'],compact_print=True)
361 |
362 | # [RU]: It seems like CSDL doesn't work with csdl_om anymore
363 | # sim.executable.check_totals(of=['loss_sum'], wrt=['magnet_pos_delta_dv'],compact_print=True)
364 |
365 | ############# Run the optimization with modOpt #############
366 | from modopt import CSDLProblem
367 |
368 | prob = CSDLProblem(
369 | problem_name='em_motor_opt',
370 | simulator=sim,
371 | )
372 |
373 | from modopt import SNOPT
374 |
375 | optimizer = SNOPT(prob,
376 | Major_iterations = 100,
377 | Major_optimality =1e-8,
378 | Major_feasibility=1e-6,
379 | append2file=True)
380 | # append2file=False)
381 |
382 |
383 | # from electric_motor_mdo.optimization.HF.baseline.motor_dash import MotorDashboard
384 | # dashboard = MotorDashboard(instances=1)
385 | # sim.add_recorder(dashboard.get_recorder())
386 |
387 | # Solve your optimization problem
388 | optimizer.solve()
389 | # print("="*40)
390 |
391 | fea_mm.inputs_dict[input_name_mm]['function'].vector.setArray(sim['uhat_bc'])
392 | with XDMFFile(MPI.COMM_WORLD, "solutions/input_"+input_name_mm+".xdmf", "w") as xdmf:
393 | xdmf.write_mesh(fea_mm.mesh)
394 | fea_mm.inputs_dict[input_name_mm]['function'].name = input_name_mm
395 | xdmf.write_function(fea_mm.inputs_dict[input_name_mm]['function'])
396 | with XDMFFile(MPI.COMM_WORLD, "solutions/state_"+state_name_mm+".xdmf", "w") as xdmf:
397 | xdmf.write_mesh(fea_mm.mesh)
398 | fea_mm.states_dict[state_name_mm]['function'].name = state_name_mm
399 | xdmf.write_function(fea_mm.states_dict[state_name_mm]['function'])
400 |
401 | magnetic_flux_density = pde.B(state_function_em, state_function_mm)
402 | move(fea_em.mesh, state_function_mm)
403 | with XDMFFile(MPI.COMM_WORLD, "solutions/state_"+state_name_em+".xdmf", "w") as xdmf:
404 | xdmf.write_mesh(fea_em.mesh)
405 | fea_em.states_dict[state_name_em]['function'].name = state_name_em
406 | xdmf.write_function(fea_em.states_dict[state_name_em]['function'])
407 |
408 | with XDMFFile(MPI.COMM_WORLD, "solutions/magnetic_flux_density.xdmf", "w") as xdmf:
409 | xdmf.write_mesh(fea_em.mesh)
410 | magnetic_flux_density.name = "B"
411 | xdmf.write_function(magnetic_flux_density)
412 |
--------------------------------------------------------------------------------
/examples/nonlinear_poisson_opt/run_nonlinear_poisson_opt.py:
--------------------------------------------------------------------------------
1 |
2 | from femo.fea.fea_dolfinx import *
3 | from femo.csdl_opt.fea_model import FEAModel
4 | from femo.csdl_opt.state_model import StateModel
5 | from femo.csdl_opt.output_model import OutputModel
6 | import numpy as np
7 | import csdl
8 | from python_csdl_backend import Simulator
9 | from matplotlib import pyplot as plt
10 | import argparse
11 |
12 | parser = argparse.ArgumentParser()
13 | parser.add_argument('--nel',dest='nel',default='16',
14 | help='Number of elements')
15 |
16 | parser.add_argument('--refine',dest='refine',default='1',
17 | help='Level of mesh refinement')
18 |
19 | args = parser.parse_args()
20 |
21 | num_el = int(args.nel)
22 | refine = int(args.refine)
23 | '''
24 | 1. Define the mesh
25 | '''
26 | # import gmsh
27 | # gmsh.initialize()
28 | #
29 | # model = gmsh.model()
30 | # model.add("Circle")
31 | # model.setCurrent("Circle")
32 | # R = 1.
33 | # xc = 0.
34 | # yc = 0.
35 | # disk = model.occ.addDisk(0, 0, 0, R, R)
36 | # model.occ.synchronize()
37 | # model.add_physical_group(2, [disk])
38 | # # mesh refinement factor
39 | # # mu = 2
40 | # # gmsh.option.setNumber("Mesh.CharacteristicLengthFromPoints", mu)
41 | #
42 | # gdim = 2
43 | # model.mesh.generate(gdim)
44 | # for i in range(refine):
45 | # model.mesh.refine()
46 | # # gmsh.write("Circle.msh")
47 |
48 |
49 | #
50 | # from dolfinx.io.gmshio import model_to_mesh
51 | # from mpi4py import MPI
52 | # model_rank = 0
53 | # mesh, cell_tags, facet_tags = model_to_mesh(model, MPI.COMM_SELF, model_rank, gdim=2)
54 | # mesh.name = "Circle"
55 | # gmsh.finalize()
56 | # # with XDMFFile(mesh.comm, f"circle_1.xdmf", "w") as file:
57 | # # file.write_mesh(mesh)
58 | # # mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
59 | #
60 | #
61 |
62 | mesh = createUnitSquareMesh(num_el)
63 | # mesh = createIntervalMesh(num_el, -1., 1.)
64 | # mesh = createRectangleMesh(np.array([-1.0,-1.0]),
65 | # np.array([1., 1.]),
66 | # num_el,
67 | # num_el)
68 | # mesh = dolfinx.mesh.create_mesh(num_el, domain=circle())
69 | # with dolfinx.io.XDMFFile(MPI.COMM_WORLD, "circle_1.xdmf", "r") as xdmf:
70 | # mesh = xdmf.read_mesh(name="Circle")
71 | '''
72 | 2. Set up the PDE problem
73 | '''
74 |
75 |
76 | PI = np.pi
77 | # ALPHA_1 = 2E-3
78 | # ALPHA_2 = 3E-2
79 | ALPHA_1 = 6E-7
80 | ALPHA_2 = 2E-6
81 |
82 | def P(u):
83 | return grad(u)
84 |
85 | def g(u):
86 | return u**3
87 |
88 | def interiorResidual(u,v,f):
89 | mesh = u.function_space.mesh
90 | n = FacetNormal(mesh)
91 | h_E = CellDiameter(mesh)
92 | x = SpatialCoordinate(mesh)
93 | return inner(P(u), grad(v))*dx \
94 | + inner(u**3,v)*dx \
95 | - inner(f, v)*dx
96 |
97 | def boundaryResidual(u,v,u_exact,
98 | sym=False,
99 | beta_value=1e1,
100 | overPenalize=False):
101 | mesh = u.function_space.mesh
102 | n = FacetNormal(mesh)
103 | h_E = CellDiameter(mesh)
104 | x = SpatialCoordinate(mesh)
105 | beta = Constant(mesh, beta_value)
106 | sgn = 1.0
107 | if (sym is not True):
108 | sgn = -1.0
109 | nitsche_1 = - inner(dot(P(u), n), v)*ds
110 | dP = derivative(P(u), u, v)
111 | nitsche_2 = sgn*inner(u_exact-u, dot(dP, n))*ds
112 | retval = nitsche_1 + nitsche_2
113 | penalty = beta*h_E**(-1)*inner(u-u_exact, v)*ds
114 | if (overPenalize or sym):
115 | retval += penalty
116 | return retval
117 |
118 | def pdeRes(u,v,f,u_exact=None,weak_bc=False,sym=False,overPenalize=False):
119 | """
120 | The variational form of the PDE residual for the Poisson's problem
121 | """
122 | retval = interiorResidual(u,v,f)
123 | if (weak_bc):
124 | retval += boundaryResidual(u,v,u_exact,sym=sym,overPenalize=overPenalize)
125 | return retval
126 |
127 | # H1 regularization
128 | # def outputForm(u, f, u_exact):
129 | # return 0.5*inner(u-u_exact, u-u_exact)*dx + \
130 | # ALPHA/2*(f**2+inner(grad(f),grad(f)))*dx
131 | # L1 regularization
132 | # def outputForm(u, f, u_exact):
133 | # return 0.5*inner(u-u_exact, u-u_exact)*dx + \
134 | # ALPHA_2*abs(f)*dx
135 | # # L2+L1 regularization
136 | # def outputForm(u, f, u_exact):
137 | # return 0.5*inner(u-u_exact, u-u_exact)*dx + \
138 | # ALPHA_1/2*f**2*dx + ALPHA_2*abs(f)*dx
139 | # L2 regularization
140 | def outputForm(u, f, u_exact):
141 | return 0.5*inner(u-u_exact, u-u_exact)*dx + \
142 | ALPHA_1/2*f**2*dx
143 |
144 | x = ufl.SpatialCoordinate(mesh)
145 | u_ex_ufl = ufl.sin(2*ufl.pi*x[0])*ufl.sin(ufl.pi*x[1])
146 | # f_ex_ufl = 5.*ufl.pi**2*ufl.sin(2*ufl.pi*x[0])*ufl.sin(ufl.pi*x[1]) + \
147 | # (ufl.sin(2*ufl.pi*x[0])**3)*(ufl.sin(ufl.pi*x[1])**3)
148 | fea = FEA(mesh)
149 | # Record the function evaluations during optimization process
150 | fea.record = True
151 | # Add input to the PDE problem:
152 | input_name = 'f'
153 | input_function_space = FunctionSpace(mesh, ('DG', 0))
154 | # input_function_space = FunctionSpace(mesh, ('CG', 1))
155 | input_function = Function(input_function_space)
156 | # Add state to the PDE problem:
157 | state_name = 'u'
158 | state_function_space = FunctionSpace(mesh, ('CG', 1))
159 | state_function = Function(state_function_space)
160 | v = TestFunction(state_function_space)
161 |
162 | residual_form = pdeRes(state_function, v, input_function)
163 | # u_ex = fea.add_exact_solution(Expression_u, state_function_space)
164 | # f_ex = fea.add_exact_solution(Expression_f, input_function_space)
165 | u_ex = Function(state_function_space)
166 | project(u_ex_ufl,u_ex)
167 | f_ex_ufl = -div(P(u_ex_ufl))+(u_ex_ufl)**3
168 | f_ex = Function(input_function_space)
169 | project(f_ex_ufl,f_ex)
170 | # Add output to the PDE problem:
171 | output_name = 'l2_functional'
172 | output_form = outputForm(state_function, input_function, u_ex_ufl)
173 |
174 |
175 |
176 | '''
177 | 3. Define the boundary conditions
178 | '''
179 |
180 | # ########### Strongly enforced boundary conditions #############
181 | # locate_BC1 = locate_dofs_geometrical((state_function_space, state_function_space),
182 | # lambda x: np.isclose(x[0], 0. ,atol=1e-8))
183 | # locate_BC2 = locate_dofs_geometrical((state_function_space, state_function_space),
184 | # lambda x: np.isclose(x[0], 1. ,atol=1e-8))
185 | # locate_BC3 = locate_dofs_geometrical((state_function_space, state_function_space),
186 | # lambda x: np.isclose(x[1], 0. ,atol=1e-8))
187 | # locate_BC4 = locate_dofs_geometrical((state_function_space, state_function_space),
188 | # lambda x: np.isclose(x[1], 1. ,atol=1e-8))
189 | # locate_BC_list = [locate_BC1, locate_BC2, locate_BC3, locate_BC4]
190 | #
191 | # fea.add_strong_bc(u_ex, locate_BC_list, state_function_space)
192 | # residual_form = pdeRes(state_function, v, input_function)
193 |
194 | ########### Weakly enforced boundary conditions #############
195 | ############## Unsymmetric Nitsche's method #################
196 | residual_form = pdeRes(state_function, v, input_function,
197 | u_exact=u_ex_ufl, weak_bc=True, sym=True)
198 | #############################################################
199 |
200 |
201 |
202 | fea.add_input(input_name, input_function)
203 | fea.add_state(name=state_name,
204 | function=state_function,
205 | residual_form=residual_form,
206 | arguments=[input_name])
207 | fea.add_output(name=output_name,
208 | type='scalar',
209 | form=output_form,
210 | arguments=[input_name,state_name])
211 |
212 |
213 |
214 | '''
215 | 4. Set up the CSDL model
216 | '''
217 |
218 |
219 | # fea.PDE_SOLVER = 'Newton'
220 | fea.PDE_SOLVER = 'SNES'
221 | # fea.REPORT = True
222 | x = SpatialCoordinate(mesh)
223 | f_0_ufl = x[0]+x[1]
224 | f_0 = lambda x: eval(str(f_0_ufl))
225 | f_0_func = Function(input_function_space)
226 | f_0_func.interpolate(f_0)
227 | # f_0_func.vector[:] = 0.01*f_ex.vector
228 |
229 | fea_model = FEAModel(fea=[fea])
230 | fea_model.create_input("{}".format(input_name),
231 | shape=fea.inputs_dict[input_name]['shape'],
232 | val=0.1)
233 | # val=25.*np.ones(fea.inputs_dict[input_name]['shape']))
234 | # val=0.1)
235 | # val=getFuncArray(f_0_func))
236 | # val=10*np.ones(fea.inputs_dict[input_name]['shape']) * 0.86)
237 |
238 | # fea_model.connect('f','u_state_model.f')
239 | # fea_model.connect('f','l2_functional_output_model.f')
240 | # fea_model.connect('u_state_model.u','l2_functional_output_model.u')
241 |
242 | # fea_model.add_design_variable(input_name, lower=-12., upper=12.)
243 | fea_model.add_design_variable(input_name)
244 | fea_model.add_objective(output_name)
245 |
246 | # Ru: the new Python backend of CSDL has issue for promotions or connecting
247 | # the variables for custom operations as from Aug 30.
248 | sim = Simulator(fea_model)
249 | # sim = om_simulator(fea_model)
250 | ########### Test the forward solve ##############
251 | # sim[input_name] = getFuncArray(f_ex)
252 |
253 | sim.run()
254 | # print("objective value:", sim[output_name])
255 | ########### Generate the N2 diagram #############
256 | # sim.visualize_implementation()
257 |
258 | ############# Check the derivatives #############
259 | # sim.check_totals()
260 | # sim.check_partials(compact_print=True)
261 | # sim.executable.check_totals(of='l2_functional', wrt='f',compact_print=True)
262 | '''
263 | 5. Set up the optimization problem
264 | '''
265 | ############# Run the optimization with modOpt #############
266 | from modopt import CSDLProblem
267 |
268 | prob = CSDLProblem(
269 | problem_name='nonlinear_poisson_opt',
270 | simulator=sim,
271 | )
272 |
273 | from modopt import SNOPT, SLSQP
274 | # optimizer = SNOPT(prob,
275 | # Major_iterations = 1000,
276 | # Major_optimality = 1e-9,
277 | # append2file=False)
278 | optimizer = SLSQP(prob, maxiter=1000, ftol=1e-10)
279 |
280 | # # Check first derivatives at the initial guess, if needed
281 | # optimizer.check_first_derivatives(prob.x0)
282 |
283 | # Solve your optimization problem
284 | optimizer.solve()
285 | print("="*40)
286 | # optimizer.print_results()
287 |
288 | print("Objective value: ", sim['l2_functional_output_model.'+output_name])
289 | print("="*40)
290 | control_error = errorNorm(f_ex_ufl, input_function)
291 | print("Error in controls:", control_error)
292 | state_error = errorNorm(u_ex_ufl, state_function)
293 | print("Error in states:", state_error)
294 | print("="*40)
295 |
296 |
297 |
298 | with XDMFFile(MPI.COMM_WORLD, "solutions/state_"+state_name+".xdmf", "w") as xdmf:
299 | xdmf.write_mesh(fea.mesh)
300 | fea.states_dict[state_name]['function'].name = state_name
301 | xdmf.write_function(fea.states_dict[state_name]['function'])
302 | with XDMFFile(MPI.COMM_WORLD, "solutions/input_"+input_name+".xdmf", "w") as xdmf:
303 | xdmf.write_mesh(fea.mesh)
304 | fea.inputs_dict[input_name]['function'].name = input_name
305 | xdmf.write_function(fea.inputs_dict[input_name]['function'])
306 | with XDMFFile(MPI.COMM_WORLD, "solutions/f_ex.xdmf", "w") as xdmf:
307 | xdmf.write_mesh(fea.mesh)
308 | xdmf.write_function(f_ex)
309 | with XDMFFile(MPI.COMM_WORLD, "solutions/u_ex.xdmf", "w") as xdmf:
310 | xdmf.write_mesh(fea.mesh)
311 | xdmf.write_function(u_ex)
312 |
--------------------------------------------------------------------------------
/examples/ongoing/shape_opt/run_shape_opt_roof.py:
--------------------------------------------------------------------------------
1 | """
2 | Structural analysis of the classic shell obstacle course:
3 | 1/3: Scordelis-Lo Roof
4 | """
5 | from femo.fea.fea_dolfinx import *
6 | from femo.csdl_opt.fea_model import FEAModel
7 | from femo.csdl_opt.state_model import StateModel
8 | from femo.csdl_opt.output_model import OutputModel
9 | from femo.csdl_opt.pre_processor.general_filter_model \
10 | import GeneralFilterModel
11 | import numpy as np
12 | import csdl
13 | from csdl import Model
14 | from csdl_om import Simulator
15 | from matplotlib import pyplot as plt
16 | import argparse
17 | from mpi4py import MPI
18 | from shell_analysis_fenicsX import *
19 |
20 | # The shell problem setup
21 |
22 | roof = [#### tri mesh ####
23 | "roof_tri_30_20.xdmf",
24 | "roof_tri_60_40.xdmf",
25 | "roof5_25882.xdmf",
26 | "roof6_104524.xdmf",
27 | "roof12_106836.xdmf",
28 | #### quad mesh ####
29 | "roof_quad_3_2.xdmf",
30 | "roof_quad_6_4.xdmf",
31 | "roof_quad_12_8.xdmf",
32 | "roof_quad_24_16.xdmf",
33 | "roof_quad_30_20.xdmf",
34 | "roof_quad_60_40.xdmf",
35 | "roof_quad_120_80.xdmf",
36 | "roof_quad_240_160.xdmf",
37 | "roof_quad_360_240.xdmf"]
38 |
39 | filename = "../shell_analysis_fenics/mesh/mesh-examples/scordelis-lo-roof/"+roof[0]
40 | with dolfinx.io.XDMFFile(MPI.COMM_WORLD, filename, "r") as xdmf:
41 | mesh = xdmf.read_mesh(name="Grid")
42 |
43 | nel = mesh.topology.index_map(mesh.topology.dim).size_local
44 | nn = mesh.topology.index_map(0).size_local
45 |
46 | E_val = 4.32e8
47 | nu_val = 0.0
48 | h_val = 0.25
49 | f_d = -90.
50 |
51 | E = Constant(mesh,E_val) # Young's modulus
52 | nu = Constant(mesh,nu_val) # Poisson ratio
53 | # h = Constant(mesh,h_val) # Shell thickness
54 |
55 | f = Constant(mesh, (0,0,f_d)) # Body force per unit area
56 |
57 | element_type = "CG2CG1" # with quad/tri elements
58 | #element_type = "CG2CR1" # with tri elements
59 |
60 | element = ShellElement(
61 | mesh,
62 | element_type,
63 | # inplane_deg=3,
64 | # shear_deg=3
65 | )
66 | dx_inplane, dx_shear = element.dx_inplane, element.dx_shear
67 |
68 |
69 | def pdeRes(h,w,E,f,CLT,dx_inplane,dx_shear):
70 | elastic_model = ElasticModel(mesh,w,CLT)
71 | elastic_energy = elastic_model.elasticEnergy(E, h, dx_inplane,dx_shear)
72 | return elastic_model.weakFormResidual(elastic_energy, f)
73 |
74 |
75 | def compliance(u_mid,h):
76 | h_mesh = CellDiameter(mesh)
77 | alpha = 1e-1
78 | dX = ufl.Measure('dx', domain=mesh, metadata={"quadrature_degree":0})
79 | return 0.5*dot(u_mid,u_mid)*dX \
80 | + 0.5*alpha*dot(grad(h), grad(h))*(h_mesh**2)*dX
81 |
82 | def volume(h):
83 | return h*dx
84 |
85 | #######################################################
86 | ############## The optimization problem ###############
87 | #######################################################
88 | fea = FEA(mesh)
89 | # Add input to the PDE problem:
90 | input_name = 'thickness'
91 | input_function_space = FunctionSpace(mesh, ("DG", 0))
92 | input_function = Function(input_function_space)
93 |
94 | # Add state to the PDE problem:
95 | state_name = 'displacements'
96 | state_function_space = element.W
97 | state_function = Function(state_function_space)
98 | material_model = MaterialModel(E=E,nu=nu,h=input_function) # Simple isotropic material
99 | residual_form = pdeRes(input_function,state_function,
100 | E,f,material_model.CLT,dx_inplane,dx_shear)
101 |
102 | # Add output to the PDE problem:
103 | output_name_1 = 'compliance'
104 | output_form_1 = compliance(state_function.sub(0), input_function)
105 | output_name_2 = 'volume'
106 | output_form_2 = volume(input_function)
107 |
108 |
109 | fea.add_input(input_name, input_function)
110 | fea.add_state(name=state_name,
111 | function=state_function,
112 | residual_form=residual_form,
113 | arguments=[input_name])
114 | fea.add_output(name=output_name_1,
115 | type='scalar',
116 | form=output_form_1,
117 | arguments=[state_name,input_name])
118 | fea.add_output(name=output_name_2,
119 | type='scalar',
120 | form=output_form_2,
121 | arguments=[input_name])
122 | ############ Set the BCs for the Scordelis-Lo roof problem ###################
123 | ubc = Function(state_function_space)
124 | ubc.vector.set(0.0)
125 |
126 | locate_BC1 = locate_dofs_geometrical((state_function_space.sub(0).sub(1), state_function_space.sub(0).sub(1).collapse()[0]),
127 | lambda x: np.isclose(x[0], 25. ,atol=1e-6))
128 | locate_BC2 = locate_dofs_geometrical((state_function_space.sub(0).sub(2), state_function_space.sub(0).sub(2).collapse()[0]),
129 | lambda x: np.isclose(x[0], 25. ,atol=1e-6))
130 | locate_BC3 = locate_dofs_geometrical((state_function_space.sub(0).sub(1), state_function_space.sub(0).sub(1).collapse()[0]),
131 | lambda x: np.isclose(x[1], 0. ,atol=1e-6))
132 | locate_BC4 = locate_dofs_geometrical((state_function_space.sub(1).sub(0), state_function_space.sub(1).sub(0).collapse()[0]),
133 | lambda x: np.isclose(x[1], 0. ,atol=1e-6))
134 | locate_BC5 = locate_dofs_geometrical((state_function_space.sub(1).sub(2), state_function_space.sub(1).sub(2).collapse()[0]),
135 | lambda x: np.isclose(x[1], 0. ,atol=1e-6))
136 | locate_BC6 = locate_dofs_geometrical((state_function_space.sub(0).sub(0), state_function_space.sub(0).sub(0).collapse()[0]),
137 | lambda x: np.isclose(x[0], 0. ,atol=1e-6))
138 | locate_BC7 = locate_dofs_geometrical((state_function_space.sub(1).sub(1), state_function_space.sub(1).sub(1).collapse()[0]),
139 | lambda x: np.isclose(x[0], 0. ,atol=1e-6))
140 | locate_BC8 = locate_dofs_geometrical((state_function_space.sub(1).sub(2), state_function_space.sub(1).sub(2).collapse()[0]),
141 | lambda x: np.isclose(x[0], 0. ,atol=1e-6))
142 |
143 | bcs = [dirichletbc(ubc, locate_BC1, state_function_space.sub(0).sub(1)),
144 | dirichletbc(ubc, locate_BC2, state_function_space.sub(0).sub(2)),
145 | dirichletbc(ubc, locate_BC3, state_function_space.sub(0).sub(1)),
146 | dirichletbc(ubc, locate_BC4, state_function_space.sub(1).sub(0)),
147 | dirichletbc(ubc, locate_BC5, state_function_space.sub(1).sub(2)),
148 | dirichletbc(ubc, locate_BC6, state_function_space.sub(0).sub(0)),
149 | dirichletbc(ubc, locate_BC7, state_function_space.sub(1).sub(1)),
150 | dirichletbc(ubc, locate_BC8, state_function_space.sub(1).sub(2))
151 | ]
152 |
153 | ############ Strongly enforced boundary conditions #############
154 | # fea.add_strong_bc(ubc, locate_BC_list, state_function_space)
155 | for i in range(len(bcs)):
156 | fea.bc.append(bcs[i])
157 | ########## Solve with Newton solver wrapper: ##########
158 | # solveNonlinear(F, w, bcs)
159 |
160 |
161 | '''
162 | 4. Set up the CSDL model
163 | '''
164 |
165 | fea_model = FEAModel(fea=fea)
166 | np.random.seed(0)
167 | fea_model.create_input("{}".format('thickness'),
168 | shape=nel,
169 | val=np.ones(nel) * 0.25)
170 |
171 | fea_model.add_design_variable('thickness', upper=1.0, lower=1e-4)
172 | fea_model.add_objective('compliance')
173 | fea_model.add_constraint('volume', equals=200.)
174 | sim = Simulator(fea_model)
175 |
176 | ########### Test the forward solve ##############
177 |
178 | sim.run()
179 |
180 | ########### Generate the N2 diagram #############
181 | #sim.visualize_implementation()
182 |
183 |
184 | ############ Check the derivatives #############
185 | sim.check_partials(compact_print=True)
186 | # sim.prob.check_totals(compact_print=True)
187 |
188 | '''
189 | 5. Set up the optimization problem
190 | '''
191 | ############# Run the optimization with pyOptSparse #############
192 | import openmdao.api as om
193 | ###### Driver = SNOPT #########
194 | driver = om.pyOptSparseDriver()
195 | driver.options['optimizer']='SNOPT'
196 | driver.opt_settings['Verify level'] = 0
197 |
198 | driver.opt_settings['Major iterations limit'] = 100000
199 | driver.opt_settings['Minor iterations limit'] = 100000
200 | driver.opt_settings['Iterations limit'] = 100000000
201 | driver.opt_settings['Major step limit'] = 2.0
202 |
203 | driver.opt_settings['Major feasibility tolerance'] = 1e-6
204 | driver.opt_settings['Major optimality tolerance'] = 1e-8
205 | driver.options['print_results'] = False
206 |
207 | sim.prob.driver = driver
208 | sim.prob.setup()
209 |
210 | # from timeit import default_timer
211 | # start = default_timer()
212 |
213 | # sim.prob.run_driver()
214 |
215 | # stop = default_timer()
216 | # print('Optimization runtime:', str(stop-start), 'seconds')
217 |
218 | print("Compliance value: ", sim['compliance'])
219 |
220 | ########## Output: ##############
221 |
222 | uZ = computeNodalDisp(state_function.sub(0))[2]
223 | # Comparing the results to the numerical solution
224 | print("Scordelis-Lo roof theory tip deflection: v_tip = -0.3024")
225 | print("Tip deflection:", min(uZ))
226 | print(" Number of elements = "+str(mesh.topology.index_map(mesh.topology.dim).size_local))
227 | print(" Number of vertices = "+str(mesh.topology.index_map(0).size_local))
228 |
229 | ########## Visualization: ##############
230 |
231 | u_mid, _ = state_function.split()
232 | with XDMFFile(MPI.COMM_WORLD, "solutions/u_mid.xdmf", "w") as xdmf:
233 | xdmf.write_mesh(mesh)
234 | xdmf.write_function(u_mid)
235 |
--------------------------------------------------------------------------------
/examples/ongoing/shape_opt/ufl_shape_derivatives.py:
--------------------------------------------------------------------------------
1 |
2 | #############################################
3 | #Code snippet to compute the shape derivatives
4 | #############################################
5 | from femo.fea.fea_dolfinx import *
6 | mesh_2D = createUnitSquareMesh(2)
7 | X = SpatialCoordinate(mesh_2D)
8 |
9 | VX = VectorFunctionSpace(mesh_2D,("CG",1))
10 |
11 | VT = FunctionSpace(mesh_2D,("CG",1))
12 | T = Function(VT)
13 | T.x.array[:] = 1.
14 | vol = T*dx
15 | args = vol.arguments()
16 | # UFL arguments need unique indices within a form
17 | n = max(a.number() for a in args) if args else -1
18 | du = ufl.Argument(VX, n+1)
19 | dLdX = ufl.derivative(vol, X, du)
20 | print("Derivatives of volume w.r.t. spatial coordinates")
21 | print(assembleVector(dLdX).reshape(-1,2))
22 | fdim = 0
23 | num_facets_owned_by_proc = mesh_2D.topology.index_map(fdim).size_local
24 | geometry_entities = dolfinx.cpp.mesh.entities_to_geometry(mesh_2D, fdim, np.arange(num_facets_owned_by_proc, dtype=np.int32), False)
25 | points = mesh_2D.geometry.x
26 | print('Node id, Coords')
27 | for e, entity in enumerate(geometry_entities):
28 | print(e, points[entity])
29 | #############################################
--------------------------------------------------------------------------------
/examples/poisson_opt/run_poisson_opt.py:
--------------------------------------------------------------------------------
1 |
2 | from femo.fea.fea_dolfinx import *
3 | from femo.csdl_opt.fea_model import FEAModel
4 | from femo.csdl_opt.state_model import StateModel
5 | from femo.csdl_opt.output_model import OutputModel
6 | import numpy as np
7 | import csdl
8 | from python_csdl_backend import Simulator
9 | from matplotlib import pyplot as plt
10 | import argparse
11 |
12 | '''
13 | 1. Define the mesh
14 | '''
15 |
16 | parser = argparse.ArgumentParser()
17 | parser.add_argument('--nel',dest='nel',default='16',
18 | help='Number of elements')
19 |
20 | args = parser.parse_args()
21 | num_el = int(args.nel)
22 | mesh = createUnitSquareMesh(num_el)
23 |
24 | '''
25 | 2. Set up the PDE problem
26 | '''
27 |
28 |
29 | PI = np.pi
30 | ALPHA = 1E-6
31 |
32 | def interiorResidual(u,v,f):
33 | mesh = u.function_space.mesh
34 | n = FacetNormal(mesh)
35 | h_E = CellDiameter(mesh)
36 | x = SpatialCoordinate(mesh)
37 | return inner(grad(u), grad(v))*dx \
38 | - inner(f, v)*dx
39 |
40 | def boundaryResidual(u,v,u_exact,
41 | sym=False,
42 | beta_value=0.1,
43 | overPenalize=False):
44 |
45 | '''
46 | Formulation from Github:
47 | https://github.com/MiroK/fenics-nitsche/blob/master/poisson/
48 | poisson_circle_dirichlet.py
49 | '''
50 | mesh = u.function_space.mesh
51 | n = FacetNormal(mesh)
52 | h_E = CellDiameter(mesh)
53 | x = SpatialCoordinate(mesh)
54 | beta = Constant(mesh, beta_value)
55 | sgn = 1.0
56 | if (sym is not True):
57 | sgn = -1.0
58 | retval = sgn*inner(u_exact-u, dot(grad(v), n))*ds \
59 | - inner(dot(grad(u), n), v)*ds
60 | penalty = beta*h_E**(-1)*inner(u-u_exact, v)*ds
61 | if (overPenalize or sym):
62 | retval += penalty
63 | return retval
64 |
65 | def pdeRes(u,v,f,u_exact=None,weak_bc=False,sym=False):
66 | """
67 | The variational form of the PDE residual for the Poisson's problem
68 | """
69 | retval = interiorResidual(u,v,f)
70 | if (weak_bc):
71 | retval += boundaryResidual(u,v,u_exact,sym=sym)
72 | return retval
73 |
74 | def outputForm(u, f, u_exact):
75 | return 0.5*inner(u-u_exact, u-u_exact)*dx + \
76 | ALPHA/2*f**2*dx
77 |
78 | class Expression_f:
79 | def __init__(self):
80 | self.alpha = 1e-6
81 |
82 | def eval(self, x):
83 | return (1/(1+self.alpha*4*np.power(PI,4))*
84 | np.sin(PI*x[0])*np.sin(PI*x[1]))
85 |
86 | class Expression_u:
87 | def __init__(self):
88 | pass
89 |
90 | def eval(self, x):
91 | return (1/(2*np.power(PI, 2))*
92 | np.sin(PI*x[0])*np.sin(PI*x[1]))
93 |
94 |
95 | fea = FEA(mesh)
96 | # Record the function evaluations during optimization process
97 | fea.record = True
98 | # Add input to the PDE problem:
99 | input_name = 'f'
100 | input_function_space = FunctionSpace(mesh, ('DG', 0))
101 | input_function = Function(input_function_space)
102 | # Add state to the PDE problem:
103 | state_name = 'u'
104 | state_function_space = FunctionSpace(mesh, ('CG', 1))
105 | state_function = Function(state_function_space)
106 | v = TestFunction(state_function_space)
107 |
108 | u_ex = fea.add_exact_solution(Expression_u, state_function_space)
109 | f_ex = fea.add_exact_solution(Expression_f, input_function_space)
110 |
111 |
112 | ALPHA = 1e-6
113 | # Add output to the PDE problem:
114 | output_name = 'l2_functional'
115 | output_form = outputForm(state_function, input_function, u_ex)
116 |
117 |
118 |
119 | '''
120 | 3. Define the boundary conditions
121 | '''
122 |
123 | ############ Strongly enforced boundary conditions #############
124 | ubc = Function(state_function_space)
125 | ubc.vector.set(0.0)
126 | locate_BC1 = locate_dofs_geometrical((state_function_space, state_function_space),
127 | lambda x: np.isclose(x[0], 0. ,atol=1e-6))
128 | locate_BC2 = locate_dofs_geometrical((state_function_space, state_function_space),
129 | lambda x: np.isclose(x[0], 1. ,atol=1e-6))
130 | locate_BC3 = locate_dofs_geometrical((state_function_space, state_function_space),
131 | lambda x: np.isclose(x[1], 0. ,atol=1e-6))
132 | locate_BC4 = locate_dofs_geometrical((state_function_space, state_function_space),
133 | lambda x: np.isclose(x[1], 1. ,atol=1e-6))
134 | locate_BC_list = [locate_BC1, locate_BC2, locate_BC3, locate_BC4]
135 | fea.add_strong_bc(ubc, locate_BC_list, state_function_space)
136 | residual_form = pdeRes(state_function, v, input_function)
137 | #
138 | # ########### Weakly enforced boundary conditions #############
139 | # ############## Unsymmetric Nitsche's method #################
140 | # residual_form = pdeRes(state_function, v, input_function,
141 | # u_exact=u_ex, weak_bc=True, sym=True)
142 | # #############################################################
143 | #
144 |
145 |
146 | fea.add_input(input_name, input_function)
147 | fea.add_state(name=state_name,
148 | function=state_function,
149 | residual_form=residual_form,
150 | arguments=[input_name])
151 | fea.add_output(name=output_name,
152 | type='scalar',
153 | form=output_form,
154 | arguments=[input_name,state_name])
155 |
156 |
157 |
158 | '''
159 | 4. Set up the CSDL model
160 | '''
161 |
162 |
163 | fea.PDE_SOLVER = 'Newton'
164 | # fea.REPORT = True
165 | fea_model = FEAModel(fea=[fea])
166 | fea_model.create_input("{}".format(input_name),
167 | shape=fea.inputs_dict[input_name]['shape'],
168 | val=0.1*np.ones(fea.inputs_dict[input_name]['shape']) * 0.86)
169 |
170 | # fea_model.connect('f','u_state_model.f')
171 | # fea_model.connect('f','l2_functional_output_model.f')
172 | # fea_model.connect('u_state_model.u','l2_functional_output_model.u')
173 |
174 | fea_model.add_design_variable(input_name)
175 | fea_model.add_objective(output_name, scaler=1e5)
176 |
177 | # Ru: the new Python backend of CSDL has issue for promotions or connecting
178 | # the variables for custom operations as from Aug 30.
179 | sim = Simulator(fea_model)
180 | # sim = om_simulator(fea_model)
181 | ########### Test the forward solve ##############
182 | sim[input_name] = getFuncArray(f_ex)
183 |
184 | sim.run()
185 |
186 | ########### Generate the N2 diagram #############
187 | # sim.visualize_implementation()
188 |
189 | ############# Check the derivatives #############
190 | # sim.check_totals()
191 | # sim.check_partials(compact_print=True)
192 | # sim.executable.check_totals(of='l2_functional', wrt='f',compact_print=True)
193 | '''
194 | 5. Set up the optimization problem
195 | '''
196 | # ############## Run the optimization with pyOptSparse #############
197 | # import openmdao.api as om
198 | # ####### Driver = SNOPT #########
199 | # driver = om.pyOptSparseDriver()
200 | # driver.options['optimizer']='SNOPT'
201 | #
202 | # driver.opt_settings['Major feasibility tolerance'] = 1e-12
203 | # driver.opt_settings['Major optimality tolerance'] = 1e-14
204 | # driver.options['print_results'] = False
205 | #
206 | # sim.prob.driver = driver
207 | # sim.prob.setup()
208 | #
209 | # sim.prob.run_driver()
210 |
211 | ############# Run the optimization with modOpt #############
212 | from modopt import CSDLProblem
213 |
214 | prob = CSDLProblem(
215 | problem_name='poisson_opt',
216 | simulator=sim,
217 | )
218 |
219 | from modopt import SNOPT, SLSQP
220 |
221 | # optimizer = SNOPT(prob,
222 | # Major_optimality = 1e-8,
223 | # append2file=True)
224 | # # append2file=False)
225 | optimizer = SLSQP(
226 | prob,
227 | ftol=1e-13,
228 | maxiter=20,
229 | )
230 |
231 | # # Check first derivatives at the initial guess, if needed
232 | # optimizer.check_first_derivatives(prob.x0)
233 |
234 | # Solve your optimization problem
235 | optimizer.solve()
236 | print("="*40)
237 | # optimizer.print_results()
238 |
239 | print("Objective value: ", sim['l2_functional_output_model.'+output_name])
240 | print("="*40)
241 | control_error = errorNorm(f_ex, input_function)
242 | print("Error in controls:", control_error)
243 | state_error = errorNorm(u_ex, state_function)
244 | print("Error in states:", state_error)
245 | print("="*40)
246 |
247 | with XDMFFile(MPI.COMM_WORLD, "solutions/state_"+state_name+".xdmf", "w") as xdmf:
248 | xdmf.write_mesh(fea.mesh)
249 | fea.states_dict[state_name]['function'].name = state_name
250 | xdmf.write_function(fea.states_dict[state_name]['function'])
251 | with XDMFFile(MPI.COMM_WORLD, "solutions/input_"+input_name+".xdmf", "w") as xdmf:
252 | xdmf.write_mesh(fea.mesh)
253 | fea.inputs_dict[input_name]['function'].name = input_name
254 | xdmf.write_function(fea.inputs_dict[input_name]['function'])
255 | with XDMFFile(MPI.COMM_WORLD, "solutions/f_ex.xdmf", "w") as xdmf:
256 | xdmf.write_mesh(fea.mesh)
257 | xdmf.write_function(f_ex)
258 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_v2_gmsh_6492.msh:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:60bf9c90d324ad59210efd6fe6cc866a34c783accabb10662f0a6556bd4cad12
3 | size 752768
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_6rib_caddee_mesh_2374_quad.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:53d2735424c64fdbfbd58ce5e157f99eb15811a152fae571f2dbc3282287eb64
3 | size 80436
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_6rib_caddee_mesh_2374_quad.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:56597594fb41328a5301932f87a7f77617db0301d150a0f0d0bd7884df761caa
3 | size 880
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_6rib_caddee_mesh_4862_quad.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9806b4ce2aec4cbf1889f0297c8ae87e711fb62643bdbd8cf27b71c7266e2adf
3 | size 152727
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_6rib_caddee_mesh_4862_quad.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f2cbdb6ab65467eae9fd9a83e2b771648514cb778d077689ffcfdc60a43c35ec
3 | size 880
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_caddee_mesh_SI_2303_quad.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a7c242ddbd9003aba73a9edb0a473b349f0945a11f67a3a6d0e58aaa97b2450c
3 | size 78798
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_caddee_mesh_SI_2303_quad.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:b628bb6feab64c083274fcc5c1806afe7f3b22064b9bf748524d3df626678d26
3 | size 884
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_caddee_mesh_SI_6307_quad.h5:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:cffc1046773d5126eecd4f002f40ad3be94a708b036b0bdf1a58224d0d37e2cd
3 | size 194199
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_caddee_mesh_SI_6307_quad.xdmf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c49971515516a616b8cb58f638bdef31c7ae5a912b163283a141e4f934664cc8
3 | size 884
4 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_paneled_mesh_data_2303.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/test_shell_m3l/pav_wing/pav_wing_v2_paneled_mesh_data_2303.pickle
--------------------------------------------------------------------------------
/examples/test_shell_m3l/pav_wing/pav_wing_v2_paneled_mesh_data_6307.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RuruX/femo/984caecf3e4b95ef20c3bed13b0a427864beb48b/examples/test_shell_m3l/pav_wing/pav_wing_v2_paneled_mesh_data_6307.pickle
--------------------------------------------------------------------------------
/examples/test_shell_m3l/run_pav_shell_modal.py:
--------------------------------------------------------------------------------
1 | ## Caddee
2 | from caddee.utils.aircraft_models.pav.pav_geom_mesh import PavGeomMesh
3 | import caddee.api as cd
4 |
5 | ## Solvers
6 | from VAST.core.vast_solver import VASTFluidSover
7 | from VAST.core.fluid_problem import FluidProblem
8 | from VAST.core.generate_mappings_m3l import VASTNodalForces
9 | from VAST.core.vlm_llt.viscous_correction import ViscousCorrectionModel
10 | # from lsdo_airfoil.core.pressure_profile import PressureProfile, NodalPressureProfile
11 | import dolfinx
12 | from femo.fea.utils_dolfinx import *
13 | import shell_module as rmshell
14 | from shell_pde import ShellPDE
15 |
16 | # Other lsdo lab stuff
17 | import csdl
18 | from python_csdl_backend import Simulator
19 | from modopt import SLSQP
20 | from modopt import CSDLProblem
21 | import m3l
22 | from m3l.utils.utils import index_functions
23 | import lsdo_geo as lg
24 | import array_mapper as am
25 | from m3l.core.function_spaces import IDWFunctionSpace
26 |
27 | ## Other stuff
28 | import numpy as np
29 | from mpi4py import MPI
30 | import pickle
31 | import pathlib
32 | import sys
33 |
34 | sys.setrecursionlimit(100000)
35 |
36 | debug_geom_flag = False
37 | force_reprojection = False
38 | visualize_flag = False
39 | ft2m = 0.3048
40 | in2m = 0.0254
41 |
42 | wing_cl0 = 0.3366
43 | pitch_angle_list = [-0.02403544, 6, 12.48100761]
44 |
45 |
46 | pitch_angle = np.deg2rad(pitch_angle_list[2])
47 |
48 |
49 | caddee = cd.CADDEE()
50 | caddee.system_model = system_model = cd.SystemModel()
51 |
52 | # region Geometry and meshes
53 | pav_geom_mesh = PavGeomMesh()
54 | pav_geom_mesh.setup_geometry(
55 | include_wing_flag=True,
56 | include_htail_flag=False,
57 | )
58 | pav_geom_mesh.setup_internal_wingbox_geometry(debug_geom_flag=debug_geom_flag,
59 | force_reprojection=force_reprojection)
60 | pav_geom_mesh.sys_rep.spatial_representation.assemble()
61 | pav_geom_mesh.oml_mesh(include_wing_flag=True,
62 | debug_geom_flag=debug_geom_flag, force_reprojection=force_reprojection)
63 | pav_geom_mesh.vlm_meshes(include_wing_flag=True, num_wing_spanwise_vlm=21, num_wing_chordwise_vlm=5,
64 | visualize_flag=visualize_flag, force_reprojection=force_reprojection)
65 | pav_geom_mesh.setup_index_functions()
66 |
67 | caddee.system_representation = sys_rep = pav_geom_mesh.sys_rep
68 | caddee.system_parameterization = sys_param = pav_geom_mesh.sys_param
69 | sys_param.setup()
70 | spatial_rep = sys_rep.spatial_representation
71 | # endregion
72 |
73 | # region FEniCS
74 | #############################################
75 | # filename = "./pav_wing/pav_wing_v2_caddee_mesh_SI_6307_quad.xdmf"
76 | filename = "./pav_wing/pav_wing_v2_caddee_mesh_SI_2303_quad.xdmf"
77 |
78 | with dolfinx.io.XDMFFile(MPI.COMM_WORLD, filename, "r") as xdmf:
79 | fenics_mesh = xdmf.read_mesh(name="Grid")
80 | nel = fenics_mesh.topology.index_map(fenics_mesh.topology.dim).size_local
81 | nn = fenics_mesh.topology.index_map(0).size_local
82 |
83 | nodes = fenics_mesh.geometry.x
84 |
85 |
86 | with open('./pav_wing/pav_wing_v2_paneled_mesh_data_'+str(nodes.shape[0])+'.pickle', 'rb') as f:
87 | nodes_parametric = pickle.load(f)
88 |
89 | for i in range(len(nodes_parametric)):
90 | nodes_parametric[i] = (nodes_parametric[i][0].replace(' ', '_').replace(',',''), np.array([nodes_parametric[i][1]]))
91 |
92 | wing_thickness = pav_geom_mesh.functions['wing_thickness']
93 | thickness_nodes = wing_thickness.evaluate(nodes_parametric)
94 |
95 | shell_pde = ShellPDE(fenics_mesh)
96 |
97 |
98 | # Unstiffened Aluminum 2024 (T4)
99 | # reference: https://asm.matweb.com/search/SpecificMaterial.asp?bassnum=ma2024t4
100 | E = 73.1E9 # unit: Pa
101 | nu = 0.33
102 | h = 0.02*in2m # unit: m
103 | rho = 2780 # unit: kg/m^3
104 | f_d = -rho*h*9.81 # self-weight unit: N
105 | tensile_yield_strength = 324E6 # unit: Pa
106 | safety_factor = 1.5
107 |
108 |
109 | y_bc = -1e-6
110 | semispan = pav_geom_mesh.geom_data['points']['wing']['l_tip_te'][1] + 0.001
111 |
112 | G = E/2/(1+nu)
113 |
114 | #### Getting facets of the LEFT and the RIGHT edge ####
115 | DOLFIN_EPS = 3E-16
116 | def ClampedBoundary(x):
117 | return np.greater(x[1], y_bc)
118 | def TipChar(x):
119 | return np.less(x[1], semispan)
120 | fdim = fenics_mesh.topology.dim - 1
121 |
122 | ds_1 = createCustomMeasure(fenics_mesh, fdim, ClampedBoundary, measure='ds', tag=100)
123 | dS_1 = createCustomMeasure(fenics_mesh, fdim, ClampedBoundary, measure='dS', tag=100)
124 | dx_2 = createCustomMeasure(fenics_mesh, fdim+1, TipChar, measure='dx', tag=10)
125 |
126 | g = Function(shell_pde.W)
127 | with g.vector.localForm() as uloc:
128 | uloc.set(0.)
129 |
130 | ################### m3l ########################
131 |
132 | # create the shell dictionaries:
133 | shells = {}
134 | shells['wing_shell'] = {'E': E, 'nu': nu, 'rho': rho,# material properties
135 | 'dss': ds_1(100), # custom integrator: ds measure
136 | 'dSS': dS_1(100), # custom integrator: dS measure
137 | 'dxx': dx_2(10), # custom integrator: dx measure
138 | 'g': g}
139 |
140 |
141 | ################# PAV Wing #################
142 |
143 | # Wing shell Mesh
144 | z_offset = 0.0
145 | wing_shell_mesh = am.MappedArray(input=fenics_mesh.geometry.x).reshape((-1,3))
146 | shell_mesh = rmshell.LinearShellMesh(
147 | meshes=dict(
148 | wing_shell_mesh=wing_shell_mesh,
149 | ))
150 |
151 |
152 | # endregion
153 |
154 |
155 |
156 | # region Mission
157 | design_scenario_name = 'structural_sizing'
158 | design_scenario = cd.DesignScenario(name=design_scenario_name)
159 | # endregion
160 |
161 | # region Cruise condition
162 | cruise_name = "cruise_3"
163 | cruise_model = m3l.Model()
164 | cruise_condition = cd.CruiseCondition(name=cruise_name)
165 | cruise_condition.atmosphere_model = cd.SimpleAtmosphereModel()
166 | cruise_condition.set_module_input(name='altitude', val=600 * ft2m)
167 | cruise_condition.set_module_input(name='mach_number', val=0.145972) # 112 mph = 0.145972 Mach
168 | cruise_condition.set_module_input(name='range', val=80467.2) # 50 miles = 80467.2 m
169 | cruise_condition.set_module_input(name='pitch_angle', val=pitch_angle)
170 | cruise_condition.set_module_input(name='flight_path_angle', val=0)
171 | cruise_condition.set_module_input(name='roll_angle', val=0)
172 | cruise_condition.set_module_input(name='yaw_angle', val=0)
173 | cruise_condition.set_module_input(name='wind_angle', val=0)
174 | cruise_condition.set_module_input(name='observer_location', val=np.array([0, 0, 600 * ft2m]))
175 |
176 | cruise_ac_states = cruise_condition.evaluate_ac_states()
177 | cruise_model.register_output(cruise_ac_states)
178 | # endregion
179 |
180 | # region VLM Solver
181 | vlm_model = VASTFluidSover(
182 | surface_names=[
183 | pav_geom_mesh.mesh_data['vlm']['mesh_name']['wing'],
184 | ],
185 | surface_shapes=[
186 | (1,) + pav_geom_mesh.mesh_data['vlm']['chamber_surface']['wing'].evaluate().shape[1:],
187 | ],
188 | fluid_problem=FluidProblem(solver_option='VLM', problem_type='fixed_wake'),
189 | mesh_unit='m',
190 | cl0=[wing_cl0, ]
191 | )
192 | wing_vlm_panel_forces, vlm_forces, vlm_moments = vlm_model.evaluate(ac_states=cruise_ac_states)
193 | cruise_model.register_output(vlm_forces)
194 | cruise_model.register_output(vlm_moments)
195 |
196 | vlm_force_mapping_model = VASTNodalForces(
197 | surface_names=[
198 | pav_geom_mesh.mesh_data['vlm']['mesh_name']['wing'],
199 | ],
200 | surface_shapes=[
201 | (1,) + pav_geom_mesh.mesh_data['vlm']['chamber_surface']['wing'].evaluate().shape[1:],
202 | ],
203 | initial_meshes=[
204 | pav_geom_mesh.mesh_data['vlm']['chamber_surface']['wing'],
205 | ]
206 | )
207 |
208 | wing_oml_mesh = pav_geom_mesh.mesh_data['oml']['oml_geo_nodes']['wing']
209 | oml_forces = vlm_force_mapping_model.evaluate(vlm_forces=wing_vlm_panel_forces,
210 | nodal_force_meshes=[wing_oml_mesh, ])
211 | wing_forces = oml_forces[0]
212 |
213 | # endregion
214 |
215 | # region Strucutral Loads
216 |
217 | wing_force = pav_geom_mesh.functions['wing_force']
218 | oml_para_nodes = pav_geom_mesh.mesh_data['oml']['oml_para_nodes']['wing']
219 |
220 |
221 | wing_force.inverse_evaluate(oml_para_nodes, wing_forces)
222 | cruise_model.register_output(wing_force.coefficients)
223 |
224 | left_wing_oml_para_coords = pav_geom_mesh.mesh_data['oml']['oml_para_nodes']['left_wing']
225 | left_oml_geo_nodes = spatial_rep.evaluate_parametric(left_wing_oml_para_coords)
226 |
227 | left_wing_forces = wing_force.evaluate(left_wing_oml_para_coords)
228 | wing_component = pav_geom_mesh.geom_data['components']['wing']
229 |
230 | shell_force_map_model = rmshell.RMShellForces(component=wing_component,
231 | mesh=shell_mesh,
232 | pde=shell_pde,
233 | shells=shells)
234 | cruise_structural_wing_mesh_forces = shell_force_map_model.evaluate(
235 | nodal_forces=left_wing_forces,
236 | nodal_forces_mesh=left_oml_geo_nodes)
237 | # endregion
238 |
239 | # region Structures
240 |
241 | shell_displacements_model = rmshell.RMShell(component=wing_component,
242 | mesh=shell_mesh,
243 | pde=shell_pde,
244 | shells=shells)
245 |
246 | cruise_structural_wing_mesh_displacements, cruise_structural_wing_mesh_rotations, wing_mass = \
247 | shell_displacements_model.evaluate(
248 | forces=cruise_structural_wing_mesh_forces,
249 | thicknesses=thickness_nodes)
250 |
251 | cruise_model.register_output(cruise_structural_wing_mesh_displacements)
252 | cruise_model.register_output(wing_mass)
253 |
254 | # endregion
255 |
256 | # region Nodal Displacements
257 |
258 | grid_num = 10
259 | transfer_para_mesh = []
260 | structural_left_wing_names = pav_geom_mesh.geom_data['primitive_names']['structural_left_wing_names']
261 | for name in structural_left_wing_names:
262 | for u in np.linspace(0,1,grid_num):
263 | for v in np.linspace(0,1,grid_num):
264 | transfer_para_mesh.append((name, np.array([u,v]).reshape((1,2))))
265 |
266 | transfer_geo_nodes_ma = spatial_rep.evaluate_parametric(transfer_para_mesh)
267 |
268 |
269 | shell_nodal_displacements_model = rmshell.RMShellNodalDisplacements(component=wing_component,
270 | mesh=shell_mesh,
271 | pde=shell_pde,
272 | shells=shells)
273 | nodal_displacements = shell_nodal_displacements_model.evaluate(cruise_structural_wing_mesh_displacements, transfer_geo_nodes_ma)
274 | wing_displacement = pav_geom_mesh.functions['wing_displacement']
275 |
276 | wing_displacement.inverse_evaluate(transfer_para_mesh, nodal_displacements)
277 | cruise_model.register_output(wing_displacement.coefficients)
278 |
279 | # endregion
280 |
281 | # Add cruise m3l model to cruise condition
282 | cruise_condition.add_m3l_model('cruise_model', cruise_model)
283 | # Add design condition to design scenario
284 | design_scenario.add_design_condition(cruise_condition)
285 |
286 | system_model.add_design_scenario(design_scenario=design_scenario)
287 |
288 | caddee_csdl_model = caddee.assemble_csdl()
289 |
290 | system_model_name = 'system_model.'+design_scenario_name+'.'+cruise_name+'.'+cruise_name+'.'
291 |
292 | caddee_csdl_model.add_constraint(system_model_name+'Wing_rm_shell_model.rm_shell.compliance_model.compliance',upper=2E-4,scaler=1E4)
293 | caddee_csdl_model.add_constraint(system_model_name+'Wing_rm_shell_model.rm_shell.aggregated_stress_model.wing_shell_aggregated_stress',upper=324E6/1.5,scaler=1E-8)
294 | caddee_csdl_model.add_objective(system_model_name+'Wing_rm_shell_model.rm_shell.mass_model.mass', scaler=1e-1)
295 |
296 | # Minimum thickness: 0.02 inch -> 0.000508 m
297 |
298 | h_min = h
299 |
300 | i = 0
301 | shape = (9, 1)
302 | valid_structural_left_wing_names = structural_left_wing_names
303 |
304 | ################################################################
305 | #### Full thicknesses: individual for spars, skins and ribs ####
306 | ################################################################
307 | for name in valid_structural_left_wing_names:
308 | primitive = spatial_rep.get_primitives([name])[name].geometry_primitive
309 | name = name.replace(' ', '_').replace(',','')
310 | surface_id = i
311 |
312 | h_init = caddee_csdl_model.create_input('wing_thickness_dv_'+name, val=h_min)
313 | # h_init = caddee_csdl_model.create_input('wing_thickness_'+name, val=h_min+i*0.0001)
314 | caddee_csdl_model.add_design_variable('wing_thickness_dv_'+name, # 0.02 in
315 | lower=0.005 * in2m,
316 | upper=0.1 * in2m,
317 | scaler=1000,
318 | )
319 | caddee_csdl_model.register_output('wing_thickness_surface_'+name, csdl.expand(h_init, shape))
320 | caddee_csdl_model.connect('wing_thickness_surface_'+name,
321 | system_model_name+'wing_thickness_function_evaluation.'+\
322 | name+'_wing_thickness_coefficients')
323 | i += 1
324 |
325 |
326 | sim = Simulator(caddee_csdl_model, analytics=True)
327 | sim.run()
328 |
329 | # sim.check_totals(of=[system_model_name+'Wing_rm_shell_model.rm_shell.aggregated_stress_model.wing_shell_aggregated_stress'],
330 | # wrt=['h_spar', 'h_skin', 'h_rib'])
331 |
332 | # sim.check_totals(of=[system_model_name+'Wing_rm_shell_model.rm_shell.mass_model.mass'],
333 | # wrt=['h_spar', 'h_skin', 'h_rib'])
334 | ########################## Run optimization ##################################
335 | prob = CSDLProblem(problem_name='pav', simulator=sim)
336 |
337 | # optimizer = SLSQP(prob, maxiter=50, ftol=1E-5)
338 |
339 | from modopt import SNOPT
340 | optimizer = SNOPT(prob,
341 | Major_iterations = 100,
342 | Major_optimality = 1e-5,
343 | append2file=False)
344 |
345 | optimizer.solve()
346 | optimizer.print_results()
347 |
348 |
349 | ####### Aerodynamic output ##########
350 | print("="*60)
351 | print("="*20+'aerodynamic outputs'+"="*20)
352 | print("="*60)
353 | print('Pitch: ', np.rad2deg(
354 | sim[system_model_name+cruise_name+'_ac_states_operation.'+cruise_name+'_pitch_angle']))
355 | print('C_L: ', sim[system_model_name+'wing_vlm_mesh_vlm_model.vast.VLMSolverModel.VLM_outputs.LiftDrag.wing_vlm_mesh_C_L'])
356 | # print('Total lift: ', sim[system_model_name+'wing_vlm_mesh_vlm_model.vast.VLMSolverModel.VLM_outputs.LiftDrag.total_lift'])
357 |
358 | ####### Structural output ##########
359 | print("="*60)
360 | print("="*20+'structure outputs'+"="*20)
361 | print("="*60)
362 | # Comparing the solution to the Kirchhoff analytical solution
363 | f_shell = sim[system_model_name+'Wing_rm_shell_force_mapping.wing_shell_forces']
364 | f_vlm = sim[system_model_name+'wing_vlm_mesh_vlm_nodal_forces_model.wing_vlm_mesh_oml_forces'].reshape((-1,3))
365 | u_shell = sim[system_model_name+'Wing_rm_shell_model.rm_shell.disp_extraction_model.wing_shell_displacement']
366 | # u_nodal = sim['Wing_rm_shell_displacement_map.wing_shell_nodal_displacement']
367 | uZ = u_shell[:,2]
368 | # uZ_nodal = u_nodal[:,2]
369 |
370 |
371 | wing_tip_compliance = sim[system_model_name+'Wing_rm_shell_model.rm_shell.compliance_model.compliance']
372 | wing_mass = sim[system_model_name+'Wing_rm_shell_model.rm_shell.mass_model.mass']
373 | wing_elastic_energy = sim[system_model_name+'Wing_rm_shell_model.rm_shell.elastic_energy_model.elastic_energy']
374 | wing_aggregated_stress = sim[system_model_name+'Wing_rm_shell_model.rm_shell.aggregated_stress_model.wing_shell_aggregated_stress']
375 | wing_von_Mises_stress = sim[system_model_name+'Wing_rm_shell_model.rm_shell.von_Mises_stress_model.von_Mises_stress']
376 | ########## Output: ##########
377 | # print("Spar, rib, skin thicknesses:", sim['h_spar'], sim['h_rib'], sim['h_skin'])
378 |
379 | fz_func = Function(shell_pde.VT)
380 | fz_func.x.array[:] = f_shell[:,-1]
381 |
382 | fx_func = Function(shell_pde.VT)
383 | fx_func.x.array[:] = f_shell[:,0]
384 |
385 | fy_func = Function(shell_pde.VT)
386 | fy_func.x.array[:] = f_shell[:,1]
387 |
388 | dummy_func = Function(shell_pde.VT)
389 | dummy_func.x.array[:] = 1.0
390 | print("vlm forces:", sum(f_vlm[:,0]),sum(f_vlm[:,1]),sum(f_vlm[:,2]))
391 | print("shell forces:", dolfinx.fem.assemble_scalar(form(fx_func*ufl.dx)),
392 | dolfinx.fem.assemble_scalar(form(fy_func*ufl.dx)),
393 | dolfinx.fem.assemble_scalar(form(fz_func*ufl.dx)))
394 |
395 | print("Wing surface area:", dolfinx.fem.assemble_scalar(form(dummy_func*ufl.dx)))
396 | print("Wing tip deflection (m):",max(abs(uZ)))
397 | print("Wing tip compliance (= tip deflection^3/2 m^3):",wing_tip_compliance)
398 | print("Wing total mass (kg):", wing_mass)
399 | print("Wing aggregated von Mises stress (Pascal):", wing_aggregated_stress)
400 | print("Wing maximum von Mises stress (Pascal):", max(wing_von_Mises_stress))
401 | print(" Number of elements = "+str(nel))
402 | print(" Number of vertices = "+str(nn))
403 |
404 |
--------------------------------------------------------------------------------
/examples/test_shell_m3l/shell_dynamic_pde.py:
--------------------------------------------------------------------------------
1 | from femo.fea.fea_dolfinx import FEA
2 | from femo.csdl_opt.fea_model import FEAModel
3 | from femo.csdl_opt.state_model import StateModel
4 | from femo.csdl_opt.output_model import OutputModel, OutputFieldModel
5 | from shell_analysis_fenicsx import *
6 | from shell_analysis_fenicsx.read_properties import readCLT, sortIndex
7 | from lsdo_modules.module_csdl.module_csdl import ModuleCSDL
8 | import basix
9 | import scipy.sparse as sp
10 | import csdl
11 | import numpy as np
12 |
13 | from shell_pde import (ShellModule, ForceReshapingModel, DisplacementExtractionModel,
14 | AggregatedStressModel)
15 |
16 | class ShellResidual(ShellModule):
17 | '''
18 | Dynamic shell model
19 |
20 | Output:
21 | - residual
22 | '''
23 | def initialize(self):
24 | self.parameters.declare('pde', default=None)
25 | self.parameters.declare('shells', default={}) # material properties
26 |
27 | def define(self):
28 | pde = self.parameters['pde']
29 | shell_mesh = pde.mesh
30 | shells = self.parameters['shells']
31 | shell_name = list(shells.keys())[0] # this is only taking the first mesh added to the solver.
32 |
33 | E = shells[shell_name]['E']
34 | nu = shells[shell_name]['nu']
35 | rho = shells[shell_name]['rho']
36 | dss = shells[shell_name]['dss']
37 | dSS = shells[shell_name]['dSS']
38 | dxx = shells[shell_name]['dxx']
39 | g = shells[shell_name]['g']
40 |
41 | PENALTY_BC = True
42 |
43 |
44 | fea = FEA(shell_mesh)
45 | fea.PDE_SOLVER = "Newton"
46 | fea.initialize = True
47 | fea.linear_problem = True
48 | # Add input to the PDE problem:
49 | input_name_1 = shell_name+'_thicknesses'
50 | input_function_space_1 = pde.VT
51 | # input_function_space_1 = FunctionSpace(shell_mesh, ("DG", 0))
52 | input_function_1 = Function(input_function_space_1)
53 | # Add input to the PDE problem:
54 | input_name_2 = 'F_solid'
55 | input_function_space_2 = pde.VF
56 | input_function_2 = Function(input_function_space_2)
57 |
58 | # Add state to the PDE problem:
59 | state_name = 'disp_solid'
60 | state_function_space = pde.W
61 | state_function = Function(state_function_space)
62 |
63 |
64 | # Simple isotropic material
65 | residual_form = pde.pdeRes(input_function_1,state_function,
66 | input_function_2,E,nu,
67 | penalty=PENALTY_BC, dss=dss, dSS=dSS, g=g)
68 |
69 | # Add output to the PDE problem:
70 | output_name_1 = 'compliance'
71 | u_mid, theta = ufl.split(state_function)
72 | output_form_1 = pde.compliance(u_mid,input_function_1, dxx)
73 | output_name_2 = 'mass'
74 | output_form_2 = pde.mass(input_function_1, rho)
75 | output_name_3 = 'elastic_energy'
76 | output_form_3 = pde.elastic_energy(state_function,input_function_1,E)
77 | output_name_4 = 'pnorm_stress'
78 | m, rho = 1e-6, 100
79 | dx_reduced = ufl.Measure("dx", domain=shell_mesh, metadata={"quadrature_degree":4})
80 | output_form_4 = pde.pnorm_stress(state_function,input_function_1,E,nu,
81 | dx_reduced,m=m,rho=rho,alpha=None,regularization=False)
82 | output_name_5 = 'von_Mises_stress'
83 | output_form_5 = pde.von_Mises_stress(state_function,input_function_1,E,nu,surface='Top')
84 | fea.add_input(input_name_1, input_function_1, init_val=0.001, record=True)
85 | fea.add_input(input_name_2, input_function_2, record=True)
86 | fea.add_state(name=state_name,
87 | function=state_function,
88 | residual_form=residual_form,
89 | arguments=[input_name_1, input_name_2])
90 | fea.add_output(name=output_name_1,
91 | type='scalar',
92 | form=output_form_1,
93 | arguments=[state_name,input_name_1])
94 | fea.add_output(name=output_name_2,
95 | type='scalar',
96 | form=output_form_2,
97 | arguments=[input_name_1])
98 | fea.add_output(name=output_name_3,
99 | type='scalar',
100 | form=output_form_3,
101 | arguments=[input_name_1,state_name])
102 | fea.add_output(name=output_name_4,
103 | type='scalar',
104 | form=output_form_4,
105 | arguments=[input_name_1,state_name])
106 | fea.add_field_output(name=output_name_5,
107 | form=output_form_5,
108 | arguments=[input_name_1,state_name],
109 | record=True)
110 | force_reshaping_model = ForceReshapingModel(pde=pde,
111 | input_name=shell_name+'_forces',
112 | output_name=input_name_2)
113 | solid_model = StateModel(fea=fea,
114 | debug_mode=False,
115 | state_name=state_name,
116 | arg_name_list=fea.states_dict[state_name]['arguments'])
117 | compliance_model = OutputModel(fea=fea,
118 | output_name=output_name_1,
119 | arg_name_list=fea.outputs_dict[output_name_1]['arguments'])
120 | mass_model = OutputModel(fea=fea,
121 | output_name=output_name_2,
122 | arg_name_list=fea.outputs_dict[output_name_2]['arguments'])
123 | elastic_energy_model = OutputModel(fea=fea,
124 | output_name=output_name_3,
125 | arg_name_list=fea.outputs_dict[output_name_3]['arguments'])
126 | pnorm_stress_model = OutputModel(fea=fea,
127 | output_name=output_name_4,
128 | arg_name_list=fea.outputs_dict[output_name_4]['arguments'])
129 | von_Mises_stress_model = OutputFieldModel(fea=fea,
130 | output_name=output_name_5,
131 | arg_name_list=fea.outputs_field_dict[output_name_5]['arguments'])
132 |
133 | disp_extraction_model = DisplacementExtractionModel(pde=pde,
134 | input_name=state_name,
135 | output_name=shell_name+'_displacement')
136 | aggregated_stress_model = AggregatedStressModel(m=m, rho=rho,
137 | input_name=output_name_4,
138 | output_name=shell_name+'_aggregated_stress')
139 |
140 | self.add(force_reshaping_model, name='force_reshaping_model')
141 | self.add(solid_model, name='solid_model')
142 | self.add(disp_extraction_model, name='disp_extraction_model')
143 | self.add(compliance_model, name='compliance_model')
144 | self.add(von_Mises_stress_model, name='von_Mises_stress_model')
145 | self.add(mass_model, name='mass_model')
146 | self.add(elastic_energy_model, name='elastic_energy_model')
147 | self.add(pnorm_stress_model, name='von_mises_stress_model')
148 | self.add(aggregated_stress_model, name='aggregated_stress_model')
149 |
150 |
151 | dim = len(state_function.x.array)
152 | F_ssr = self.create_output('F_extended', shape = (2*dim,))
153 | # Need to expose the output variable F_solid
154 | Fi = self.register_output('Fi', csdl.reshape(F_solid, dim))
155 |
156 | for i in range(dim):
157 | F_ssr[i] = Fi[i]
158 | F_ssr[i+dim] = 0
159 |
160 | K = self.register_output('K', csdl.matmat(csdl.matmat(mask, sum_k), mask) + mask_eye)
161 | mass_matrix = self.register_output('mass_matrix', csdl.matmat(csdl.matmat(mask, sum_m), mask) + mask_eye)
162 |
163 | # compute inverse mass matrix
164 | mass_matrix_inverse = self.create_output('mass_matrix_inverse', shape=mass_matrix.shape)
165 | for i in range(mass_matrix.shape[0]):
166 | mass_matrix_inverse[i,i] = 1/mass_matrix[i,i]
167 |
168 | A_ssr = self.create_output('A_ssr', shape = (2*dim,2*dim))
169 | for i in range(dim):
170 | for j in range(dim):
171 | A_ssr[i,j] = 0
172 | A_ssr[i+dim,j+dim] = 0
173 | if i == j:
174 | A_ssr[i+dim,j] = 1
175 | else:
176 | A_ssr[i+dim,j] = 0
177 | A_ssr[0:dim,dim:2*dim] = -csdl.matmat(K*mass_matrix_inverse)
178 |
179 | delta = self.create_output('delta', shape = (2*dim,))
180 | delta[0:dim] = self.declare_variable('velocities', shape=(dim,), val=0)
181 | delta[dim:2*dim] = self.declare_variable('displacements', shape=(dim,), val=0)
182 |
183 | residual = self.register_output('residual', F_ssr + csdl.matvec(A_ssr, delta))
184 |
185 |
186 |
--------------------------------------------------------------------------------
/femo/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/femo/csdl_opt/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/femo/csdl_opt/fea_model.py:
--------------------------------------------------------------------------------
1 | from csdl import Model
2 | from femo.csdl_opt.state_model import StateModel
3 | from femo.csdl_opt.output_model import OutputModel, OutputFieldModel
4 |
5 | class FEAModel(Model):
6 | def initialize(self):
7 | self.parameters.declare('fea')
8 |
9 | def define(self):
10 | self.fea_list = fea_list = self.parameters['fea']
11 | for fea in fea_list:
12 | for state_name in fea.states_dict:
13 | arg_name_list_state = fea.states_dict[state_name]['arguments']
14 | state_model = StateModel(fea=fea,
15 | debug_mode=True,
16 | state_name=state_name,
17 | arg_name_list=arg_name_list_state)
18 |
19 | self.add(state_model,
20 | name='{}_state_model'.format(state_name))
21 |
22 | for output_name in fea.outputs_dict:
23 | arg_name_list_output = fea.outputs_dict[output_name]['arguments']
24 | output_model = OutputModel(fea=fea,
25 | output_name=output_name,
26 | arg_name_list=arg_name_list_output)
27 |
28 | self.add(output_model,
29 | name='{}_output_model'.format(output_name))
30 |
31 | for output_name in fea.outputs_field_dict:
32 | arg_name_list_output = fea.outputs_field_dict[output_name]['arguments']
33 | output_model = OutputFieldModel(fea=fea,
34 | output_name=output_name,
35 | arg_name_list=arg_name_list_output)
36 |
37 | self.add(output_model,
38 | name='{}_output_model'.format(output_name))
39 |
--------------------------------------------------------------------------------
/femo/csdl_opt/output_model.py:
--------------------------------------------------------------------------------
1 | from femo.fea.fea_dolfinx import *
2 | from csdl import Model, CustomExplicitOperation
3 | import csdl
4 | import numpy as np
5 |
6 |
7 | class OutputModel(Model):
8 |
9 | def initialize(self):
10 | self.parameters.declare('fea', types=FEA)
11 | self.parameters.declare('output_name', types=str)
12 | self.parameters.declare('arg_name_list', types=list)
13 |
14 | def define(self):
15 | self.fea = self.parameters['fea']
16 | arg_name_list = self.parameters['arg_name_list']
17 | output_name = self.parameters['output_name']
18 |
19 | args_dict = dict()
20 | args_list = []
21 | for arg_name in arg_name_list:
22 | if arg_name in self.fea.inputs_dict:
23 | args_dict[arg_name] = self.fea.inputs_dict[arg_name]
24 | elif arg_name in self.fea.states_dict:
25 | args_dict[arg_name] = self.fea.states_dict[arg_name]
26 | arg = self.declare_variable(arg_name,
27 | shape=(args_dict[arg_name]['shape'],),
28 | val=1.0)
29 | args_list.append(arg)
30 |
31 |
32 | e = OutputOperation(fea=self.fea,
33 | args_dict=args_dict,
34 | output_name=output_name,
35 | )
36 | output = csdl.custom(*args_list, op=e)
37 | output_ = self.register_output(output_name, output)
38 | self.print_var(output_)
39 |
40 | class OutputOperation(CustomExplicitOperation):
41 | """
42 | input: input/state variables
43 | output: output
44 | """
45 | def initialize(self):
46 | self.parameters.declare('fea')
47 | self.parameters.declare('args_dict')
48 | self.parameters.declare('output_name')
49 |
50 | def define(self):
51 | self.fea = self.parameters['fea']
52 | self.output_name = output_name = self.parameters['output_name']
53 | self.args_dict = args_dict = self.parameters['args_dict']
54 | for arg_name in args_dict:
55 | arg = args_dict[arg_name]
56 | self.add_input(arg_name,
57 | shape=(arg['shape'],),)
58 | self.output = self.fea.outputs_dict[output_name]
59 | self.output_size = self.output['shape']
60 | # for field output
61 | self.output_dim = 1
62 | # for scalar output
63 | if self.output_size == 1:
64 | self.output_dim = 0
65 | self.add_output(output_name,
66 | shape=(self.output_size,))
67 | self.declare_derivatives('*', '*')
68 |
69 | def compute(self, inputs, outputs):
70 | for arg_name in inputs:
71 | arg = self.args_dict[arg_name]
72 | update(arg['function'], inputs[arg_name])
73 |
74 | outputs[self.output_name] = np.array(assemble(self.output['form'],
75 | dim=self.output_dim))
76 |
77 | def compute_derivatives(self, inputs, derivatives):
78 | for arg_name in inputs:
79 | arg = self.args_dict[arg_name]
80 | update(arg['function'], inputs[arg_name])
81 |
82 | for arg_name in self.args_dict:
83 | derivatives[self.output_name,arg_name] = assemble(
84 | computePartials(
85 | self.output['form'],
86 | self.args_dict[arg_name]['function']),
87 | dim=self.output_dim+1)
88 |
89 |
90 |
91 | class OutputFieldModel(Model):
92 |
93 | def initialize(self):
94 | self.parameters.declare('fea', types=FEA)
95 | self.parameters.declare('output_name', types=str)
96 | self.parameters.declare('arg_name_list', types=list)
97 |
98 | def define(self):
99 | self.fea = self.parameters['fea']
100 | arg_name_list = self.parameters['arg_name_list']
101 | output_name = self.parameters['output_name']
102 |
103 | args_dict = dict()
104 | args_list = []
105 | for arg_name in arg_name_list:
106 | if arg_name in self.fea.inputs_dict:
107 | args_dict[arg_name] = self.fea.inputs_dict[arg_name]
108 | elif arg_name in self.fea.states_dict:
109 | args_dict[arg_name] = self.fea.states_dict[arg_name]
110 | arg = self.declare_variable(arg_name,
111 | shape=(args_dict[arg_name]['shape'],),
112 | val=1.0)
113 | args_list.append(arg)
114 |
115 | e = OutputFieldOperation(fea=self.fea,
116 | args_dict=args_dict,
117 | output_name=output_name,
118 | )
119 | output = csdl.custom(*args_list, op=e)
120 | self.register_output(output_name, output)
121 |
122 | class OutputFieldOperation(CustomExplicitOperation):
123 | """
124 | input: input/state variables
125 | output: output
126 | """
127 | def initialize(self):
128 | self.parameters.declare('fea')
129 | self.parameters.declare('args_dict')
130 | self.parameters.declare('output_name')
131 |
132 | def define(self):
133 | self.fea = self.parameters['fea']
134 | self.output_name = output_name = self.parameters['output_name']
135 | self.args_dict = args_dict = self.parameters['args_dict']
136 | for arg_name in args_dict:
137 | arg = args_dict[arg_name]
138 | self.add_input(arg_name,
139 | shape=(arg['shape'],),)
140 | self.output = self.fea.outputs_field_dict[output_name]
141 | self.output_size = self.output['shape']
142 | # for field output
143 | self.output_dim = 1
144 |
145 | self.add_output(output_name,
146 | shape=(self.output_size,))
147 | # self.declare_derivatives('*', '*')
148 |
149 | def compute(self, inputs, outputs):
150 | for arg_name in inputs:
151 | arg = self.args_dict[arg_name]
152 | update(arg['function'], inputs[arg_name])
153 |
154 | self.fea.projectFieldOutput(self.output['form'],self.output['func'])
155 | if self.output['record']:
156 | self.output['recorder'].write_function(self.output['func'],
157 | self.fea.opt_iter)
158 |
159 | outputs[self.output_name] = getFuncArray(self.output['func'])
160 |
--------------------------------------------------------------------------------
/femo/csdl_opt/state_model.py:
--------------------------------------------------------------------------------
1 |
2 | from femo.fea.fea_dolfinx import *
3 | from csdl import Model, CustomImplicitOperation
4 | import csdl
5 | import numpy as np
6 |
7 | class StateModel(Model):
8 |
9 | def initialize(self):
10 | self.parameters.declare('debug_mode', default=False)
11 | self.parameters.declare('fea', types=FEA)
12 | self.parameters.declare('state_name', types=str)
13 | self.parameters.declare('arg_name_list', types=list)
14 |
15 | def define(self):
16 | self.fea = self.parameters['fea']
17 | arg_name_list = self.parameters['arg_name_list']
18 | state_name = self.parameters['state_name']
19 | self.debug_mode = self.parameters['debug_mode']
20 | args_dict = dict()
21 | args_list = []
22 | for arg_name in arg_name_list:
23 | args_dict[arg_name] = self.fea.inputs_dict[arg_name]
24 | # arg = self.declare_variable(arg_name,
25 | # shape=(args_dict[arg_name]['shape'],),
26 | # val=1.0)
27 | arg = self.declare_variable(arg_name,
28 | shape=(args_dict[arg_name]['shape'],),
29 | val=getFuncArray(args_dict[arg_name]['function']))
30 | args_list.append(arg)
31 | self.print_var(arg)
32 |
33 | e = StateOperation(fea=self.fea,
34 | args_dict=args_dict,
35 | state_name=state_name,
36 | debug_mode=self.debug_mode)
37 | state = csdl.custom(*args_list, op=e)
38 | self.register_output(state_name, state)
39 |
40 |
41 | class StateOperation(CustomImplicitOperation):
42 | """
43 | input: input variable
44 | output: state
45 | """
46 | def initialize(self):
47 | self.parameters.declare('debug_mode')
48 | self.parameters.declare('fea')
49 | self.parameters.declare('args_dict')
50 | self.parameters.declare('state_name')
51 |
52 | def define(self):
53 | self.debug_mode = self.parameters['debug_mode']
54 | self.fea = self.parameters['fea']
55 | self.state_name = state_name = self.parameters['state_name']
56 | self.args_dict = args_dict = self.parameters['args_dict']
57 | if self.debug_mode == True:
58 | print(str(self.state_name)+"="*40)
59 | print("CSDL: Running define()...")
60 | print("="*40)
61 |
62 | for arg_name in args_dict:
63 | arg = args_dict[arg_name]
64 | self.add_input(arg_name,
65 | shape=(arg['shape'],),)
66 |
67 | self.state = self.fea.states_dict[state_name]
68 | self.add_output(state_name,
69 | shape=(self.state['shape'],),)
70 | self.declare_derivatives('*', '*')
71 | self.bcs = self.fea.bc
72 | self.linear = self.fea.linear_problem
73 | self.ksp = None
74 |
75 | def evaluate_residuals(self, inputs, outputs, residuals):
76 | if self.debug_mode == True:
77 | print(str(self.state_name)+"="*40)
78 | print("CSDL: Running evaluate_residuals()...")
79 | print("="*40)
80 |
81 | for arg_name in inputs:
82 | arg = self.args_dict[arg_name]
83 | update(arg['function'], inputs[arg_name])
84 | update(self.state['function'], outputs[self.state_name])
85 | residuals[self.state_name] = assembleVector(self.state['residual_form'])
86 |
87 | def solve_residual_equations(self, inputs, outputs):
88 | if self.debug_mode == True:
89 | print(str(self.state_name)+"="*40)
90 | print("CSDL: Running solve_residual_equations()...")
91 | print("="*40)
92 |
93 | self.fea.opt_iter += 1
94 | for arg_name in inputs:
95 | arg = self.args_dict[arg_name]
96 | update(arg['function'], inputs[arg_name])
97 | if arg['record']:
98 | arg['recorder'].write_function(arg['function'],
99 | self.fea.opt_iter)
100 |
101 | update(self.state['function'], outputs[self.state_name])
102 |
103 | self.fea.solve(self.state['residual_form'],
104 | self.state['function'],
105 | self.bcs)
106 |
107 | outputs[self.state_name] = getFuncArray(self.state['function'])
108 | if self.fea.record:
109 | if self.state['function'].function_space.num_sub_spaces > 1:
110 | u_mid,_ = self.state['function'].split()
111 | self.state['recorder'].write_function(u_mid, self.fea.opt_iter)
112 |
113 | else:
114 | self.state['recorder'].write_function(self.state['function'],
115 | self.fea.opt_iter)
116 |
117 | def compute_derivatives(self, inputs, outputs, derivatives):
118 | if self.debug_mode == True:
119 | print(str(self.state_name)+"="*40)
120 | print("CSDL: Running compute_derivatives()...")
121 | print("="*40)
122 |
123 | for arg_name in inputs:
124 | update(self.args_dict[arg_name]['function'], inputs[arg_name])
125 | update(self.state['function'], outputs[self.state_name])
126 |
127 | state = self.state
128 | args_dict = self.args_dict
129 | dR_du = state['dR_du']
130 | if dR_du == None:
131 | dR_du = computePartials(state['residual_form'],state['function'])
132 | self.dRdu = assembleMatrix(dR_du)
133 | dRdf_dict = dict()
134 | dR_df_list = state['dR_df_list']
135 | arg_list = state['arguments']
136 | for arg_ind in range(len(arg_list)):
137 | arg_name = arg_list[arg_ind]
138 | if dR_df_list == None:
139 | dRdf = assembleMatrix(computePartials(
140 | state['residual_form'],
141 | args_dict[arg_name]['function']))
142 | else:
143 | dRdf = dR_df_list[arg_ind]
144 |
145 | df = createFunction(args_dict[arg_name]['function'])
146 | dRdf_dict[arg_name] = dict(dRdf=dRdf, df=df)
147 |
148 | self.dRdf_dict = dRdf_dict
149 | self.A,_ = assembleSystem(dR_du,
150 | state['residual_form'],
151 | bcs=self.bcs)
152 | # self.A,_ = assembleSystem(dR_du,
153 | # state['residual_form'],
154 | # bcs=[])
155 | self.dR = self.state['d_residual']
156 | self.du = self.state['d_state']
157 | if self.linear is True:
158 | self.ksp = setUpKSP_MUMPS(self.A)
159 |
160 |
161 | def compute_jacvec_product(self, inputs, outputs,
162 | d_inputs, d_outputs, d_residuals, mode):
163 | if self.debug_mode == True:
164 | print(str(self.state_name)+"="*40)
165 | print("CSDL: Running compute_jacvec_product()..."+"mode "+str(mode))
166 | print("="*40)
167 |
168 | ######################
169 | # Might be redundant #
170 | for arg_name in inputs:
171 | update(self.args_dict[arg_name]['function'], inputs[arg_name])
172 | update(self.state['function'], outputs[self.state_name])
173 | ######################
174 | state_name = self.state_name
175 | args_dict = self.args_dict
176 | if mode == 'fwd':
177 | if state_name in d_residuals:
178 | if state_name in d_outputs:
179 | update(self.du, d_outputs[state_name])
180 | d_residuals[state_name] += computeMatVecProductFwd(
181 | self.dRdu, self.du)
182 | for arg_name in self.dRdf_dict:
183 | if arg_name in d_inputs:
184 | update(self.dRdf_dict[arg_name]['df'],
185 | d_inputs[arg_name])
186 | dRdf = self.dRdf_dict[arg_name]['dRdf']
187 | d_residuals[state_name] += computeMatVecProductFwd(
188 | dRdf, self.dRdf_dict[arg_name]['df'])
189 |
190 | if mode == 'rev':
191 | if state_name in d_residuals:
192 | update(self.dR, d_residuals[state_name])
193 | if state_name in d_outputs:
194 | d_outputs[state_name] += computeMatVecProductBwd(
195 | self.dRdu, self.dR)
196 | for arg_name in self.dRdf_dict:
197 | if arg_name in d_inputs:
198 | dRdf = self.dRdf_dict[arg_name]['dRdf']
199 | d_inputs[arg_name] += computeMatVecProductBwd(
200 | dRdf, self.dR)
201 |
202 | def apply_inverse_jacobian(self, d_outputs, d_residuals, mode):
203 | if self.debug_mode == True:
204 | print(str(self.state_name)+"="*40)
205 | print("CSDL: Running apply_inverse_jacobian()..."+"mode "+str(mode))
206 | print("="*40)
207 |
208 | state_name = self.state_name
209 | if mode == 'fwd':
210 | d_outputs[state_name] = self.fea.solveLinearFwd(
211 | self.du, self.A, self.dR,
212 | d_residuals[state_name],
213 | self.ksp)
214 | else:
215 | d_residuals[state_name] = self.fea.solveLinearBwd(
216 | self.dR, self.A, self.du,
217 | d_outputs[state_name],
218 | self.ksp)
219 |
--------------------------------------------------------------------------------
/femo/fea/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/femo/fea/fea_dolfinx.py:
--------------------------------------------------------------------------------
1 | """
2 | The FEniCS wrapper for variational forms and partial derivatives computation
3 | """
4 |
5 | from femo.fea.utils_dolfinx import *
6 | from dolfinx.io import XDMFFile
7 | import ufl
8 |
9 | from dolfinx.fem.petsc import apply_lifting
10 | from dolfinx.fem import (set_bc, Function, FunctionSpace, dirichletbc,
11 | locate_dofs_topological, locate_dofs_geometrical,
12 | Constant, VectorFunctionSpace)
13 | from ufl import (grad, SpatialCoordinate, CellDiameter, FacetNormal,
14 | div, Identity)
15 | import matplotlib.pyplot as plt
16 | from scipy.sparse import csr_matrix
17 |
18 | import os.path
19 |
20 |
21 | class AbstractFEA(object):
22 | """
23 | The abstract class of the FEniCS wrapper for defining the variational forms
24 | for PDE residuals and outputs, computing derivatives, and solving
25 | the problems.
26 | """
27 | def __init__(self, **args):
28 |
29 | self.mesh = None
30 | self.sym_nitsche = False
31 | self.initFunctionSpace(self.mesh)
32 | self.res = None
33 |
34 | def __init__(self, mesh):
35 | self.mesh = mesh
36 |
37 | self.inputs_dict = dict()
38 | self.states_dict = dict()
39 | self.outputs_dict = dict()
40 | self.bcs_list = list()
41 |
42 |
43 | def add_strong_bc(self, bc):
44 | self.bcs_list.append(bc)
45 |
46 | def add_input(self, name, function):
47 | if name in self.inputs_dict:
48 | raise ValueError('name has already been used for an input')
49 |
50 | function.rename(name, name)
51 | self.inputs_dict[name] = dict(
52 | function=function,
53 | )
54 |
55 | def add_state(self, name, function, residual_form, *arguments):
56 | function.rename(name, name)
57 | self.states_dict[name] = dict(
58 | function=function,
59 | residual_form=residual_form,
60 | arguments=arguments,
61 | )
62 |
63 | def add_output(self, name, form, *arguments):
64 | self.outputs_dict[name] = dict(
65 | form=form,
66 | arguments=arguments,
67 | )
68 |
69 |
70 | class FEA(object):
71 | """
72 | The class of the FEniCS wrapper for the motor problem,
73 | with methods to compute the variational forms, partial derivatives,
74 | and solve the nonlinear/linear subproblems.
75 | """
76 | def __init__(self, mesh):
77 |
78 | self.mesh = mesh
79 |
80 |
81 | self.inputs_dict = dict()
82 | self.states_dict = dict()
83 | self.outputs_dict = dict()
84 | self.outputs_field_dict = dict()
85 | self.bc = []
86 |
87 | self.PDE_SOLVER = "Newton"
88 | self.REPORT = True
89 |
90 | self.ubc = None
91 | self.custom_solve = None
92 |
93 | self.opt_iter = 0
94 | self.initial_solve = True
95 | self.initialize = False
96 | self.record = False
97 | self.recorder_path = "records"
98 | self.linear_problem = False
99 |
100 | def add_input(self, name, function, init_val=1.0, record=False):
101 | if name in self.inputs_dict:
102 | raise ValueError('name has already been used for an input')
103 | function.x.array[:] = init_val
104 | self.inputs_dict[name] = dict(
105 | function=function,
106 | function_space=function.function_space,
107 | shape=len(getFuncArray(function)),
108 | recorder=self.createRecorder(name, record),
109 | record=record
110 | )
111 |
112 | def add_state(self, name, function, residual_form, arguments,
113 | dR_du=None, dR_df_list=None, record=False):
114 |
115 | self.states_dict[name] = dict(
116 | function=function,
117 | residual_form=residual_form,
118 | function_space=function.function_space,
119 | shape=len(getFuncArray(function)),
120 | d_residual=Function(function.function_space),
121 | d_state=Function(function.function_space),
122 | dR_du=dR_du,
123 | dR_df_list=dR_df_list,
124 | arguments=arguments,
125 | recorder=self.createRecorder(name, record),
126 | record=record
127 | )
128 |
129 | def add_output(self, name, type, form, arguments):
130 | if type == 'field':
131 | shape = len(getFormArray(form))
132 | elif type == 'scalar':
133 | shape = 1
134 | partials = []
135 | for argument in arguments:
136 | if argument in self.inputs_dict:
137 | partial = derivative(form, self.inputs_dict[argument]['function'])
138 | elif argument in self.states_dict:
139 | partial = derivative(form, self.states_dict[argument]['function'])
140 | partials.append(partial)
141 | self.outputs_dict[name] = dict(
142 | form=form,
143 | shape=shape,
144 | arguments=arguments,
145 | partials=partials,
146 | )
147 |
148 | def add_field_output(self, name, form, arguments, record=False):
149 |
150 | V = FunctionSpace(self.mesh, ("CG", 1))
151 | output_func = Function(V)
152 | partials = []
153 | self.outputs_field_dict[name] = dict(
154 | form=form,
155 | func=output_func,
156 | shape=len(getFuncArray(output_func)),
157 | arguments=arguments,
158 | partials=partials,
159 | recorder=self.createRecorder(name, record),
160 | record=record
161 | )
162 |
163 | def add_exact_solution(self, Expression, function_space):
164 | f_analytic = Expression()
165 | f_ex = Function(function_space)
166 | f_ex.interpolate(f_analytic.eval)
167 | return f_ex
168 |
169 | def add_strong_bc(self, ubc, locate_BC_list,
170 | function_space=None):
171 | if function_space == None:
172 | for locate_BC in locate_BC_list:
173 | self.bc.append(dirichletbc(ubc, locate_BC))
174 | else:
175 | for locate_BC in locate_BC_list:
176 | self.bc.append(dirichletbc(ubc, locate_BC, function_space))
177 |
178 | def solve(self, res, func, bc):
179 | """
180 | Solve the PDE problem
181 | """
182 | solver_type=self.PDE_SOLVER
183 | report=self.REPORT
184 | initialize=self.initialize
185 | if self.custom_solve is not None and self.initial_solve == True:
186 | self.custom_solve(res,func,bc,report)
187 | # self.initial_solve = False
188 | else:
189 | solveNonlinear(res,func,bc,solver_type,report,initialize)
190 |
191 |
192 | def solveLinearFwd(self, du, A, dR, dR_array, ksp=None):
193 | """
194 | solve linear system dR = dR_du (A) * du in DOLFIN type
195 | """
196 | setFuncArray(dR, dR_array)
197 |
198 | du.vector.set(0.0)
199 | if ksp is None:
200 | # solveKSP(A, dR.vector, du.vector)
201 | solveKSP_mumps(transpose(A), du.vector, dR.vector)
202 | else:
203 | ksp.solve(du.vector, dR.vector)
204 | du.vector.assemble()
205 | du.vector.ghostUpdate()
206 | return du.vector.getArray()
207 |
208 | def solveLinearBwd(self, dR, A, du, du_array, ksp=None):
209 | """
210 | solve linear system du = dR_du.T (A_T) * dR in DOLFIN type
211 | """
212 | setFuncArray(du, du_array)
213 |
214 | dR.vector.set(0.0)
215 | if ksp is None:
216 | # solveKSP(transpose(A), du.vector, dR.vector)
217 | solveKSP_mumps(transpose(A), du.vector, dR.vector)
218 | else:
219 | ksp.solve(du.vector, dR.vector)
220 | dR.vector.assemble()
221 | dR.vector.ghostUpdate()
222 | return dR.vector.getArray()
223 |
224 | def projectFieldOutput(self,form,func):
225 | project(form, func, lump_mass=False)
226 |
227 |
228 | def createRecorder(self, name, record=False):
229 | recorder = None
230 | if record or self.record:
231 | recorder = XDMFFile(MPI.COMM_WORLD,
232 | self.recorder_path+"/record_"+name+".xdmf", "w")
233 | recorder.write_mesh(self.mesh)
234 | return recorder
235 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Includes the list of all dependencies required to run setup() and use the package
2 | # Also used in testing workflow and to host the documentation on Read the Docs
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import codecs
3 | from setuptools import setup, find_packages
4 |
5 | base_dir = os.path.abspath(os.path.dirname(__file__))
6 | def read(fname):
7 | return codecs.open(os.path.join(base_dir, fname), encoding="utf-8").read()
8 |
9 | setup(
10 | name='femo',
11 | version='0.1',
12 | packages=find_packages(),
13 | #packages=['fe_csdl_opt'],
14 | url='https://github.com/RuruX/femo',
15 | license='GNU LGPLv3',
16 | author='Ru Xiang',
17 | author_email='rxiang@ucsd.edu',
18 | description="Finite Element for Multidisciplinary Optimization",
19 | long_description=read("README.md"),
20 | long_description_content_type="text/markdown",
21 | )
22 |
--------------------------------------------------------------------------------