diff --git a/.coveragerc b/.coveragerc index e9fda4b0..4cb78b98 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,11 +3,8 @@ branch = True source = src omit = - src/f3dasm/simulation/abaqus_script/* - src/f3dasm/experiment/files/* tests/* **/__init__.py - src/f3dasm/machinelearning/* [report] # Regexes for lines to exclude from consideration diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/draft-pdf.yml index b7112755..5932f04c 100644 --- a/.github/workflows/draft-pdf.yml +++ b/.github/workflows/draft-pdf.yml @@ -3,7 +3,12 @@ name: Build JOSS paper on: pull_request: branches: + - "pr/**" - "main" + push: + branches: + - "pr/**" + jobs: paper: diff --git a/README.md b/README.md index 24f88f20..2b234a6d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ f3dasm ------ -*Framework for data-driven design \& analysis of structures and materials* + +
*** @@ -41,31 +42,17 @@ The best way to get started is to follow the [installation instructions](https:/ ## Illustrative benchmarks -This package includes a collection of illustrative benchmark studies that demonstrate the capabilities of the framework. These studies are available in the `/studies` folder, and include the following studies: +This package includes a collection of illustrative benchmark studies that demonstrate the capabilities of the framework. These studies are available in the `/studies/` folder, and include the following studies: - Benchmarking optimization algorithms against well-known benchmark functions - 'Fragile Becomes Supercompressible' ([Bessa et al. (2019)](https://onlinelibrary.wiley.com/doi/full/10.1002/adma.201904845)) ## Authorship -* Current created and developer: [M.P. van der Schelling](https://github.com/mpvanderschelling/) (M.P.vanderSchelling@tudelft.nl) +* Current creator and developer: [M.P. van der Schelling](https://github.com/mpvanderschelling/) (M.P.vanderSchelling@tudelft.nl) The Bessa research group at TU Delft is small... At the moment, we have limited availability to help future users/developers adapting the code to new problems, but we will do our best to help! - - -## Referencing - -If you use or edit our work, please cite at least one of the appropriate references: - -[1] Bessa, M. A., Bostanabad, R., Liu, Z., Hu, A., Apley, D. W., Brinson, C., Chen, W., & Liu, W. K. (2017). A framework for data-driven analysis of materials under uncertainty: Countering the curse of dimensionality. Computer Methods in Applied Mechanics and Engineering, 320, 633-667. - -[2] Bessa, M. A., & Pellegrino, S. (2018). Design of ultra-thin shell structures in the stochastic post-buckling range using Bayesian machine learning and optimization. International Journal of Solids and Structures, 139, 174-188. - -[3] Bessa, M. A., Glowacki, P., & Houlder, M. (2019). Bayesian machine learning in metamaterial design: fragile becomes super-compressible. Advanced Materials, 31(48), 1904845. - -[4] Mojtaba, M., Bostanabad, R., Chen, W., Ehmann, K., Cao, J., & Bessa, M. A. (2019). Deep learning predicts path-dependent plasticity. Proceedings of the National Academy of Sciences, 116(52), 26414-26420. - ## Community Support If you find any **issues, bugs or problems** with this template, please use the [GitHub issue tracker](https://github.com/bessagroup/f3dasm/issues) to report them. diff --git a/VERSION b/VERSION index 721b9931..3e1ad720 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.8 \ No newline at end of file +1.5.0 \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 7789eb74..285b2faf 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,8 +27,8 @@ project = 'f3dasm' author = 'Martin van der Schelling' copyright = '2024, Martin van der Schelling' -version = '1.4.8' -release = '1.4.8' +version = '1.5.0' +release = '1.5.0' # -- General configuration ---------------------------------------------------- @@ -138,4 +138,4 @@ html_css_files = ['readthedocs-custom.css', 'theme_overrides.css', ] # Source: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_show_sourcelink -html_show_sourcelink = True +html_show_sourcelink = False diff --git a/docs/source/img/contour_data.png b/docs/source/img/contour_data.png deleted file mode 100644 index a68058c1..00000000 Binary files a/docs/source/img/contour_data.png and /dev/null differ diff --git a/docs/source/img/contour_samples.png b/docs/source/img/contour_samples.png deleted file mode 100644 index 74aa1fa2..00000000 Binary files a/docs/source/img/contour_samples.png and /dev/null differ diff --git a/docs/source/img/data-driven-process.png b/docs/source/img/data-driven-process.png new file mode 100644 index 00000000..65adf929 Binary files /dev/null and b/docs/source/img/data-driven-process.png differ diff --git a/docs/source/img/f3dasm-blocks.svg b/docs/source/img/f3dasm-blocks.svg deleted file mode 100644 index e18e6e43..00000000 --- a/docs/source/img/f3dasm-blocks.svg +++ /dev/null @@ -1,1870 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - Design - - - - - - - - - - - Design of - - - - - - - experiments - - - - - - - - - - Continuous - - - - - - - Categorical - - - - - - - Discrete - - - - - - - - - - - Parameter - - - - - - - - - Machine - - - - - - - Learning - - - - - - - - - - - Model - - - - - - - - - - Regressor - - - - - - - Neural net - - - - - - - Obj. functions - - - - - - - - - Sampling - - - - - - - - - - - Sampler - - - - - - - - - - LHS - - - - - - - - - - - - - - - sampling - - - - - - - Sobol sequence - - - - - - - Random uniform - - - - - - - - - - - - 1. Design of experiments - - - - - - - - - Optimization - - - - - - - - - - - Optimizer - - - - - - - - - - Gradient - - - - - - - - - - - - - - - based - - - - - - - Gradient - - - - - - - - - - - - - - - free - - - - - - - L2O - - - - - - - 3. Machine learning - - - - - - - - - Simulation - - - - - - - - - - - Simulator - - - - - - - - - - Abaqus - - - - - - - Fenics - - - - - - - - - - - - 2. Data generation - - - - - - - - - - - Loss function - - - - - - - - Run optimization on - - - - - - - benchmark functions - - - - - - - - Abaqus simulation on - - - - - - - Flower RVE - - - - - - - - - - - - - - - - - block - - - - - - - base - - - - - - - implementation - - - - - - - - experiment - - - - - - - - - - - - Data loader - - - - - - - - - - - - - - - - - - - - - Meta - - - - - - - - - - - - - - - optimizer - - - - - - - 4. Optimization - - - - - diff --git a/docs/source/img/f3dasm-cluster.png b/docs/source/img/f3dasm-cluster.png deleted file mode 100644 index ca050f13..00000000 Binary files a/docs/source/img/f3dasm-cluster.png and /dev/null differ diff --git a/docs/source/img/f3dasm-datageneration.png b/docs/source/img/f3dasm-datageneration.png deleted file mode 100644 index 253cfb70..00000000 Binary files a/docs/source/img/f3dasm-datageneration.png and /dev/null differ diff --git a/docs/source/img/f3dasm-design.png b/docs/source/img/f3dasm-design.png deleted file mode 100644 index 9b59b324..00000000 Binary files a/docs/source/img/f3dasm-design.png and /dev/null differ diff --git a/docs/source/img/f3dasm-domain.png b/docs/source/img/f3dasm-domain.png deleted file mode 100644 index d3ad3227..00000000 Binary files a/docs/source/img/f3dasm-domain.png and /dev/null differ diff --git a/docs/source/img/f3dasm-experimentdata.png b/docs/source/img/f3dasm-experimentdata.png deleted file mode 100644 index bda03a99..00000000 Binary files a/docs/source/img/f3dasm-experimentdata.png and /dev/null differ diff --git a/docs/source/img/f3dasm-logo.png b/docs/source/img/f3dasm-logo.png deleted file mode 100644 index ebbe2b0e..00000000 Binary files a/docs/source/img/f3dasm-logo.png and /dev/null differ diff --git a/docs/source/img/f3dasm-logo.svg b/docs/source/img/f3dasm-logo.svg deleted file mode 100644 index c960c424..00000000 --- a/docs/source/img/f3dasm-logo.svg +++ /dev/null @@ -1,527 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - F3DASM - Framework for Data-driven Design and Analysis of Structures and Materials - - - - - - - - - - diff --git a/docs/source/img/f3dasm-optimizer.png b/docs/source/img/f3dasm-optimizer.png deleted file mode 100644 index 83ffb1b4..00000000 Binary files a/docs/source/img/f3dasm-optimizer.png and /dev/null differ diff --git a/docs/source/img/f3dasm-parameter.png b/docs/source/img/f3dasm-parameter.png deleted file mode 100644 index c257d228..00000000 Binary files a/docs/source/img/f3dasm-parameter.png and /dev/null differ diff --git a/docs/source/img/f3dasm_logo_long.png b/docs/source/img/f3dasm_logo_long.png new file mode 100644 index 00000000..369cfadb Binary files /dev/null and b/docs/source/img/f3dasm_logo_long.png differ diff --git a/docs/source/img/performance.png b/docs/source/img/performance.png deleted file mode 100644 index dbc015cb..00000000 Binary files a/docs/source/img/performance.png and /dev/null differ diff --git a/docs/source/img/samples.png b/docs/source/img/samples.png deleted file mode 100644 index fa09eb76..00000000 Binary files a/docs/source/img/samples.png and /dev/null differ diff --git a/docs/source/index.rst b/docs/source/index.rst index f501388c..37373255 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,17 +6,41 @@ f3dasm ====== + .. toctree:: - :maxdepth: 1 + :name: gettingstartedtoc + :caption: Getting started + :maxdepth: 3 :hidden: - + :includehidden: + rst_doc_files/general/overview rst_doc_files/general/installation - rst_doc_files/defaults + rst_doc_files/general/benchmark + +.. toctree:: + :name: functionalitiestoc + :caption: Functionalities + :maxdepth: 3 + :hidden: + :includehidden: + auto_examples/index + rst_doc_files/defaults + +.. toctree:: + :name: apitoc + :caption: API + :hidden: + + rst_doc_files/reference/index.rst API reference <_autosummary/f3dasm> .. toctree:: + :name: licensetoc + :caption: License :hidden: + license.rst + .. include:: readme.rst \ No newline at end of file diff --git a/docs/source/license.rst b/docs/source/license.rst new file mode 100644 index 00000000..cb7ae73f --- /dev/null +++ b/docs/source/license.rst @@ -0,0 +1,33 @@ +.. _license: + +BSD 3-Clause License +==================== + +Copyright (c) 2022, Martin van der Schelling + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/source/readme.rst b/docs/source/readme.rst index f823774b..2fa4d79a 100644 --- a/docs/source/readme.rst +++ b/docs/source/readme.rst @@ -1,114 +1,46 @@ -Welcome to the documentation page of the 'Framework for Data-Driven Design and Analysis of Structures and Materials'. -Here you will find all information on installing, using and contributing to the Python package. +.. image:: ./img/f3dasm_logo_long.png + :align: center + :width: 70% -Basic Concepts --------------- +| +Summary +------- **f3dasm** introduces a general and user-friendly data-driven Python package for researchers and practitioners working on design and analysis of materials and structures. -Some of the key features of are: - -- Modular design - - - The framework introduces flexible interfaces, allowing users to easily integrate their own models and algorithms. - -- Automatic data management - - - the framework automatically manages I/O processes, saving you time and effort implementing these common procedures. - -- :doc:`Easy parallelization ` - - - the framework manages parallelization of experiments, and is compatible with both local and high-performance cluster computing. - -- :doc:`Built-in defaults ` - - - The framework includes a collection of :ref:`benchmark functions `, :ref:`optimization algorithms ` and :ref:`sampling strategies ` to get you started right away! -- :doc:`Hydra integration ` - - The framework is integrated with `hydra `_ configuration manager, to easily manage and run experiments. +.. image:: ./img/data-driven-process.png + :align: center + :width: 100% - -.. .. include:: auto_examples/001_domain/index.rst - -.. .. include:: auto_examples/002_experimentdata/index.rst - -.. .. include:: auto_examples/003_datageneration/index.rst - -.. .. include:: auto_examples/004_optimization/index.rst +---- Getting started --------------- - The best way to get started is to: * Read the :ref:`overview` section, containing a brief introduction to the framework and a statement of need. * Follow the :ref:`installation-instructions` to get going! * Check out the :ref:`examples` section, containing a collection of examples to get you familiar with the framework. +---- + Authorship & Citation --------------------- - :mod:`f3dasm` is created and maintained by Martin van der Schelling [1]_. .. [1] PhD Candiate, Delft University of Technology, `Website `_ , `GitHub `_ -.. If you use :mod:`f3dasm` in your research or in a scientific publication, it is appreciated that you cite the paper below: - -.. **Computer Methods in Applied Mechanics and Engineering** (`paper `_): - -.. .. code-block:: tex - -.. @article{Bessa2017, -.. title={A framework for data-driven analysis of materials under uncertainty: Countering the curse of dimensionality}, -.. author={Bessa, Miguel A and Bostanabad, Ramin and Liu, Zeliang and Hu, Anqi and Apley, Daniel W and Brinson, Catherine and Chen, Wei and Liu, Wing Kam}, -.. journal={Computer Methods in Applied Mechanics and Engineering}, -.. volume={320}, -.. pages={633--667}, -.. year={2017}, -.. publisher={Elsevier} -.. } - - -.. Statement of Need -.. ----------------- - -.. The use of state-of-the-art machine learning tools for innovative structural and materials design has demonstrated their potential in various studies. -.. Although the specific applications may differ, the data-driven modelling and optimization process remains the same. -.. Therefore, the framework for data-driven design and analysis of structures and materials (:mod:`f3dasm`) is an attempt to develop a systematic approach of inverting the material design process. - - -.. The framework, originally proposed by Bessa et al. [3]_ integrates the following fields: - -.. - **Design \& Sampling**, in which input variables describing the microstructure, structure, properties and external conditions of the system to be evaluated are determined and sampled. -.. - **Simulation**, typically through computational analysis, resulting in the creation of a material response database. -.. - **Machine learning**, in which a surrogate model is trained to fit experimental findings. -.. - **Optimization**, where we try to iteratively improve the model to obtain a superior design. - -.. The effectiveness of the first published version of :mod:`f3dasm` framework has been demonstrated in various computational mechanics and materials studies, -.. such as the design of a super-compressible meta-material [4]_ and a spiderweb nano-mechanical resonator inspired -.. by nature and guided by machine learning [5]_. - -.. .. [3] Bessa, M. A., Bostanabad, R., Liu, Z., Hu, A., Apley, D. W., Brinson, C., Chen, W., & Liu, W. K. (2017). -.. *A framework for data-driven analysis of materials under uncertainty: Countering the curse of dimensionality. -.. Computer Methods in Applied Mechanics and Engineering*, 320, 633-667. - -.. .. [4] Bessa, M. A., Glowacki, P., & Houlder, M. (2019). -.. *Bayesian machine learning in metamaterial design: -.. Fragile becomes supercompressible*. Advanced Materials, 31(48), 1904845. - -.. .. [5] Shin, D., Cupertino, A., de Jong, M. H., Steeneken, P. G., Bessa, M. A., & Norte, R. A. (2022). -.. *Spiderweb nanomechanical resonators via bayesian optimization: inspired by nature and guided by machine learning*. Advanced Materials, 34(3), 2106248. - +---- Contribute ---------- - :mod:`f3dasm` is an open-source project, and contributions of any kind are welcome and appreciated. If you want to contribute, please go to the `GitHub wiki page `_. +---- Useful links ------------ - * `GitHub repository `_ (source code) * `Wiki for development `_ * `PyPI package `_ (distribution package) @@ -116,8 +48,8 @@ Useful links Related extension libraries --------------------------- * `f3dasm_optimize `_: Optimization algorithms for the :mod:`f3dasm` package. -.. * `f3dasm_simulate `_: Simulators for the :mod:`f3dasm` package. -.. * `f3dasm_teach `_: Hub for practical session and educational material on using :mod:`f3dasm`. + +---- License ------- @@ -125,4 +57,4 @@ Copyright 2024, Martin van der Schelling All rights reserved. -:mod:`f3dasm` is a free and open-source software published under a `BSD 3-Clause License `_. +:mod:`f3dasm` is a free and open-source software published under a :doc:`BSD 3-Clause License <./license>`. diff --git a/docs/source/rst_doc_files/classes/datageneration/datagenerator.rst b/docs/source/rst_doc_files/classes/datageneration/datagenerator.rst deleted file mode 100644 index 0d82c3e2..00000000 --- a/docs/source/rst_doc_files/classes/datageneration/datagenerator.rst +++ /dev/null @@ -1,109 +0,0 @@ -.. _data-generation: - -Datagenerator -============= - -The :class:`~f3dasm.datageneration.DataGenerator` class is the main class of the :mod:`~f3dasm.datageneration` module. -It is used to generate :attr:`~f3dasm.ExperimentData.output_data` for the :class:`~f3dasm.ExperimentData` by taking a :class:`~f3dasm.ExperimentSample` object. - -The :class:`~f3dasm.datageneration.DataGenerator` can serve as the interface between the -:class:`~f3dasm.ExperimentData` object and any third-party simulation software. - -.. image:: ../../../img/f3dasm-datageneration.png - :width: 70% - :align: center - :alt: DataGenerator - -| - -Use the simulator in the data-driven process --------------------------------------------- - -In order to run your simulator on each of the :class:`~f3dasm.ExperimentSample` of your :class:`~f3dasm.ExperimentData`, you follow these steps: -In this case, we are utilizing a one of the :ref:`benchmark-functions` to mock a simulator. - -We provide the datagenerator to the :meth:`~f3dasm.ExperimentData.evaluate` function with the :class:`~f3dasm.datageneration.DataGenerator` object as an argument. - - .. code-block:: python - - experimentdata.evaluate(data_generator="Ackley", method='sequential', kwargs={'some_additional_parameter': 1}) - -.. note:: - - Any key-word arguments that need to be passed down to the :class:`~f3dasm.datageneration.DataGenerator` can be passed in the :code:`kwargs` argument of the :meth:`~f3dasm.ExperimentData.evaluate` function. - - -There are three methods available of handling the :class:`~f3dasm.ExperimentSample` objects: - -* :code:`sequential`: regular for-loop over each of the :class:`~f3dasm.ExperimentSample` objects in order -* :code:`parallel`: utilizing the multiprocessing capabilities (with the `pathos `_ multiprocessing library), each :class:`~f3dasm.ExperimentSample` object is run in a separate core -* :code:`cluster`: utilizing the multiprocessing capabilities, each :class:`~f3dasm.ExperimentSample` object is run in a separate node. After completion of an sample, the node will automatically pick the next available sample. More information on this mode can be found in the :ref:`cluster-mode` section. -* :code:`cluster_parallel`: Combination of the :code:`cluster` and :code:`parallel` mode. Each node will run multiple samples in parallel. - -Implement your simulator -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. _data-generation-function: - -In order to implement your simulator in :mod:`f3dasm`, you need to follow these steps: - -1. Create a new class, inheriting from the :class:`~f3dasm.datageneration.DataGenerator` class. -2. Implement the :meth:`~f3dasm.datageneration.DataGenerator.execute` method. This method should not have any arguments (apart from ``self``) and should submit a script with the name of the current ``job_number`` to the simulator. - -.. note:: - - The :class:`~f3dasm.datageneration.DataGenerator` class has access to the current design through the ``self.experiment_sample`` attribute. - You can retrieve the ``job_number`` of the current design by calling ``self.experiment_sample.job_number``. - - - -Setting up the pre-processing and post-processing benchmark-functions ---------------------------------------------------------------------- - -Once you have created the ``data_generator`` object, you can plug-in a pre-processing and post-processing method: - - -pre-processing -^^^^^^^^^^^^^^ -The preprocessing method is used to create a simulator input file from the information in the :class:`~f3dasm.ExperimentSample`. - - -This method should adhere to a few things: - -* The first argument of the function needs to be ``experiment_sample`` of type :class:`~f3dasm.ExperimentSample`. -* The method should return None. -* The method should create the input file ready for the simulator to process with the job_number as name (``experiment_sample.job_number``) - -You can retrieve the parameters of the :class:`~f3dasm.ExperimentSample` object by calling the :meth:`~f3dasm.ExperimentSample.get` method. - -You can add the ``pre-process-function`` to the :class:`~f3dasm.datageneration.DataGenerator` object by passing it through the :meth:`~f3dasm.datageneration.DataGenerator.add_pre_process` method: - -.. code-block:: python - - experimentdata.add_pre_process(pre_process_function) - -.. note:: - - You can add any additional key-word arguments to the :meth:`~f3dasm.datageneration.DataGenerator.add_pre_process` method, which will be passed down to the :meth:`~f3dasm.datageneration.DataGenerator.pre_process` method. - - -post-processing -^^^^^^^^^^^^^^^ - -The post-processing method converts the output of the simulator to a ``results.pkl`` `pickle `_ file. -This ``results.pkl`` is then loaded into the :class:`~f3dasm.ExperimentData` object. - -This method should adhere to a few things: - -* The first argument of the function needs to be ``experiment_sample`` of type :class:`~f3dasm.ExperimentSample`. -* The method should return None. -* The method read the output of the simulator (it's name is ``experiment_sample.job_number``) and convert it to a ``results.pkl`` file. -* This pickle file is stored in the current working directory. - -You can add the ``post-process-function`` to the :class:`~f3dasm.datageneration.DataGenerator` object by passing it through the :meth:`~f3dasm.datageneration.DataGenerator.add_post_process` method: - -.. code-block:: python - - experimentdata.add_post_process(pre_process_function) - -.. include:: ../../../auto_examples/003_datageneration/index.rst \ No newline at end of file diff --git a/docs/source/rst_doc_files/classes/datageneration/f3dasm-simulate.rst b/docs/source/rst_doc_files/classes/datageneration/f3dasm-simulate.rst deleted file mode 100644 index 0534b1d1..00000000 --- a/docs/source/rst_doc_files/classes/datageneration/f3dasm-simulate.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _f3dasm-simulate: - -:code:`f3dasm-simulate` -======================= - -Extend the Simulation capabilities ------------------------------------- - -The :mod:`f3dasm.datageneration` module is designed to be easily extended by third-party libraries. -In order to not bloat the main :mod:`f3dasm` package, these extensions are provided as separate package: `f3dasm_simulate `_. - -More ports to simulation software are available in the `f3dasm_simulate `_ package, which can be installed via pip: - -.. code-block:: bash - - pip install f3dasm_simulate - -More information about this extension can be found in the `f3dasm_simulate documentation page `_ \ No newline at end of file diff --git a/docs/source/rst_doc_files/classes/datageneration/functions.rst b/docs/source/rst_doc_files/classes/datageneration/functions.rst deleted file mode 100644 index 1858ca4b..00000000 --- a/docs/source/rst_doc_files/classes/datageneration/functions.rst +++ /dev/null @@ -1,163 +0,0 @@ -.. _benchmark-functions: - -Benchmark functions -=================== - -:mod:`f3dasm` comes with a set of benchmark functions that can be used to test the performance of -optimization algorithms or to mock some expensive simulation in order to test the workflow - - -.. note:: - - The gradients of the benchmark functions are computed with the automated differentiation package `autograd `_. - - -In order to augment the benchmark functions, you can provide 4 keyword arguments to the :class:`~f3dasm.design.ExperimentData.evaluate` method: - -* ``scale_bounds``: A 2D list of float that define the scaling lower and upper boundaries for each dimension. The normal benchmark function box-constraints will be scaled to these boundaries. -* ``noise``: A float that defines the standard deviation of the Gaussian noise that is added to the objective value. -* ``offset``: A boolean value. If ``True``, the benchmark function will be offset by a constant vector that will be randomly generated. -* ``seed``: Seed for the random number generator for the ``noise`` and ``offset`` calculations. - -Benchmark functions can substitute the expensive simulation in the -:class:`~f3dasm.ExperimentData` object by providing the name of the function as the ``data_generator`` argument: - -.. code-block:: python - - from f3dasm.design import ExperimentData, Domain - import numpy as np - - domain = Domain(...) - # Create the ExperimentData object - experiment_data = ExperimentData.from_sampling('random', domain=domain, n_samples=10, seed=42) - - # Evaluate the Ackley function with noise, offset and scale_bounds - experiment_data.evaluate('Ackley', kwargs={'noise': 0.1, 'scale_bounds': [[0., 1.], [-1., 1.]], 'offset': True, 'seed': 42}) - - -.. _implemented-benchmark-functions: - -Implemented benchmark functions -------------------------------- - -These benchmark functions are taken and modified from the `Python Benchmark Test Optimization Function Single Objective `_ github repository. -The following implementations of benchmark functions can instantiated with the name in the 'Data-generator argument' column. - -.. note:: - - Not all benchmark functions are implemented for all dimensions. - If you want to use a benchmark function for a dimension that is not implemented, you will get a :class:`~NotImplementedError`. - -Convex functions -^^^^^^^^^^^^^^^^ - -======================== ====================================================== =========================== -Name Docs of the Python class Data-generator argument -======================== ====================================================== =========================== -Ackley N. 2 :class:`~f3dasm.datageneration.AckleyN2` ``"Ackley N. 2"`` -Bohachevsky N. 1 :class:`~f3dasm.datageneration.BohachevskyN1` ``"Bohachevsky N. 1"`` -Booth :class:`~f3dasm.datageneration.Booth` ``"Booth"`` -Brent :class:`~f3dasm.datageneration.Brent` ``"Brent"`` -Brown :class:`~f3dasm.datageneration.Brown` ``"Brown"`` -Bukin N. 6 :class:`~f3dasm.datageneration.BukinN6` ``"Bukin N. 6"`` -Dixon Price :class:`~f3dasm.datageneration.DixonPrice` ``"Dixon Price"`` -Exponential :class:`~f3dasm.datageneration.Exponential` ``"Exponential"`` -Matyas :class:`~f3dasm.datageneration.Matyas` ``"Matyas"`` -McCormick :class:`~f3dasm.datageneration.McCormick` ``"McCormick"`` -Perm 0, d, beta :class:`~f3dasm.datageneration.PermZeroDBeta` ``"Perm 0, d, beta"`` -Powell :class:`~f3dasm.datageneration.Powell` ``"Powell"`` -Rotated Hyper-Ellipsoid :class:`~f3dasm.datageneration.RotatedHyperEllipsoid` ``"Rotated Hyper-Ellipsoid"`` -Schwefel 2.20 :class:`~f3dasm.datageneration.Schwefel2_20` ``"Schwefel 2.20"`` -Schwefel 2.21 :class:`~f3dasm.datageneration.Schwefel2_21` ``"Schwefel 2.21"`` -Schwefel 2.22 :class:`~f3dasm.datageneration.Schwefel2_22` ``"Schwefel 2.22"`` -Schwefel 2.23 :class:`~f3dasm.datageneration.Schwefel2_23` ``"Schwefel 2.23"`` -Sphere :class:`~f3dasm.datageneration.Sphere` ``"Sphere"`` -Sum Squares :class:`~f3dasm.datageneration.SumSquares` ``"Sum Squares"`` -Thevenot :class:`~f3dasm.datageneration.Thevenot` ``"Thevenot"`` -Trid :class:`~f3dasm.datageneration.Trid` ``"Trid"`` -Xin She Yang N.3 :class:`~f3dasm.datageneration.XinSheYangN3` ``"Xin She Yang N.3"`` -Xin-She Yang N.4 :class:`~f3dasm.datageneration.XinSheYangN4` ``"Xin-She Yang N.4"`` -======================== ====================================================== =========================== - - - -Seperable functions -^^^^^^^^^^^^^^^^^^^ - -======================== ============================================== ============================ -Name Docs of the Python class Data-generator argument -======================== ============================================== ============================ -Ackley :class:`~f3dasm.datageneration.Ackley` ``"Ackley"`` -Bohachevsky N. 1 :class:`~f3dasm.datageneration.BohachevskyN1` ``"Bohachevsky N. 1"`` -Easom :class:`~f3dasm.datageneration.Easom` ``"Easom"`` -Egg Crate :class:`~f3dasm.datageneration.EggCrate` ``"Egg Crate"`` -Exponential :class:`~f3dasm.datageneration.Exponential` ``"Exponential"`` -Griewank :class:`~f3dasm.datageneration.Griewank` ``"Griewank"`` -Michalewicz :class:`~f3dasm.datageneration.Michalewicz` ``"Michalewicz"`` -Powell :class:`~f3dasm.datageneration.Powell` ``"Powell"`` -Qing :class:`~f3dasm.datageneration.Qing` ``"Qing"`` -Quartic :class:`~f3dasm.datageneration.Quartic` ``"Quartic"`` -Rastrigin :class:`~f3dasm.datageneration.Rastrigin` ``"Rastrigin"`` -Schwefel :class:`~f3dasm.datageneration.Schwefel` ``"Schwefel"`` -Schwefel 2.20 :class:`~f3dasm.datageneration.Schwefel2_20` ``"Schwefel 2.20"`` -Schwefel 2.21 :class:`~f3dasm.datageneration.Schwefel2_21` ``"Schwefel 2.21"`` -Schwefel 2.22 :class:`~f3dasm.datageneration.Schwefel2_22` ``"Schwefel 2.22"`` -Schwefel 2.23 :class:`~f3dasm.datageneration.Schwefel2_23` ``"Schwefel 2.23"`` -Sphere :class:`~f3dasm.datageneration.Sphere` ``"Sphere"`` -Styblinski Tank :class:`~f3dasm.datageneration.StyblinskiTank` ``"Styblinski Tank"`` -Sum Squares :class:`~f3dasm.datageneration.SumSquares` ``"Sum Squares"`` -Thevenot :class:`~f3dasm.datageneration.Thevenot` ``"Thevenot"`` -Xin She Yang :class:`~f3dasm.datageneration.XinSheYang` ``"Xin She Yang"`` -======================== ============================================== ============================ - -Multimodal functions -^^^^^^^^^^^^^^^^^^^^ - -======================== ================================================ ========================== -Name Docs of the Python class Data-generator argument -======================== ================================================ ========================== -Ackley :class:`~f3dasm.datageneration.Ackley` ``"Ackley"`` -Ackley N. 3 :class:`~f3dasm.datageneration.AckleyN3` ``"Ackley N. 3"`` -Ackley N. 4 :class:`~f3dasm.datageneration.AckleyN4` ``"Ackley N. 4"`` -Adjiman :class:`~f3dasm.datageneration.Adjiman` ``"Adjiman"`` -Bartels :class:`~f3dasm.datageneration.Bartels` ``"Bartels"`` -Beale :class:`~f3dasm.datageneration.Beale` ``"Beale"`` -Bird :class:`~f3dasm.datageneration.Bird` ``"Bird"`` -Bohachevsky N. 2 :class:`~f3dasm.datageneration.BohachevskyN2` ``"Bohachevsky N. 2"`` -Bohachevsky N. 3 :class:`~f3dasm.datageneration.BohachevskyN3` ``"Bohachevsky N. 3"`` -Branin :class:`~f3dasm.datageneration.Branin` ``"Branin"`` -Bukin N. 6 :class:`~f3dasm.datageneration.BukinN6` ``"Bukin N. 6"`` -Colville :class:`~f3dasm.datageneration.Colville` ``"Colville"`` -Cross-in-Tray :class:`~f3dasm.datageneration.CrossInTray` ``"Cross-in-Tray"`` -De Jong N. 5 :class:`~f3dasm.datageneration.DeJongN5` ``"De Jong N. 5"`` -Deckkers-Aarts :class:`~f3dasm.datageneration.DeckkersAarts` ``"Deckkers-Aarts"`` -Easom :class:`~f3dasm.datageneration.Easom` ``"Easom"`` -Egg Crate :class:`~f3dasm.datageneration.EggCrate` ``"Egg Crate"`` -Egg Holder :class:`~f3dasm.datageneration.EggHolder` ``"Egg Holder"`` -Goldstein-Price :class:`~f3dasm.datageneration.GoldsteinPrice` ``"Goldstein-Price"`` -Happy Cat :class:`~f3dasm.datageneration.HappyCat` ``"Happy Cat"`` -Himmelblau :class:`~f3dasm.datageneration.Himmelblau` ``"Himmelblau"`` -Holder-Table :class:`~f3dasm.datageneration.HolderTable` ``"Holder-Table"`` -Keane :class:`~f3dasm.datageneration.Keane` ``"Keane"`` -Langermann :class:`~f3dasm.datageneration.Langermann` ``"Langermann"`` -Levy :class:`~f3dasm.datageneration.Levy` ``"Levy"`` -Levy N. 13 :class:`~f3dasm.datageneration.LevyN13` ``"Levy N. 13"`` -McCormick :class:`~f3dasm.datageneration.McCormick` ``"McCormick"`` -Michalewicz :class:`~f3dasm.datageneration.Michalewicz` ``"Michalewicz"`` -Periodic :class:`~f3dasm.datageneration.Periodic` ``"Periodic"`` -Perm d, beta :class:`~f3dasm.datageneration.PermDBeta` ``"Perm d, beta"`` -Qing :class:`~f3dasm.datageneration.Qing` ``"Qing"`` -Quartic :class:`~f3dasm.datageneration.Quartic` ``"Quartic"`` -Rastrigin :class:`~f3dasm.datageneration.Rastrigin` ``"Rastrigin"`` -Rosenbrock :class:`~f3dasm.datageneration.Rosenbrock` ``"Rosenbrock"`` -Salomon :class:`~f3dasm.datageneration.Salomon` ``"Salomon"`` -Schwefel :class:`~f3dasm.datageneration.Schwefel` ``"Schwefel"`` -Shekel :class:`~f3dasm.datageneration.Shekel` ``"Shekel"`` -Shubert :class:`~f3dasm.datageneration.Shubert` ``"Shubert"`` -Shubert N. 3 :class:`~f3dasm.datageneration.ShubertN3` ``"Shubert N. 3"`` -Shubert N. 4 :class:`~f3dasm.datageneration.ShubertN4` ``"Shubert N. 4"`` -Styblinski Tank :class:`~f3dasm.datageneration.StyblinskiTank` ``"Styblinski Tank"`` -Thevenot :class:`~f3dasm.datageneration.Thevenot` ``"Thevenot"`` -Xin She Yang :class:`~f3dasm.datageneration.XinSheYang` ``"Xin She Yang"`` -Xin She Yang N.2 :class:`~f3dasm.datageneration.XinSheYangN2` ``"Xin She Yang N.2"`` -======================== ================================================ ========================== diff --git a/docs/source/rst_doc_files/classes/design/domain.rst b/docs/source/rst_doc_files/classes/design/domain.rst deleted file mode 100644 index add74685..00000000 --- a/docs/source/rst_doc_files/classes/design/domain.rst +++ /dev/null @@ -1,171 +0,0 @@ -Domain and parameters -===================== - -This section will give you information on how to set up your search space with the :ref:`domain ` class and the :ref:`parameters ` - - -Domain ------- - -.. _domain: - -The :class:`~f3dasm.design.Domain` is a set of parameter instances that make up the feasible search space. - -.. image:: ../../../img/f3dasm-domain.png - :width: 100% - :align: center - :alt: Domain - -| - - -To start, we create an empty domain object: - -.. code-block:: python - - from f3dasm.design import Domain - - domain = Domain() - - -Now we can add some parameters! - -.. _parameters: - -Input parameters ----------------- - -Input parameters are singular features of the input search space. They are used to define the search space of the design. - -.. image:: ../../../img/f3dasm-parameter.png - :width: 50% - :align: center - :alt: Parameters - -| - -There are four types of parameters that can be created: :ref:`float `, :ref:`int `, :ref:`categorical ` and :ref:`constant ` parameters. - -.. _continuous-parameter: - -Floating point parameters -^^^^^^^^^^^^^^^^^^^^^^^^^ - -* We can create **continous** parameters with a lower bound (:code:`low`) and upper bound (:code:`high`) with the :meth:`~f3dasm.design.Domain.add_float` method: - -.. code-block:: python - - domain.add_float(name='x1', low=0.0, high=100.0) - domain.add_float(name='x2', low=0.0, high=4.0) - -An optional argument :code:`log` can be set to :code:`True` to create a log-scaled parameter: - -.. _discrete-parameter: - -Discrete parameters -^^^^^^^^^^^^^^^^^^^ - -* We can create **discrete** parameters with a lower bound (:code:`low`) and upper bound (:code:`high`) with the :meth:`~f3dasm.design.Domain.add_int` method: - -.. code-block:: python - - domain.add_int(name='x3', low=2, high=4) - domain.add_int(name='x4', low=74, high=99) - -An optional argument :code:`step` can be set to an integer value to define the step size between the lower and upper bound. By default the step size is 1. - -.. _categorical-parameter: - -Categorical parameters -^^^^^^^^^^^^^^^^^^^^^^ - -* We can create **categorical** parameters with a list of values (:code:`categories`) with the :meth:`~f3dasm.design.Domain.add_category` method: - -.. code-block:: python - - domain.add_category(name='x5', categories=['test1','test2','test3','test4']) - domain.add_category(name='x6', categories=[0.9, 0.2, 0.1, -2]) - -.. _constant-parameter: - -Constant parameters -^^^^^^^^^^^^^^^^^^^ - -* We can create **constant** parameters with any value (:code:`value`) with the :meth:`~f3dasm.design.Domain.add_constant` method: - -.. code-block:: python - - domain.add_constant(name='x7', value=0.9) - -.. _domain-from-yaml: - -Output parameters ------------------ - -Output parameters are the results of evaluating the input design with a data generation model. -Output parameters can hold any type of data, e.g. a scalar value, a vector, a matrix, etc. -Normally, you would not need to define output parameters, as they are created automatically when you store a variable to the :class:`~f3dasm.ExperimentData` object. - -.. code-block:: python - - domain.add_output(name='y', to_disk=False) - -The :code:`to_disk` argument can be set to :code:`True` to store the output parameter on disk. A reference to the file is stored in the :class:`~f3dasm.ExperimentData` object. -This is useful when the output data is very large, or when the output data is an array-like object. -More information on storing output can be found in :ref:`this section ` - -Domain from a `hydra `_ configuration file -------------------------------------------------------------- - -If you are using `hydra `_ to manage your configuration files, you can create a domain from a configuration file. -Your config needs to have a key (e.g. :code:`domain`) that has a dictionary with the parameter names (e.g. :code:`param_1`) as keys -and a dictionary with the parameter type (:code:`type`) and the corresponding arguments as values: - -.. code-block:: yaml - :caption: config.yaml - - domain: - param_1: - type: float - low: -1.0 - high: 1.0 - param_2: - type: int - low: 1 - high: 10 - param_3: - type: category - categories: ['red', 'blue', 'green', 'yellow', 'purple'] - param_4: - type: constant - value: some_value - - -The domain can now be created by calling the :func:`~f3dasm.design.Domain.from_yaml` method: - -.. code-block:: python - - import hydra - - @hydra.main(config_path="conf", config_name="config") - def my_app(cfg): - domain = Domain.from_yaml(cfg.domain) - -Helper function for single-objective, n-dimensional continuous domains ----------------------------------------------------------------------- - -We can make easily make a :math:`n`-dimensional continous domain with the helper function :func:`~f3dasm.design.make_nd_continuous_domain`. -We have to specify the boundaries (``bounds``) for each of the dimensions with a list of lists or numpy :class:`~numpy.ndarray`: - -.. code-block:: python - - from f3dasm.design import make_nd_continuous_domain - import numpy as np - bounds = np.array([[-1.0, 1.0], [-1.0, 1.0]]) - domain = make_nd_continuous_domain(bounds=bounds, dimensionality=2) - - -.. .. minigallery:: f3dasm.design.Domain -.. :add-heading: Examples using the `Domain` object -.. :heading-level: - - diff --git a/docs/source/rst_doc_files/classes/design/experimentdata.rst b/docs/source/rst_doc_files/classes/design/experimentdata.rst deleted file mode 100644 index 2da768a1..00000000 --- a/docs/source/rst_doc_files/classes/design/experimentdata.rst +++ /dev/null @@ -1,401 +0,0 @@ -Experiment Data -=============== - -The :class:`~f3dasm.ExperimentData` object is the main object used to store implementations of a design-of-experiments, -keep track of results, perform optimization and extract data for machine learning purposes. - -All other processses of :mod:`f3dasm` use this object to manipulate and access data about your experiments. - -The :class:`~f3dasm.ExperimentData` object consists of the following attributes: - -- :ref:`domain `: The feasible :class:`~f3dasm.design.Domain` of the Experiment. Used for sampling and optimization. -- :ref:`input_data `: Tabular data containing the input variables of the experiments as column and the experiments as rows. -- :ref:`output_data `: Tabular data containing the tracked outputs of the experiments. -- :ref:`project_dir `: A user-defined project directory where all files related to your data-driven process will be stored. - - -.. image:: ../../../img/f3dasm-experimentdata.png - :width: 100% - :align: center - :alt: ExperimentData object - -| - -.. note:: - - Users of :mod:`f3dasm` are advised to not directly manipulate the attributes of the ExperimentData object. Instead, the methods of ExperimentData should be used to manipulate the data. - -The :class:`~f3dasm.ExperimentData` object can be constructed in several ways: - -* :ref:`By providing your own data ` -* :ref:`Reconstructed from the project directory ` -* :ref:`By a sampling strategy ` -* :ref:`From a hydra configuration file ` - -.. _experimentdata-own: - -ExperimentData from your own data ---------------------------------- - -You can construct a :class:`~f3dasm.ExperimentData` object by providing it :ref:`input_data `, :ref:`output_data `, a :ref:`domain ` object and a :ref:`filename `. - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> data = ExperimentData( - domain=domain, input_data=input_data, output_data=output_data) - - -The following sections will explain how to construct a :class:`~f3dasm.ExperimentData` object from your own data. - -.. _domain-format: - -domain -^^^^^^ - -The ``domain`` argument should be a :class:`~f3dasm.design.Domain` object. It defines the feasible domain of the design-of-experiments. -Learn more about the :class:`~f3dasm.design.Domain` object in the :ref:`domain ` section. - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> domain = Domain() - >>> domain.add_float('x0', 0., 1.) - >>> domain.add_float('x1', 0., 1.) - >>> data = ExperimentData(domain=domain) - -.. warning :: - - If you don't provide a :class:`~f3dasm.design.Domain` object, the domain will be inferred from the input data. - Constructing the dataframe by inferring it from samples can be useful if you have a large number of parameters and you don't want to manually specify the domain. - This will be done by looking at the data-type and boundaries of the input data. - However, this is not recommended as it can lead to unexpected results. - -.. _input-data-format: - -input_data -^^^^^^^^^^ - -Input data describes the input variables of the experiments. -The input data is provided in a tabular manner, with the number of rows equal to the number of experiments and the number of columns equal to the number of input variables. - -Single parameter values can have any of the basic built-in types: ``int``, ``float``, ``str``, ``bool``. Lists, tuples or array-like structures are not allowed. - -Several datatypes are supported for the ``input_data`` argument: - -* A :class:`~pandas.DataFrame` object with the input variable names as columns and the experiments as rows. - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> domain.add_float('x0', 0., 1.) - >>> domain.add_float('x1', 0., 1.) - >>> data = ExperimentData(domain=domain, input_data=df) - -* A two-dimensional :class:`~numpy.ndarray` object with shape (, ) - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> import numpy as np - >>> input_array = np.array([[0.1, 0.2], [0.3, 0.4]]) - >>> domain.add_float('x0', 0., 1.) - >>> domain.add_float('x1', 0., 1.) - >>> data = ExperimentData(domain=domain, input_data=input_array) - -.. note:: - - When providing a :class:`~numpy.ndarray` object, you need to provide a :class:`~f3dasm.design.Domain` object as well. - Also, the order of the input variables is inferred from the order of the columns in the :class:`~f3dasm.design.Domain` object. - - -* A string or path to a ``.csv`` file containing the input data. The ``.csv`` file should contain a header row with the names of the input variables and the first column should be indices for the experiments. - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> domain.add_float('x0', 0., 1.) - >>> domain.add_float('x1', 0., 1.) - >>> data = ExperimentData(domain=doman, input_data="my_experiment_data.csv") - -.. _output-data-format: - -output_data -^^^^^^^^^^^ - -Output data describes the output variables of the experiments. -The output data is provided in a tabular manner, with the number of rows equal to the number of experiments and the number of columns equal to the number of output variables. - - -Several datatypes are supported for the ``output_data`` argument: - -* A :class:`~pandas.DataFrame` object with the output variable names as columns and the experiments as rows. - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> df = pd.DataFrame(...) # your data in a pandas DataFrame - >>> domain.add_output('x0') - >>> domain.add_output('x1') - >>> data = ExperimentData(domain=domain, output_data=df) - -* A two-dimensional :class:`~numpy.ndarray` object with shape (, ) - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> import numpy as np - >>> output_array = np.array([[0.1, 0.2], [0.3, 0.4]]) - >>> domain.add_output('x0') - >>> domain.add_output('x1') - >>> data = ExperimentData(domain=domain, output_array=output_array) - -* A string or path to a ``.csv`` file containing the output data. The ``.csv`` file should contain a header row with the names of the output variables and the first column should be indices for the experiments. - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> domain.add_output('x0') - >>> domain.add_output('x1') - >>> data = ExperimentData(domain=domain, output_data="my_experiment_data.csv") - -If you don't have output data yet, you can also construct an :class:`~f3dasm.ExperimentData` object without providing output data. - - -.. _filename-format: - -project directory -^^^^^^^^^^^^^^^^^ - -The ``project_dir`` argument is used to :ref:`store the ExperimentData to disk ` -You can provide a string or a path to a directory. This can either be a relative or absolute path. -If the directory does not exist, it will be created. - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> project_dir = "folder/to/my_project_directory" - >>> data = ExperimentData(project_dir=project_dir) - -You can also set the project directory manually after creation with the :meth:`~f3dasm.ExperimentData.set_project_dir` method" - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> data = ExperimentData() - >>> data.set_project_dir("folder/to/my_project_directory") - - -.. _experimentdata-file: - -ExperimentData from project directory -------------------------------------- - -If you already have constructed the :class:`~f3dasm.ExperimentData` object before, you can retrieve it from disk by calling the :meth:`~f3dasm.ExperimentData.from_file` -classmethod with the path of project directory: - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> data = ExperimentData.from_file("folder/to/my_project_directory") - -.. _experimentdata-sampling: - -ExperimentData from sampling ----------------------------- - -You can directly construct an :class:`~f3dasm.ExperimentData` object from a sampling strategy by using the :meth:`~f3dasm.ExperimentData.from_sampling` method. -You have to provide the following arguments: - -* A sampling function. To learn more about integrating your sampling function, please refer to the :ref:`this ` section. -* A :class:`~f3dasm.design.Domain` object describing the input variables of the sampling function. -* The number of samples to generate. -* An optional seed for the random number generator. - -.. code-block:: python - - from f3dasm import ExperimentData, Domain - - def your_sampling_function(domain, n_samples, seed): - # your sampling function - # ... - return samples - - domain = Domain() - domain.add_float('x0', 0., 1.) - domain.add_float('x1', 0., 1.) - data = ExperimentData.from_sampling(sampler=your_sampling_function, domain=domain, n_samples=10, seed=42) - -You can use :ref:`built-in samplers ` by providing one of the following strings as the ``sampler`` argument: - -.. code-block:: python - - from f3dasm import ExperimentData - from f3dasm.design import Domain - - domain = Domain() - domain.add_float(name='x0', low=0., high=0.) - domain.add_float(name='x1', low=0., high=0.) - data = ExperimentData.from_sampling(sampler="latin", domain=domain, n_samples=10, seed=42) - -.. _experimentdata-hydra: - -ExperimentData from a `hydra `_ configuration file ---------------------------------------------------------------------- - -If you are using `hydra `_ for configuring your experiments, you can use it to construct -an :class:`~f3dasm.ExperimentData` object from the information in the :code:`config.yaml` file with the :meth:`~f3dasm.ExperimentData.from_yaml` method. - -You can create an experimentdata :class:`~f3dasm.ExperimentData` object in the same ways as described above, but now using the hydra configuration file. - - -.. code-block:: yaml - :caption: config.yaml - - - domain: - x0: - type: float - lower_bound: 0. - upper_bound: 1. - x1: - type: float - lower_bound: 0. - upper_bound: 1. - - experimentdata: - input_data: path/to/input_data.csv - output_data: - domain: ${domain} - -.. note:: - - The :class:`~f3dasm.design.Domain` object will be constructed using the :code:`domain` key in the :code:`config.yaml` file. Make sure you have the :code:`domain` key in your :code:`config.yaml`! - To see how to configure the :class:`~f3dasm.design.Domain` object with hydra, see :ref:`this ` section. - -Inside your python script, you can then create the :class:`~f3dasm.ExperimentData` object with the :meth:`~f3dasm.ExperimentData.from_yaml` method: - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> import hydra - - >>> @hydra.main(config_path="conf", config_name="config") - >>> def my_app(config): - >>> data = ExperimentData.from_yaml(config.experimentdata) - - -To create the :class:`~f3dasm.ExperimentData` object with the :meth:`~f3dasm.ExperimentData.from_sampling` method, you can use the following configuration: - -.. code-block:: yaml - :caption: config.yaml for from_sampling - - domain: - x0: - type: float - lower_bound: 0. - upper_bound: 1. - x1: - type: float - lower_bound: 0. - upper_bound: 1. - - experimentdata: - from_sampling: - domain: ${domain} - sampler: random - seed: 1 - n_samples: 3 - - -.. note:: - - Make sure you have the :code:`domain` key in your :code:`config.yaml`! - To see how to configure the :class:`~f3dasm.design.Domain` object with hydra, see :ref:`this ` section. - - -To create the :class:`~f3dasm.ExperimentData` object with the :meth:`~f3dasm.ExperimentData.from_file` method, you can use the following configuration: - -.. code-block:: yaml - :caption: config.yaml for from_file - - experimentdata: - from_file: path/to/my_experiment_data - -Adding data after construction ------------------------------- - -If you have constructed your :class:`~f3dasm.ExperimentData` object, you can add ``input_data``, ``output_data``, a ``domain`` or the ``filename`` using the :meth:`~f3dasm.ExperimentData.add` method: - -.. code-block:: python - - >>> from f3dasm import ExperimentData - >>> from f3dasm.design import Domain - >>> data = ExperimentData() - >>> domain = Domain() - >>> domain.add_float(name='x0', low=0., high=1.) - >>> domain.add_float(name='x1', low=0., high=1.) - >>> data.add(input_data=input_data) - -.. warning:: - - You can only add data to an existing :class:`~f3dasm.ExperimentData` object if the domain is the same as the existing domain. - - -Exporting ---------- - -.. _experimentdata-store: - -Storing the ExperimentData object -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The :class:`~f3dasm.ExperimentData` object can be exported to a collection of files using the :meth:`~f3dasm.ExperimentData.store` method. -You can provide a path to a directory where the files will be stored, or if not provided, the files will be stored in the directory provided in the :attr:`~f3dasm.design.ExperimentData.project_dir` attribute: - -.. code-block:: python - - >>> data.store("path/to/project_dir") - -Inside the project directory, a subfolder `experiment_data` will be created with the following files: - -- :code:`domain.pkl`: The :class:`~f3dasm.design.Domain` object -- :code:`input.csv`: The :attr:`~f3dasm.design.ExperimentData.input_data` table -- :code:`output.csv`: The :attr:`~f3dasm.design.ExperimentData.output_data` table -- :code:`jobs.pkl`: The :attr:`~f3dasm.design.ExperimentData.jobs` object - -These files are used to load the :class:`~f3dasm.ExperimentData` object again using the :meth:`~f3dasm.ExperimentData.from_file` method. - -.. code-block:: python - - >>> data = ExperimentData.from_file("path/to/project_dir") - - - -.. code-block:: none - :caption: Directory Structure - - project_dir/ - └── experiment_data/ - ├── domain.pkl - ├── input.csv - ├── output.csv - └── jobs.pkl - -.. _experimentdata-store-other: - -Storing to other datatypes -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Alternatively, you can convert the input- and outputdata of your data-driven process to other well-known datatypes: - -* :class:`~numpy.ndarray` (:meth:`~f3dasm.ExperimentData.to_numpy`); creates a tuple of two :class:`~numpy.ndarray` objects containing the input- and outputdata. -* :class:`~xarray.Dataset` (:meth:`~f3dasm.ExperimentData.to_xarray`); creates a :class:`~xarray.Dataset` object containing the input- and outputdata. -* :class:`~pd.DataFrame` (:meth:`~f3dasm.ExperimentData.to_pandas`); creates a tuple of two :class:`~pd.DataFrame` object containing the input- and outputdata. - -.. .. minigallery:: f3dasm.ExperimentData -.. :add-heading: Examples using the `ExperimentData` object -.. :heading-level: - - diff --git a/docs/source/rst_doc_files/classes/design/experimentsample.rst b/docs/source/rst_doc_files/classes/design/experimentsample.rst deleted file mode 100644 index 4c2e0b05..00000000 --- a/docs/source/rst_doc_files/classes/design/experimentsample.rst +++ /dev/null @@ -1,202 +0,0 @@ -Experiment Sample -================= - -A :class:`~f3dasm.ExperimentSample` object contains a single realization of the design-of-experiment in :class:`~f3dasm.ExperimentData`. - -.. image:: ../../../img/f3dasm-design.png - :alt: Design - :width: 100% - :align: center - -| - -.. warning:: - A :class:`~f3dasm.ExperimentSample` is not constructed manually, but created inside the :class:`~f3dasm.ExperimentData` when it is required by internal processes. - The main use of the :class:`~f3dasm.ExperimentSample` is in the context of the :class:`~f3dasm.datageneration.DataGenerator` in order to extract design variables and store output variables. - Learn more about the :class:`~f3dasm.datageneration.DataGenerator` in the :ref:`Data Generation ` section. - - -For each of the experiments in the :class:`~f3dasm.ExperimentData`, an :class:`~f3dasm.ExperimentSample` object can be created. -This object contains the following attributes: - -* the input parameters of the experiment, :attr:`~f3dasm.design.ExperimentSample.input_data`, as a dictionary - -.. code-block:: python - - >>> experiment_sample.input_data - {'param_1': 0.0249, 'param_2': 0.034, 'param_3': 0.1} - -* the output parameters of the experiment, :attr:`~f3dasm.design.ExperimentSample.output_data`, as a dictionary - -.. code-block:: python - - >>> experiment_sample.output_data - {'output_1': 0.123, 'output_2': [0.123, 0.456, 0.789], 'output_3': 'Hello world'} - - -.. note:: - - If you have :ref:`stored your output to disk `, the :attr:`~f3dasm.design.ExperimentSample.output_data` will contain a reference to the stored output instead of the actual output. - If you want to load the objects from disk, use the :attr:`~f3dasm.design.ExperimentSample.output_data_loaded` attribute. - -* the index number of the experiment: :attr:`~f3dasm.design.ExperimentSample.job_number`, as an integer - -.. code-block:: python - - >>> experiment_sample.job_number - 0 - -Input and output parameters of an experiment sample can be accessed using the :attr:`~f3dasm.design.ExperimentSample.get` attribute, with the name of the parameter as the key. -An error will be raised if the key is not found. - -.. code-block:: python - - >>> experiment_sample.get('param_1') - 0.0249 - -Manually iterating over ExperimentData ----------------------------------------- - -The :class:`~f3dasm.ExperimentData` object can be manually iterated over to get :class:`~f3dasm.ExperimentSample` objects for each experiment: - -.. code-block:: python - - >>> for experiment_sample in experiment_data: - ... print(experiment_sample) - ExperimentSample(0 : {'x0': 0.8184054141827567, 'x1': 0.937852542255321, 'x2': 0.7376563782762678} - {}) - ExperimentSample(1 : {'x0': 0.7203461491873061, 'x1': 0.7320604457665572, 'x2': 0.2524387342272223} - {}) - ExperimentSample(2 : {'x0': 0.35449352388104904, 'x1': 0.11413412225748525, 'x2': 0.1467895592274866} - {}) - -.. _storing-output-experiment-sample: - -Storing output parameters to the experiment sample --------------------------------------------------- - -After running your simulation, you can store the result back into the :class:`~f3dasm.ExperimentSample` with the :meth:`~f3dasm.design.ExperimentSample.store` method. -There are two ways of storing your output: - -* Singular values can be stored directly to the :attr:`~f3dasm.design.ExperimentData.output_data` -* Large objects can be stored to disk and a reference path will be stored to the :attr:`~f3dasm.design.ExperimentData.output_data`. - -Single values -^^^^^^^^^^^^^ - -Single values or small lists can be stored to the :class:`~f3dasm.ExperimentData` using the ``to_disk=False`` argument, with the name of the parameter as the key. -This will create a new output parameter if the parameter name is not found in :attr:`~f3dasm.design.ExperimentData.output_data` of the :class:`~f3dasm.ExperimentData` object: -This is especially useful if you want to get a quick overview of some loss or design metric of your sample. - -.. code-block:: python - - >>> experiment_sample.store('output_1', 0.123, to_disk=False) - >>> experiment_sample('output_2', 'Hello world', to_disk=False) - -All built-in singular types are supported for storing to the :class:`~f3dasm.ExperimentData` this way. Array-like data such as numpy arrays and pandas dataframes are **not** supported and will raise an error. - -.. note:: - Outputs stored directly to the :attr:`~f3dasm.design.ExperimentData.output_data` will be stored within the :class:`~f3dasm.ExperimentData` object. - This means that the output will be loaded into memory everytime this object is accessed. For large outputs, it is recommended to store the output to disk. - -.. _store-to-disk: - -Large objects and array-like data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to store large objects or array-like data, the :meth:`~f3dasm.ExperimentSample.store` method using the ``to_disk=True`` argument, can be used. -A reference (:code:`Path`) will be saved to the :attr:`~f3dasm.design.ExperimentData.output_data`. - -.. code-block:: python - - >>> output = np.array(...) # your output data - >>> experiment_sample.store('output_numpy', output, to_disk=True) - -:mod:`f3dasm` will automatically create a new directory in the project directory for each output parameter and store the object with a generated filename referencing the :attr:`~f3dasm.design.ExperimentSample.job_number` of the design. - -.. code-block:: none - :caption: Directory Structure - - project_dir/ - ├── output_numpy/ - │ ├── 0.npy - │ ├── 1.npy - │ ├── 2.npy - │ └── 3.npy - │ - └── experiment_data/ - ├── domain.pkl - ├── input.csv - ├── output.csv - └── jobs.pkl - - -In the output data of the :class:`~f3dasm.ExperimentData` object, a reference path (e.g. :code:`/output_numpy/0.npy`) to the stored object will be saved. - - -:mod:`f3dasm` has built-in storing functions for numpy :class:`~numpy.ndarray`, pandas :class:`~pandas.DataFrame` and xarray :class:`~xarray.DataArray` and :class:`~xarray.Dataset` objects. -For any other type of object, the object will be stored in the `pickle `_ format - - -You can provide your own storing class to the :class:`~f3dasm.design.ExperimentSample.store` method call: - -* a ``store`` method should store an ``self.object`` to disk at the location of ``self.path`` -* a ``load`` method should load the object from disk at the location of ``self.path`` and return it -* a class variable ``suffix`` should be defined, which is the file extension of the stored object as a string. -* the class should inherit from the :class:`~f3dasm.design._Store` class - -You can take the following class for a :class:`~numpy.ndarray` object as an example: - -.. code-block:: python - - class NumpyStore(_Store): - suffix: int = '.npy' - - def store(self) -> None: - np.save(file=self.path.with_suffix(self.suffix), arr=self.object) - - def load(self) -> np.ndarray: - return np.load(file=self.path.with_suffix(self.suffix)) - - -After defining the storing function, it can be used as an additional argument in the :meth:`~f3dasm.ExperimentSample.store` method: - -.. code-block:: python - - >>> experiment_sample.store('output_1', my_custom_large_object, numpy_storing_function) - - -Loading output parameters that are referenced -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When loading an output parameter that is referenced, the :class:`~f3dasm.ExperimentSample` will automatically load the object from disk and return it -when you are calling the :attr:`~f3dasm.design.ExperimentSample.get` method. - -.. note:: - - If you are using a custom storing object, you need to provide the object as an additional argument to the :attr:`~f3dasm.design.ExperimentSample.get` method. - everytime you are loading the object. - -.. code-block:: python - - >>> experiment_sample.get('output_numpy') - np.array(...) - -Exporting to other datatypes ----------------------------- - -The :class:`~f3dasm.ExperimentSample` can be exported to a tuple of numpy :class:`~numpy.ndarray` from the ``input_data`` and ``output_data`` respectively using the :meth:`~f3dasm.ExperimentSample.to_numpy` method: - -.. code-block:: python - - >>> experiment_sample.to_numpy() - np.array([0.0249, 0.123, 0.456, 0.789]), np.array([0.003]) - -The :class:`~f3dasm.ExperimentSample` can be exported to a dictionary :class:`~pandas.DataFrame` using the :meth:`~f3dasm.ExperimentSample.to_dict` method: - -.. code-block:: python - - >>> experiment_sample.to_dict() - {'input_1': 0.0249, 'input_2': 0.123, 'input_3': 0.456, 'input_4': 0.789, 'output_data': -0.003, 'job_number': 0} - -.. note:: - - The :meth:`~f3dasm.ExperimentSample.to_dict` method will return a dictionary including the :attr:`~f3dasm.design.ExperimentSample.job_number` as the key. - Additionally, the ``output_data`` will also be loaded from disk if applicable. \ No newline at end of file diff --git a/docs/source/rst_doc_files/classes/machinelearning/machinelearning.rst b/docs/source/rst_doc_files/classes/machinelearning/machinelearning.rst deleted file mode 100644 index 843a1ef0..00000000 --- a/docs/source/rst_doc_files/classes/machinelearning/machinelearning.rst +++ /dev/null @@ -1,5 +0,0 @@ -Machine learning -================ - -To use the machine learning in the data-driven process, -we encourage the user to :ref:`export the ExperimentData object ` to your datatype of choice, and after your analysis, :ref:`import the results ` back into an ExperimentData object. \ No newline at end of file diff --git a/docs/source/rst_doc_files/classes/optimization/f3dasm-optimize.rst b/docs/source/rst_doc_files/classes/optimization/f3dasm-optimize.rst deleted file mode 100644 index 1ea01164..00000000 --- a/docs/source/rst_doc_files/classes/optimization/f3dasm-optimize.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _f3dasm-optimize: - -:code:`f3dasm-optimize` -======================= - - -The :mod:`f3dasm.optimization` module is designed to be easily extended by third-party libraries. -These extensions are provided as separate package: `f3dasm_optimize `_, which can be installed via pip: - -.. code-block:: bash - - pip install f3dasm_optimize - -More information about this extension can be found in the `f3dasm_optimize documentation page `_ \ No newline at end of file diff --git a/docs/source/rst_doc_files/classes/optimization/optimizers.rst b/docs/source/rst_doc_files/classes/optimization/optimizers.rst deleted file mode 100644 index ed580c0e..00000000 --- a/docs/source/rst_doc_files/classes/optimization/optimizers.rst +++ /dev/null @@ -1,82 +0,0 @@ -.. _optimization: - -Optimizer -========= - -The :class:`~f3dasm.optimization.Optimizer` class is used to find the minimum of a particular quantity of interest through an iterative fashion. - -* The :meth:`~f3dasm.optimization.Optimizer.update_step` method takes a :class:`~f3dasm.datageneration.DataGenerator` object and outputs a tuple containing the position and evaluated value of the next iteration. -* The :meth:`~f3dasm.ExperimentData.optimize` method from :class:`~f3dasm.experimentdata.ExperimentData` is used to start the optimization process. It takes the number of iterations, the optimizer object and a :class:`~f3dasm.datageneration.DataGenerator` object as arguments. For every iteration, the :meth:`~f3dasm.optimization.Optimizer.update_step` method is called and the results are stored in the :class:`~f3dasm.ExperimentData` object. - - -.. image:: ../../../img/f3dasm-optimizer.png - :width: 70% - :align: center - -| - -Create an optimizer -------------------- - -First, we have to determine the suitable search-space by creating a :class:`~f3dasm.design.Domain` object. - -.. code-block:: python - - from f3dasm import Domain, ContinuousParameter - - domain = Domain(input_space={'x0': ContinuousParameter(lower_bound=0.0, upper_bound=1.0), - 'x1': ContinuousParameter(lower_bound=0.0, upper_bound=1.0)}) - - -Next, we have to create initial samples. We can use the Latin-hypercube sampler to create samples: - -.. code-block:: python - - data.from_sampling(sampler='latin', domain=domain, n_samples=10, seed=42) - -We will use the ``"L-BFGS-B"`` optimizer to find the minimum. For built-in optimizer we can use the name of the optimizer: - -.. code-block:: python - - data.optimize(optimizer='L-BFGS-B', iterations=100, data_generator='ackley') - -.. note:: - - You can pass hyperparameters of the optimizer as a dictionary to the ``optimize()`` method - - -Implement your own optimizer ----------------------------- - -To implement your own optimizer, you have to create a class that inherits from the :class:`~f3dasm.optimization.Optimizer` class -and implement the :meth:`~f3dasm.optimization.Optimizer.update_step` method. -The :meth:`~f3dasm.optimization.Optimizer.update_step` method takes a :class:`~f3dasm.datageneration.DataGenerator` object and outputs a tuple containing the position and evaluated value of the next iteration. - -.. code-block:: python - - from f3dasm import Optimizer, DataGenerator - - class MyOptimizer(Optimizer): - def update_step(self, data_generator: DataGenerator): - # calculate the next position according to your update strategy - return x_new, y_new - - -You can access the history of evaluations through the ``self.data`` attribute. This contains a copy of the ``ExperimentData`` object. - -.. _implemented optimizers: - -Implemented optimizers ----------------------- - -The following implementations of optimizers can found under the :mod:`f3dasm.optimization` module: -These are ported from `scipy-optimize `_ - -======================== ========================================================================= =============================================================================================== -Name Key-word Reference -======================== ========================================================================= =============================================================================================== -Conjugate Gradient ``"CG"`` `scipy.minimize CG `_ -L-BFGS-B ``"LBFGSB"`` `scipy.minimize L-BFGS-B `_ -Nelder Mead ``"NelderMead"`` `scipy.minimize NelderMead `_ -Random search ``"RandomSearch"`` `numpy `_ -======================== ========================================================================= =============================================================================================== diff --git a/docs/source/rst_doc_files/classes/sampling/sampling.rst b/docs/source/rst_doc_files/classes/sampling/sampling.rst deleted file mode 100644 index 4c902a38..00000000 --- a/docs/source/rst_doc_files/classes/sampling/sampling.rst +++ /dev/null @@ -1,86 +0,0 @@ -.. _sampling: - -Sampling -======== - -In the context of the data-driven process, samplers play a crucial role in generating input data for experiments or analyses. -A sampler takes a :class:`~f3dasm.design.Domain` object, and applies a specific strategy to produce samples. -These samples serve as input for further analysis, experimentation, or modeling. - -This section describes how you can implement your sampling strategy or use the built-in samplers in a data-driven process. - -.. _integrating-samplers: - -Implement your sampling strategy --------------------------------- - -When integrating your sampling strategy into the data-driven process, you have to create a function that will take several arguments: - -* :code:`domain`: A :class:`~f3dasm.design.Domain` object that represents the design-of-experiments -* :code:`n_samples`: The number of samples you wish to generate. It's not always necessary to define this upfront, as some sampling methods might inherently determine the number of samples based on certain criteria or properties of the domain. -* :code:`seed`: A seed for the random number generator to replicate the sampling process. This enables you to control the randomness of the sampling process [1]_. By setting a seed, you ensure reproducibility, meaning that if you run the sampling function with the same seed multiple times, you'll get the same set of samples. - -.. [1] If no seed is provided, the function should use a random seed. - -The function should return the samples (``input_data``) in one of the following formats: - -* A :class:`~pandas.DataFrame` object -* A :class:`~numpy.ndarray` object - -.. code-block:: python - - def your_sampling_method(domain: Domain, n_samples: int, seed: Optional[int]): - # Your sampling logic here - ... - return your_samples - -An example: implementing a sobol sequence sampler -------------------------------------------------- - -For example, the following code defines a sampler based on a `Sobol sequence `_: - -.. code-block:: python - - from f3dasm.design import Domain - from SALib.sample import sobol_sequence - - def sample_sobol_sequence(domain: Domain, n_samples: int, **kwargs): - samples = sobol_sequence.sample(n_samples, len(domain)) - - # stretch samples - for dim, param in enumerate(domain.space.values()): - samples[:, dim] = ( - samples[:, dim] * ( - param.upper_bound - param.lower_bound - ) + param.lower_bound - ) - return samples - -To use the sampler in the data-driven process, you should pass the function to the :class:`~f3dasm.ExperimentData` object as follows: - -.. code-block:: python - - from f3dasm.design import ExperimentData, Domain - - domain = Domain(...) - # Create the ExperimentData object - experiment_data = ExperimentData(domain=domain) - - # Generate samples - experiment_data.sample(sampler=your_sampling_method, n_samples=10, seed=42) - -.. _implemented samplers: - -Implemented samplers --------------------- - -The following built-in implementations of samplers can be used in the data-driven process. - -======================== ====================================================================== =========================================================================================================== -Name Method Reference -======================== ====================================================================== =========================================================================================================== -``"random"`` Random Uniform sampling `numpy.random.uniform `_ -``"latin"`` Latin Hypercube sampling `SALib.latin `_ -``"sobol"`` Sobol Sequence sampling `SALib.sobol_sequence `_ -``"grid"`` Grid Search sampling `itertools.product `_ -======================== ====================================================================== =========================================================================================================== diff --git a/docs/source/rst_doc_files/classes/workflow/workflow.rst b/docs/source/rst_doc_files/classes/workflow/workflow.rst deleted file mode 100644 index 8c457f5e..00000000 --- a/docs/source/rst_doc_files/classes/workflow/workflow.rst +++ /dev/null @@ -1,237 +0,0 @@ -.. _workflow: - -Creating a Data-driven process -============================== - -All of the :mod:`f3dasm` classes come together in the ``main`` script creating the data-driven process. It serves as the entry-point of your data-driven application. - -The worfklow should act on the interfaces of the :mod:`f3dasm` classes and any experiment-specific scripts can be imported into the workflow. -In this way, the workflow itself is independent of the experiment-specific scripts and can be reused for different experiments. - -Example with built-ins ----------------------- - -In the following examples, we will create a workflow for the following data-driven process: - -* Create a 20D continuous :class:`~f3dasm.design.Domain` -* Sample from the domain using the built-in Latin-hypercube sampler -* Use a data generation function, which will be the ``"Ackley"`` function a from the :ref:`benchmark-functions` -* Optimize the data generation function using the built-in ``"LBFGSB"`` optimizer. - -.. image:: ../../../img/f3dasm-workflow-example.png - :width: 70% - :align: center - :alt: Workflow - -| - - -Directory Structure -^^^^^^^^^^^^^^^^^^^ - -The directory structure for the project is as follows: - -- `my_project/` is the root directory. -- `main.py` is the main entry point of the project, governed by :mod:`f3dasm`. - -.. code-block:: none - :caption: Directory Structure - - my_project/ - └── main.py - -.. _my-script: - -main.py -^^^^^^^ - -The `main.py` file is the main entry point of the project. It contains the :mod:`f3dasm` classes and acts on these interfaces. -In the main function, we create the :class:`~f3dasm.design.Domain`, sample from it, evaluate the samples with the data generation function and optimize with the optimizer. - -.. code-block:: python - :caption: main.py - - from f3dasm.design import make_nd_continuous_domain - - """Design of Experiment""" - # Create a domain object - domain = make_nd_continuous_domain(bounds=np.tile([0.0, 1.0], (20, 1)), dimensionality=20) - - # Create the ExperimentData object - data = ExperimentData(domain=domain) - - # Sampling from the domain - data.sample(sampler='latin', n_samples=10, seed=42) - - """Data Generation""" - # Use the data-generator to evaluate the initial samples - data.evaluate(data_generator="ackley", mode='sequential') - - """Optimization""" - data.optimize(data_generator="ackley", optimizer="lbfgsb", iterations=100) - -.. note:: - In the `main.py` file, notice that there is only one connection with the `my_script.py` file, which is the `my_function` function import. - This means that the workflow file (`main.py`) is independent of the application file (`my_script.py`) and can be reused for different experiments. - - -Adding your own sampler ------------------------ - -If you want to substitute the built-in sampler ``"latin"`` with your own sampler, you can do so by creating a new file, e.g. `my_script.py` and adding it to the directory structure: - -.. code-block:: none - :caption: Directory Structure - - my_project/ - ├── my_script.py - └── main.py - - -Now, we are going to creat a custom sampler in ``my_script`` and import it into the ``main.py`` file. -In the `my_script.py` file, we create a function called ``my_sampler``: - -.. code-block:: python - :caption: my_script.py - - from f3dasm import Domain - - def my_sampler(domain: Domain, n_samples: int, seed: int): - # Create the samples of the domain in a numpy array or pandas DataFrame format - ... - return samples - -.. note:: - Learn more about complying to the sampling interface in the :ref:`sampling ` section. - -Now we can import ``my_sampler`` into the ``main.py`` file and use it as a sampler: - -.. code-block:: python - :caption: main.py with custom sampler - - from f3dasm.design import make_nd_continuous_domain - from my_script import my_sampler - - """Design of Experiment""" - # Create a domain object - domain = make_nd_continuous_domain(bounds=np.tile([0.0, 1.0], (20, 1)), dimensionality=20) - - # Create the ExperimentData object - data = ExperimentData(domain=domain) - - # Sampling from the domain - data.sample(sampler=my_sampler, n_samples=10, seed=42) - - """Data Generation""" - # Use the data-generator to evaluate the initial samples - data.evaluate(data_generator="ackley", mode='sequential') - - """Optimization""" - data.optimize(data_generator="ackley", optimizer="lbfgsb", iterations=100) - - -Adding your own simulator -------------------------- - -If you want to substitute the built-in data generation ``"Ackley"`` function with your own simulator, you can do so by adding it to ``my_script``: - - -Now, the `my_script.py` file contains your own `MySimulator` class. You have to add the ``execute`` function so that it conforms with the :class:`~f3dasm.datageneration.DataGenerator` interface. -In addition, you should add a ``pre_process_function`` and a ``post_processing_function`` to the class. - -.. note:: - Learn more about complying to the :class:`~f3dasm.datageneration.DataGenerator` interface in the :ref:`data-generation` section. - -.. code-block:: python - :caption: my_script.py - - from f3dasm import ExperimentSample - from f3dasm.datageneration import DataGenerator - - - class MySimulator(DataGenerator): - def execute(self): - # Run the simulator with the input script '' - ... - - def pre_process_function(experiment_sample: ExperimentSample): - # From the experiment_sample, create a simulation script that can be handled by the simulator - ... - - def post_processing_function(experiment_sample: ExperimentSample): - # From the output of the simulator, extract the results and store them as a dictionary in a 'results.pkl' file - ... - -.. code-block:: python - :caption: main.py with custom simulator - - from f3dasm.design import make_nd_continuous_domain - from my_script import MySimulator, pre_processing_function, post_processing_function - - """Design of Experiment""" - # Create a domain object - domain = make_nd_continuous_domain(bounds=np.tile([0.0, 1.0], (20, 1)), dimensionality=20) - - # Create the ExperimentData object - data = ExperimentData(domain=domain) - - # Sampling from the domain - data.sample(sampler='latin', n_samples=10, seed=42) - - """Data Generation""" - # Create your simulator object - simulator = MySimulator() - - # add preprocessing - simulator.add_pre_process(pre_processing_function) - simulator.add_post_process(post_processing_function) - - # Use the data-generator to evaluate the initial samples - data.evaluate(data_generator=simulator, mode='sequential') - - """Optimization""" - data.optimize(data_generator=simulator, optimizer="lbfgsb", iterations=100) - -Adding your own optimizer -------------------------- - -In order to add you own optimizer, you can add it to the `my_script.py` file: - -.. code-block:: python - :caption: my_script.py - - from f3dasm.optimization import Optimizer - from f3dasm.datagenerationr import DataGenerator - - class MyOptimizer(Optimizer): - def update_step(self, data_generator: DataGenerator): - # Create the new samples according to your update strategy - return x_new, y_new - -.. note:: - Learn more about complying to the optimization interface in the :ref:`optimization` section. - -.. code-block:: python - :caption: main.py with custrom optimizer - - from f3dasm.design import make_nd_continuous_domain - - """Design of Experiment""" - # Create a domain object - domain = make_nd_continuous_domain(bounds=np.tile([0.0, 1.0], (20, 1)), dimensionality=20) - - # Create the ExperimentData object - data = ExperimentData(domain=domain) - - # Sampling from the domain - data.sample(sampler='latin', n_samples=10, seed=42) - - """Data Generation""" - # Use the data-generator to evaluate the initial samples - data.evaluate(data_generator="ackley", mode='sequential') - - """Optimization""" - # Create an instance of the optimizer - my_optimizer = MyOptimizer(domain=domain) - - data.optimize(data_generator="ackley", optimizer=my_optimizer, iterations=100) diff --git a/docs/source/rst_doc_files/cluster.rst b/docs/source/rst_doc_files/cluster.rst deleted file mode 100644 index 92f3aca4..00000000 --- a/docs/source/rst_doc_files/cluster.rst +++ /dev/null @@ -1,449 +0,0 @@ -High-performance computing cluster -================================== - -Your :mod:`f3dasm` workflow can be seemlessly translated to a high-performance computing cluster. -The advantage is that you can parallelize the total number of experiments among the nodes of the cluster. -This is especially useful when you have a large number of experiments to run. - -.. note:: - :code:`mode=cluster` and :code:`mode=parallel` simultaneous on the :meth:`~f3dasm.ExperimentData.evaluate` function cannot be used. - In the case of extended paralellization on cores, you have to implement this in your own scripts!. - -Working with array-jobs ------------------------ - -Suppose you have to run a Python script on a high-performance computing cluster. This is normally done by submitting a bash-script denoting the job-details to the cluster. -This bash-script will have a line executing the Python script from a command line interface: - -.. code-block:: bash - - python main.py - -High-performance computing clusters could have the ability to submit an array of jobs, i.e. sending out the same job to multiple nodes, can be used to parallelize the total number of experiments across nodes. -When submitting an array-job, each job has a unique identifier. - -We can use this unique identifier as a flag to the command line call: - -.. tabs:: - - .. group-tab:: TORQUE - - .. code-block:: bash - - python main.py --jobid=${PBS_ARRAYID} - - .. group-tab:: SLURM - - .. code-block:: bash - - python main.py --jobid=${SLURM_ARRAY_TASK_ID} - -After you have imported :mod:`f3dasm` -You can use the global variable :code:`f3dasm.HPC_JOBID` to get the array-job identifier and use it in your scripts. - - -.. code-block:: python - :caption: main.py - - import f3dasm - print(f3dasm.HPC_JOBID) - >>> 0 - -.. note:: - If the :code:`f3dasm.HPC_JOBID` is not set, it will return the value :code:`None` - -.. _cluster-mode: - -Using the cluster mode on ExperimentData ----------------------------------------- - -The :meth:`~f3dasm.ExperimentData.evaluate` method of the :class:`~f3dasm.ExperimentData` class has a parameter :code:`mode` that can be set to :code:`cluster`. -What this does is that for all the methods that alter the data of the :class:`~f3dasm.ExperimentData`, the :class:`~f3dasm.ExperimentData` object will be retrieved from disk and after each operation be written to the disk again. -During the operation, no other process can access the data of the :class:`~f3dasm.ExperimentData` object, as the file will be locked. - -.. note:: - You will notice that a :code:`.lock` file will be created in the directory of the :class:`~f3dasm.ExperimentData` object. This file will disable concurent access to the data. - -The cluster mode enables you to use multiple nodes to each retrieve an open :class:`~f3dasm.ExperimentSample` from the :class:`~f3dasm.ExperimentData`, execute the data generation function, and write the data back to the disk. -Whenever a node is working executing a particular design, the job-value will be set to 'in progress', making sure that other processes are not repeating that experiment. - -.. image:: ../../../img/f3dasm-cluster.png - :align: center - :width: 100% - :alt: Cluster mode - -| - -Example -------- - - -The following example is the same as in section :ref:`workflow`; only now we are omiting the optimization part and only parallelize the data generation: - -* Create a 20D continuous :class:`~f3dasm.design.Domain` -* Sample from the domain using a the Latin-hypercube sampler -* With multiple nodes; use a data generation function, which will be the ``"Ackley"`` function a from the :ref:`benchmark-functions` - - -.. image:: ../../../img/f3dasm-workflow-example-cluster.png - :width: 70% - :align: center - :alt: Workflow - -| - -We want to make sure that the sampling is done only once, and that the data generation is done in parallel. -Therefore we can divide the different nodes into two categories: - -* The first node (:code:`f3dasm.HPC_JOBID == 0`) will be the **master** node, which will be responsible for creating the design-of-experiments and sampling -* All the other nodes (:code:`f3dasm.HPC_JOBID > 0`) will be **process** nodes, which will retrieve the :class:`~f3dasm.ExperimentData` from disk and go straight to the data generation function. - -.. image:: ../../../img/f3dasm-workflow-cluster-roles.png - :width: 100% - :align: center - :alt: Cluster roles - -| - -.. note:: - This example has been tested with the `hpc06 cluster of Delft University of Technology `_. - This is a cluster that uses the `TORQUE resource manager `_. - -Directory Structure -^^^^^^^^^^^^^^^^^^^ - -The directory structure for the project is as follows: - -- `my_project/` is the root directory. -- `my_script.py` contains the user-defined script. In this case a custom data-generationr function `my_function`. -- `pbsjob.sh` is the bash script that will be submitted to the HPC. -- `main.py` is the main entry point of the project, governed by :mod:`f3dasm`. - -.. code-block:: none - :caption: Directory Structure - - my_project/ - ├── my_script.py - ├── pbsjob.sh - └── main.py - -my_script.py -^^^^^^^^^^^^ - -The user-defined script is identical to the one in :ref:`my-script`. Only now we are omiting the optimization part and only parallelize the data generation. - -pbsjob.sh -^^^^^^^^^ - -.. tabs:: - - .. group-tab:: TORQUE - - .. code-block:: bash - - #!/bin/bash - # Torque directives (#PBS) must always be at the start of a job script! - #PBS -N ExampleScript - #PBS -q mse - #PBS -l nodes=1:ppn=12,walltime=12:00:00 - - # Make sure I'm the only one that can read my output - umask 0077 - - - # The PBS_JOBID looks like 1234566[0]. - # With the following line, we extract the PBS_ARRAYID, the part in the brackets []: - PBS_ARRAYID=$(echo "${PBS_JOBID}" | sed 's/\[[^][]*\]//g') - - module load use.own - module load miniconda3 - cd $PBS_O_WORKDIR - - # Here is where the application is started on the node - # activating my conda environment: - - source activate f3dasm_env - - # limiting number of threads - OMP_NUM_THREADS=12 - export OMP_NUM_THREADS=12 - - - # If the PBS_ARRAYID is not set, set it to None - if ! [ -n "${PBS_ARRAYID+1}" ]; then - PBS_ARRAYID=None - fi - - # Executing my python program with the jobid flag - python main.py --jobid=${PBS_ARRAYID} - - .. group-tab:: SLURM - - .. code-block:: bash - - #!/bin/bash -l - - #SBATCH -J "ExmpleScript" # name of the job (can be change to whichever name you like) - #SBATCH --get-user-env # to set environment variables - - #SBATCH --partition=compute - #SBATCH --time=12:00:00 - #SBATCH --nodes=1 - #SBATCH --ntasks-per-node=12 - #SBATCH --cpus-per-task=1 - #SBATCH --mem=0 - #SBATCH --account=research-eemcs-me - #SBATCH --array=0-2 - - source activate f3dasm_env - - # Executing my python program with the jobid flag - python3 main.py --jobid=${SLURM_ARRAY_TASK_ID} - - -A few things to note: - -* Make sure that your main script is called `main.py` -* The script assumes that you are using `conda `_ to manage your python environment and the environment is called `f3dasm_env`. If you are not, you can remove the lines that activate the conda environment. -* The walltime, nodes and number of cores per node are for **each** of the jobs in the array-job. - -main.py -^^^^^^^ - -The `main.py` file is the main entry point of the project. It contains the :mod:`f3dasm` classes and acts on these interfaces. -It imports :mod:`f3dasm` and the `my_function` from `my_script.py`. -In the main function, we create the :class:`~f3dasm.design.Domain`, sample from the Latin Hypercube sampler , and executes the data generation function (`my_function`) using the :meth:`~f3dasm.ExperimentData.Experiment.evaluate` method with the specified execution mode. - -Additionally, the `main.py` file handles which node takes which role. - -.. code-block:: python - :caption: main.py - - from f3dasm import ExperimentData - from f3dasm.domain import make_nd_continuous_domain - from my_script import my_function - from time import sleep - - def create_experimentdata(): - """Design of Experiment""" - # Create a domain object - domain = make_nd_continuous_domain(bounds=np.tile([0.0, 1.0], (20, 1)), dimensionality=20) - - # Create the ExperimentData object - data = ExperimentData(domain=domain) - - # Sampling from the domain - data.sample(sampler='latin', n_samples=10) - - # Store the data to disk - data.store() - - def worker_node(): - # Extract the experimentdata from disk - data = f3dasm.ExperimentData.from_file(project_dir='.') - - """Data Generation""" - # Use the data-generator to evaluate the initial samples - data.evaluate(data_generator='Ackley', mode='cluster') - - - if __name__ is '__main__': - # Check the jobid of the current node - if f3dasm.HPC_JOBID == 0: - create_experimentdata() - worker_node() - elif f3dasm.HPC_JOBID > 0: - # Asynchronize the jobs in order to omit racing conditions - sleep(f3dasm.HPC_JOBID) - worker_node() - - - -Run the program -^^^^^^^^^^^^^^^ - -You can run the workflow by submitting the bash script to the HPC queue: - -.. tabs:: - - .. group-tab:: TORQUE - - .. code-block:: bash - - qsub pbsjob.sh -t 0-2 - - .. group-tab:: SLURM - - .. code-block:: bash - - sbatch pbsjob.sh - -For the TORQUE system, the :code:`-t 0-2` option submits an array job with 3 jobs with :code:`f3dasm.HPC_JOBID` of 0, 1 and 2. - - -.. _hydra-on-hpc: - -Running hydra on a high-performance computing cluster ------------------------------------------------------ - -.. _hydra: https://hydra.cc/ - -Combining the `hydra`_ and a high-performance computing network requires two small changes -* Adding the :code:`hpc.jobid` keyword to your configuration file -* Setting up concurrent logging - -Adding the jobid keyword to your configuration file -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Because the `hydra`_ :code:`config.yaml` file is handling command line flags, you have to add a :code:`hpc` keyword to the configuration file - -.. code-block:: yaml - :caption: config.yaml - - hpc: - jobid: -1 - -In your bash script, you have to overwrite this value with the :code:`++hpc.jobid` flag: - -.. tabs:: - - .. group-tab:: TORQUE - - .. code-block:: bash - - #!/bin/bash - # Torque directives (#PBS) must always be at the start of a job script! - #PBS -N ExampleScript - #PBS -q mse - #PBS -l nodes=1:ppn=12,walltime=12:00:00 - - # Make sure I'm the only one that can read my output - umask 0077 - - - # The PBS_JOBID looks like 1234566[0]. - # With the following line, we extract the PBS_ARRAYID, the part in the brackets []: - PBS_ARRAYID=$(echo "${PBS_JOBID}" | sed 's/\[[^][]*\]//g') - - module load use.own - module load miniconda3 - cd $PBS_O_WORKDIR - - # Here is where the application is started on the node - # activating my conda environment: - - source activate f3dasm_env - - # limiting number of threads - OMP_NUM_THREADS=12 - export OMP_NUM_THREADS=12 - - - # If the PBS_ARRAYID is not set, set it to None - if ! [ -n "${PBS_ARRAYID+1}" ]; then - PBS_ARRAYID=None - fi - - # Executing my python program with the jobid flag - python main.py ++hpc.jobid=${PBS_ARRAYID} hydra.run.dir=outputs/${now:%Y-%m-%d}/${JOB_ID} - - .. group-tab:: SLURM - - .. code-block:: bash - - #!/bin/bash -l - - #SBATCH -J "ExmpleScript" # name of the job (can be change to whichever name you like) - #SBATCH --get-user-env # to set environment variables - - #SBATCH --partition=compute - #SBATCH --time=12:00:00 - #SBATCH --nodes=1 - #SBATCH --ntasks-per-node=12 - #SBATCH --cpus-per-task=1 - #SBATCH --mem=0 - #SBATCH --account=research-eemcs-me - #SBATCH --array=0-2 - - source activate f3dasm_env - - # Executing my python program with the jobid flag - python3 main.py ++hpc.jobid=${SLURM_ARRAY_TASK_ID} hydra.run.dir=outputs/${now:%Y-%m-%d}/${SLURM_ARRAY_JOB_ID} - -In your `main.py` file, the :code:`hpc.jobid` keyword is now available from the `config.yaml` file: - -.. code-block:: python - :caption: main.py for hydra and HPC integration - - # Your f3dasm workflow - ... - - @hydra.main(config_path=".", config_name="config") - def main(config): - # Check the jobid of the current node - if config.hpc.jobid == 0: - create_experimentdata() - worker_node() - elif config.hpc.jobid > 0: - # Asynchronize the jobs in order to omit racing conditions - sleep(config.hpc.jobid) - worker_node() - -Setting up concurrent logging -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The `hydra`_ framework sets up the logging module and writes the log files to the :code:`hydra.run.dir` directory. -However, when multiple nodes log to the same file, the file can become corrupted due to concurrent writing. -In order to mitigate this, you have to overwrite the logging to file behaviour of the `hydra`_ framework. - -1. Create a `custom.yaml` file and its directories `hydra` and `job_logging` in your project as follows: - -.. code-block:: none - :caption: Directory Structure - - my_project/ - ├── hydra - | └─ job_logging - | └─ custom.yaml - ├── my_script.py - ├── pbsjob.sh - └── main.py - - -2. Inside the `custom.yaml` file, add the following lines: - -.. code-block:: yaml - :caption: custom.yaml - - # python logging configuration for tasks - version: 1 - formatters: - simple: - format: "[%(asctime)s][%(name)s][%(levelname)s] - %(message)s" - handlers: - console: - class: logging.StreamHandler - formatter: simple - stream: ext://sys.stdout - file: - class: f3dasm.DistributedFileHandler - formatter: simple - # absolute file path - filename: ${hydra.runtime.output_dir}/${hydra.job.name}.log - root: - level: INFO - handlers: [console, file] - - disable_existing_loggers: false - -.. note:: - - `hydra`_ is now using the custom :code:`f3dasm.DistributedFileHandler` class to write the log files. - -3. At the top of your `config.yaml` configuration file, add the following lines: - -.. code-block:: yaml - :caption: config.yaml - - defaults: - - override hydra/job_logging: custom diff --git a/docs/source/rst_doc_files/general/benchmark.rst b/docs/source/rst_doc_files/general/benchmark.rst new file mode 100644 index 00000000..9a986e1b --- /dev/null +++ b/docs/source/rst_doc_files/general/benchmark.rst @@ -0,0 +1,47 @@ +Studies +======= + +.. _TORQUE: https://adaptivecomputing.com/cherry-services/torque-resource-manager/ +.. _hydra: https://hydra.cc/docs/intro/ + +To get a feeling for a data-driven experiment, two benchmark studies are available to run with the :mod:`f3dasm` package. +In order to run a study, you need to have the ``f3dasm[benchmark]`` extra requirements installed + + .. code-block:: console + + pip install f3dasm[benchmark] + + +Folder structure and files of a study +------------------------------------- + + .. code-block:: none + :caption: Directory Structure + + ├── . + │ └── my_study + │ ├── main.py + │ ├── config.yaml + │ ├── pbsjob.sh + │ └── README.md + └── src/f3dasm + +* Each study is put in a separate folder +* The `README.md` file gives a description, author and optionally citable source. +* The main script that has to be called should be named `main.py` +* `pbsjob.sh` is a batchscript file that will submit the `main.py` file to a `TORQUE`_ high-performance queuing system. +* The `config.yaml` are `hydra`_ configuration files. + + +Available studies +----------------- + +There are two benchmark studies available: + ++---------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------+ +| Study | Description | ++=============================================================================================================================================+============================================================================+ +| `Fragile becomes supercompressible `_ | Designing a supercompressible meta-material | ++---------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------+ +| `Comparing optimization algorithms on benchmark functions `_ | Benchmark various optimization algorithms on analytical functions | ++---------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------+ diff --git a/docs/source/rst_doc_files/general/installation.rst b/docs/source/rst_doc_files/general/installation.rst index 53f00b9d..dcb2c4b4 100644 --- a/docs/source/rst_doc_files/general/installation.rst +++ b/docs/source/rst_doc_files/general/installation.rst @@ -1,7 +1,7 @@ .. _installation-instructions: -Installation instructions -========================= +Installation +============ There are different ways to install :mod:`f3dasm`: @@ -22,7 +22,7 @@ Installing the latest release :mod:`f3dasm` is purely Python code and compatible with: 1. Python 3.8 or higher. -2. the three major operations system (Linux, MacOS, Ubuntu). +2. the three major operations system (Windows, MacOS, Linux). 3. the `pip `_ package manager system. ---- @@ -116,7 +116,7 @@ Building from source is required to work on a contribution (bug fix, new feature 1. Use `Git `_ to check out the latest source from the `f3dasm repository `_ on - Github.: + Github: .. code-block:: console @@ -125,7 +125,7 @@ Building from source is required to work on a contribution (bug fix, new feature 2. Install a recent version of Python (3.7, 3.8, 3.9 or 3.10) - for instance using `Miniconda3 `_. or `Mamba `_. + for instance using `Miniconda3 `_ or `Mamba `_. If you installed Python with conda, we recommend to create a dedicated conda environment with all the build dependencies of f3dasm: diff --git a/docs/source/rst_doc_files/general/overview.rst b/docs/source/rst_doc_files/general/overview.rst index b488ef04..8d15aa45 100644 --- a/docs/source/rst_doc_files/general/overview.rst +++ b/docs/source/rst_doc_files/general/overview.rst @@ -3,71 +3,72 @@ Overview ======== -.. _design-of-experiments: https://f3dasm.readthedocs.io/en/latest/rst_doc_files/classes/design/experimentdata.html -.. _sampling: https://f3dasm.readthedocs.io/en/latest/rst_doc_files/classes/sampling/sampling.html -.. _optimizing: https://f3dasm.readthedocs.io/en/latest/rst_doc_files/classes/optimization/optimizers.html -.. _parallelizing: -.. _TORQUE system: https://hpc-wiki.info/hpc/Torque +A quick overview of the f3dasm package. -Summary -^^^^^^^ +---- -The process of structural and materials design involves a continuous search for the most efficient design based on specific criteria. -The different boundary conditions applied to the material or structure can result in vastly different optimal compositions. -In particular, the design of materials is faced with a high-dimensional solution space due to the overwhelming number of potential combinations that can create distinct materials. -Unfortunately, this extensive design space poses a significant challenge to accelerate the optimization process. -The immense number of possibilities makes it impractical to conduct experimental investigations of each concept. -As a result, data-driven computational analyses have been a method of interest for efficiently traversing these design spaces. +Conceptual framework +-------------------- -Most applied fields such as Mechanical Engineering have remained conservative when it comes to openly sharing databases and software. -This is in sharp contrast to the Computer Science community, especially within the sub-field of machine learning. -Understandably, the entry barrier is higher for researchers and practitioners from applied fields that are not specialized in software development. -In this work we developed a general and user-friendly data-driven package for researchers and practitioners working on design and analysis of materials and structures. -The package is called :mod:`f3dasm` (framework for data-driven design & analysis of structures and materials) and it aims at democratizing the data-driven process and making it easier to replicate research articles in this field, as well as sharing new work with the community. -This work generalizes the original closed-source framework proposed by the senior author and his co-workers [1]_, making it more flexible and adaptable to different applications, allowing to integrate different choices of software packages needed in the different steps of the data-driven process: (1) design of experiments; (2) data generation; (3) machine learning; and (4) optimization. +``f3dasm`` is a Python project that provides a general and user-friendly data-driven framework for researchers and practitioners working on the design and analysis of materials and structures. +The package aims to streamline the data-driven process and make it easier to replicate research articles in this field, as well as share new work with the community. -Statement of need -^^^^^^^^^^^^^^^^^ +In the last decades, advancements in computational resources have accelerated novel inverse design approaches for structures and materials. +In particular data-driven methods leveraging machine learning techniques play a major role in shaping our design processes today. -The effectiveness of the first published version of :mod:`f3dasm` framework has been demonstrated in various computational mechanics and materials studies, -such as the design of a super-compressible meta-material [2]_ and a spiderweb nano-mechanical resonator inspired -by nature and guided by machine learning [3]_. +Constructing a large material response database poses practical challenges, such as proper data management, efficient parallel computing and integration with third-party software. +Because most applied fields remain conservative when it comes to openly sharing databases and software, a lot of research time is instead being allocated to implement common procedures that would be otherwise readily available. +This lack of shared practices also leads to compatibility issues for benchmarking and replication of results by violating the FAIR principles. -The :mod:`f3dasm` framework is aimed at researchers who seek to develop such data-driven methods for structural & material modeling by incorporating modern machine learning tools. The framework integrates the following fields: +In this work we introduce an interface for researchers and practitioners working on design and analysis of materials and structures. +The package is called ``f3dasm`` (Framework for Data-driven Design \& Analysis of Structures and Materials). +This work generalizes the original closed-source framework proposed by the Bessa and co-workers [1]_, making it more flexible and adaptable to different applications, +namely by allowing the integration of different choices of software packages needed in the different steps of the data-driven process: -- Design of experiments, in which input variables describing the microstructure, structure, properties and external conditions of the system to be evaluated are determined and sampled; -- Data generation, typically through computational analysis, resulting in the creation of a material response database; -- Machine learning, in which a surrogate model is trained to fit experimental findings; -- Optimization, where we try to iteratively improve the model to obtain a superior design. +- **Design of experiments**, in which input variables describing the microstructure, properties and external conditions of the system are determined and sampled. +- **Data generation**, typically through computational analyses, resulting in the creation of a material response database. +- **Machine learning**, in which a surrogate model is trained to fit experimental findings. +- **Optimization**, where we try to iteratively improve the design -:mod:`f3dasm` is designed as a *modular* framework, consisting of both core and extended features, allowing user-specific instantiations of one or more steps from the above list. +.. image:: ../../img/data-driven-process.png + :align: center + :width: 100% -The *core* functionality of the framework comprises the following features: +| -- provide a way to parametrize experiments with the design-of-experiments functionalities; -- enabling experiment exploration by means of sampling and design optimization; -- provide the user tools for parallelizing their program and ordering their data; -- Allowing users to deploy experiments on high-performance computer systems (TORQUE Resource Manager). +---- -The *extensions* can be installed on the fly and contain the following features: +Computational framework +----------------------- -- provide various implementations to accommodate common data-driven workflows; -- adapter classes that link popular machine learning libraries to be used as implementations in :mod:`f3dasm`. +``f3dasm`` is an `open-source Python package `_ compatible with Python 3.8 or later. Some of the key features are: -The Python package includes extensive implementations for each of the provided fields and are organized in an object-oriented way. +- Modular design -.. [1] Bessa, M. A., Bostanabad, R., Liu, Z., Hu, A., Apley, D. W., Brinson, C., Chen, W., & Liu, W. K. (2017). - *A framework for data-driven analysis of materials under uncertainty: Countering the curse of dimensionality. - Computer Methods in Applied Mechanics and Engineering*, 320, 633-667. + - The framework introduces flexible interfaces, allowing users to easily integrate their own models and algorithms. + +- Automatic data management + + - The framework automatically manages I/O processes, saving you time and effort implementing these common procedures. + +- :doc:`Easy parallelization <../../auto_examples/005_workflow/001_cluster_computing>` + + - The framework manages parallelization of experiments, and is compatible with both local and high-performance cluster computing. -.. [2] Bessa, M. A., Glowacki, P., & Houlder, M. (2019). - *Bayesian machine learning in metamaterial design: - Fragile becomes supercompressible*. Advanced Materials, 31(48), 1904845. +- :doc:`Built-in defaults <../defaults>` -.. [3] Shin, D., Cupertino, A., de Jong, M. H., Steeneken, P. G., Bessa, M. A., & Norte, R. A. (2022). - *Spiderweb nanomechanical resonators via bayesian optimization: inspired by nature and guided by machine learning*. Advanced Materials, 34(3), 2106248. + - The framework includes a collection of :ref:`benchmark functions `, :ref:`optimization algorithms ` and :ref:`sampling strategies ` to get you started right away! +- :doc:`Hydra integration <../../auto_examples/006_hydra/001_hydra_usage>` + - The framework is integrated with `hydra `_ configuration manager, to easily manage and run experiments. + +Comprehensive `online documentation `_ is also available to assist users and developers of the framework. + + +.. [1] Bessa, M. A., Bostanabad, R., Liu, Z., Hu, A., Apley, D. W., Brinson, C., Chen, W., & Liu, W. K. (2017). + *A framework for data-driven analysis of materials under uncertainty: Countering the curse of dimensionality. + Computer Methods in Applied Mechanics and Engineering*, 320, 633-667. @@ -215,4 +216,3 @@ The Python package includes extensive implementations for each of the provided f .. References .. ---------- -.. .. bibliography:: \ No newline at end of file diff --git a/docs/source/rst_doc_files/hydra.rst b/docs/source/rst_doc_files/hydra.rst deleted file mode 100644 index 55980a9c..00000000 --- a/docs/source/rst_doc_files/hydra.rst +++ /dev/null @@ -1,139 +0,0 @@ -.. _hydra: https://hydra.cc/ - -Integration with `hydra`_ -========================= - -`hydra`_ is an open-source configuration management framework that is widely used in machine learning and other software development domains. -It is designed to help developers manage and organize complex configuration settings for their projects, -making it easier to experiment with different configurations, manage multiple environments, and maintain reproducibility in their work. - -`hydra`_ can be seamlessly integrated with the worfklows in :mod:`f3dasm` to manage the configuration settings for the project. - -Example -------- - -The following example is the same as in section :ref:`workflow`; we will create a workflow for the following data-driven process: - -* Create a 2D continuous :class:`~f3dasm.design.Domain` -* Sample from the domain using a the Latin-hypercube sampler -* Use a data generation function, which will be the ``"Ackley"`` function a from the :ref:`benchmark-functions` -* Optimize the data generation function using the built-in ``"L-BFGS-B"`` optimizer. - -.. image:: ../../../img/f3dasm-workflow-example.png - :width: 70% - :align: center - :alt: Workflow - -| - -Directory Structure -^^^^^^^^^^^^^^^^^^^ - -The directory structure for the project is as follows: - -- `my_project/` is the root directory. -- `my_script.py` contains the user-defined script. In this case a custom data-generationr function `my_function`. -- `config.yaml` is a hydraYAML configuration file. -- `main.py` is the main entry point of the project, governed by :mod:`f3dasm`. - - -.. code-block:: none - :caption: Directory Structure - - my_project/ - ├── my_script.py - ├── config.yaml - └── main.py - - -my_script.py -^^^^^^^^^^^^ - -The user-defined script is identical to the one in :ref:`my-script`. - -config.yaml -^^^^^^^^^^^ - -The `config.yaml` file contains the configuration settings for the project. -You can create configurations for each of the :mod:`f3dasm` classes: - -============================================================= ====================================================== -Class Section referencing how to create the `hydra`_ config -============================================================= ====================================================== -:class:`~f3dasm.design.Domain` :ref:`domain-from-yaml` -:class:`~f3dasm.ExperimentData` :ref:`experimentdata-hydra` -:class:`~f3dasm.optimization.Optimizer` to be implemented! -:class:`~f3dasm.datageneration.DataGenerator` to be implemented! -============================================================= ====================================================== - - - -.. code-block:: yaml - :caption: config.yaml - - domain: - x0: - type: float - low: 0.0 - high: 1.0 - x1: - type: float - low: 0.0 - high: 1.0 - - experimentdata: - from_sampling: - domain: ${domain} - sampler: 'latin' - seed: 1 - n_samples: 10 - - mode: sequential - -It specifies the search-space domain, sampler settings, and the execution mode (`sequential` in this case). -The domain is defined with `x0` and `x1` as continuous parameters with their corresponding lower and upper bounds. - -main.py -^^^^^^^ - -The `main.py` file is the main entry point of the project. It contains the :mod:`f3dasm` classes and acts on these interfaces. -It imports :mod:`f3dasm` and the `my_function` from `my_script.py`. - - -The `main.py` file is the main entry point of the project. - -* It imports the necessary modules (`f3dasm`, `hydra`) and the `my_function` from `my_script.py`. -* Inside `main.py` script defines a :code:`main` function decorated with :code:`@hydra.main`, which reads the configuration from :code:`config.yaml`. -* Within the :code:`main` function, we instantiate the :class:`~f3dasm.design.Domain`, sample from the Lating Hypercube sampler , and executes the data generation function (`my_function`) using the :meth:`~f3dasm.ExperimentData.Experiment.run` method with the specified execution mode. - - - -.. code-block:: python - :caption: main.py - - from f3dasm.design import ExperimentData - from f3dasm.datageneration.functions import Ackley - from f3dasm.optimization import LBFGSB - from my_script import my_function - - @hydra.main(config_path=".", config_name="config") - def main(config): - """Design of Experiment""" - # Create a domain object - domain = f3dasm.Domain.from_yaml(config.domain) - - # Sampling from the domain - data = f3dasm.ExperimentData.from_yaml(config) - - """Data Generation""" - # Use the data-generator to evaluate the initial samples - data.run(data_generator='ackley', mode=config.mode) - - """Optimization""" - data.optimize(data_generator="ackley", optimizer="lbfgsb", iterations=100) - - if __name__ == "__main__": - main() - -.. note:: - To use `hydra`_ on a high-performance computing cluster, take a look at the :ref:`hydra-on-hpc` section. diff --git a/docs/source/rst_doc_files/supercompressible.rst b/docs/source/rst_doc_files/supercompressible.rst deleted file mode 100644 index e78a255d..00000000 --- a/docs/source/rst_doc_files/supercompressible.rst +++ /dev/null @@ -1,316 +0,0 @@ -Fragile becomes supercompressible -################################# - -.. raw:: html - -
- Image 1 - Image 2 - Image 3 -
- -This study is based on the work of `Bessa et al. (2019) `_ -and aims to reproduce the results of the paper using the ``f3dasm`` package. - -Summary -======= - -For years, creating new materials has been a time-consuming effort that requires significant resources because we have -followed a trial-and-error design process. Now, a new paradigm is emerging where machine learning is used to design new -materials and structures with unprecedented properties. Using this data-driven process, a new super-compressible -meta-material was discovered despite being made of a fragile polymer. - -The above figure shows the newly developed meta-material prototype that was designed with the above-mentioned computational -data-driven approach and where experiments were used for validation, not discovery. This enabled the design and additive -manufacturing of a lightweight, recoverable and super-compressible meta-material achieving more than 90% compressive strain -when using a brittle base material that breaks at around 4% strain. Within minutes, the machine learning model was used to -optimize designs for different choices of base material, length-scales, and manufacturing process. Current results show that -super-compressibility is possible for optimized designs reaching stresses on the order of 1 kPa using brittle polymers, or -stresses on the order of 20 MPa using carbon-like materials. - -Design of experiments -===================== - -The supercompressible meta-material is parameterized by 5 geometric parameters and 2 material parameters. The geometry is -defined by the top and bottom diameters, :math:`D_1` and :math:`D_2`, the height :math:`P` and the cross-section parameters of the -vertical longerons: the cross-sectional area :math:`A`, moments of inertia :math:`I_x` and :math:`I_y`, and torsional constant :math:`J`. -The isotropic material is defined by its elastic constants: Young's modulus :math:`E` and shear modulus :math:`G`. - -.. raw:: html - -
- drawing -
- -Due to the principle of superposition, both the geometric and material parameters can be scaled by one of its dimensions/properties -(here :math:`D_1` and :math:`E`). Therefore, the variables that you will find in the dataset are: - -.. math:: - - \frac{D_1-D_2}{D_1},\ \frac{P}{D_1},\ \frac{I_x}{D_1^4},\ \frac{I_y}{D_1^4},\ \frac{J}{D_1^4},\ \frac{A}{D_1^2}, \frac{G}{E} - -+-------------------------------+-------------------------+ -| expression | parameter name | -+===============================+=========================+ -| :math:`\frac{D_1-D_2}{D_1}` | ``ratio_top_diameter`` | -+-------------------------------+-------------------------+ -| :math:`\frac{P}{D_1}` | ``ratio_pitch`` | -+-------------------------------+-------------------------+ -| :math:`\frac{I_x}{D_1^4}` | ``ratio_Ixx`` | -+-------------------------------+-------------------------+ -| :math:`\frac{I_y}{D_1^4}` | ``ratio_Iyy`` | -+-------------------------------+-------------------------+ -| :math:`\frac{J}{D_1^4}` | ``ratio_J`` | -+-------------------------------+-------------------------+ -| :math:`\frac{A}{D_1^2}` | ``ratio_area`` | -+-------------------------------+-------------------------+ -| :math:`\frac{G}{E}` | ``ratio_shear_modulus`` | -+-------------------------------+-------------------------+ - -This is a 7-dimensional problem and learning the response surface may require a significant amount of training points [#]_. -Therefore, you will also consider a simpler version of the problem in 3 dimensions, defined by constraining the longerons' -cross-section to be circular with diameter :math:`d`, and choosing a particular material, leading to the following 3 features: - -.. math:: - - \frac{d}{D_1}, \frac{D_2-D_1}{D_1},\ \frac{P}{D_1} - -+-------------------------------+-------------------------+ -| expression | parameter name | -+===============================+=========================+ -| :math:`\frac{D_1-D_2}{D_1}` | ``ratio_top_diameter`` | -+-------------------------------+-------------------------+ -| :math:`\frac{P}{D_1}` | ``ratio_pitch`` | -+-------------------------------+-------------------------+ -| :math:`\frac{d}{D_1}` | ``ratio_d`` | -+-------------------------------+-------------------------+ - -.. [#] Remember the "curse of dimensionality"! - -Contents of this folder -======================= - -+----------------+----------------------------------------------+ -| File/Folder | Description | -+================+==============================================+ -| ``main.py`` | Main script to run the experiment | -+----------------+----------------------------------------------+ -| ``config.yaml``| Configuration file for the experiment | -+----------------+----------------------------------------------+ -| ``README.md`` | Explanation of this experiment | -+----------------+----------------------------------------------+ -| ``img/`` | Folder with images used in this file | -+----------------+----------------------------------------------+ -| ``pbsjob.sh`` | TORQUE job file to run the experiment in a | -| | cluster | -+----------------+----------------------------------------------+ -| ``outputs/`` | Folder with the results of running this | -| | experiment | -+----------------+----------------------------------------------+ - -.. note:: - - The ``outputs/`` folder is created when the experiment has been run for the first time. - -Usage -===== - -Before running the experiment ------------------------------ - -1. Install the ``abaqus2py`` package. See `here `_ for instructions. -2. Change the ``config.yaml`` file to your liking. See `here <#explanation-of-configyaml-parameters>`_ for an explanation of the parameters. - -Running the experiment on your local machine --------------------------------------------- - -1. Navigate to this folder and run ``python main.py`` - -Running the experiment on a TORQUE cluster ------------------------------------------- - -1. Make sure you have an ``conda`` environment named ``f3dasm_env`` with the packages installed in the first step. -2. Navigate to this folder and submit the job with i.e. 2 nodes: ``qsub pbsjob.sh -t 0-2`` - -Results -======= - -Results are stored in a newly created ``outputs`` folder, with a subdirectory indicating the current date (e.g. ``2023-11-06``). - -* When running on a local machine, the output will be saved in a directory indicating the current time (e.g. ``13-50-14``). -* When running on a cluster, the output will be saved in a directory indicating the current job ID (e.g. ``538734.hpc06.hpc``). - -The following subdirectories are created: - -* ``experiment_data``: Contains the input, output, domain and jobs to construct the ``f3dasm.ExperimentData`` object. -* ``.hydra``: Contains the ``config.yaml`` file used to run the experiment. -* ``lin_buckle`` and ``riks``: Contain the ABAQUS simulation results for the linear buckling and Riks analysis, respectively. - -Lastly, a log file ``main.log`` is created. - -The folder structure is as follows: - -.. code-block:: text - - outputs/ - └── 2023-11-06/ - └── 13-50-14/ - ├── .hydra/ - ├── experiment_data/ - │ ├── domain.pkl - │ ├── input.csv - │ ├── output.csv - │ └── jobs.pkl - ├── lin_buckle/ - │ ├── 0/ - │ ├── 1/ - │ └── 2/ - ├── riks/ - │ ├── 0/ - │ ├── 1/ - │ └── 2/ - ├── loads/ - │ ├── 0.npy - │ ├── 1.npy - │ └── 2.npy - ├── max_disps/ - │ ├── 0.npy - │ ├── 1.npy - │ └── 2.npy - └── main.log - -Explanation of ``config.yaml`` parameters -========================================= - -There are two different configurations for this experiment: -- The full 7-dimensional problem as defined in the paper. -- The 3-dimensional problem, defined by constraining the longerons' cross-section to be circular with diameter :math:`d` and - choosing a fixed material. - -Common problem domain ---------------------- - -``young_modulus`` - Name Type Description - ------------- --------- --------------------------------- - ``value`` ``float`` Young's modulus value - -``n_longerons`` - Name Type Description - ------------- --------- --------------------------------- - ``value`` ``float`` Number of longerons in the design - -``bottom_diameter`` (:math:`D_2`) - Name Type Description - ------------- --------- --------------------------------- - ``value`` ``float`` Bottom diameter of the design - -``ratio_top_diameter`` (:math:`\frac{D_1-D_2}{D_1}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of top diameter ratio - ``high`` ``float`` Upper bound of top diameter ratio - -``ratio_pitch`` (:math:`\frac{P}{D_1}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of the pitch ratio - ``high`` ``float`` Upper bound of the pitch ratio - -3-dimensional problem domain ----------------------------- - -``ratio_d`` (:math:`\frac{d}{D_1}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of longerons cross-section - ``high`` ``float`` Upper bound of longerons cross-section - -``ratio_shear_modulus`` (:math:`\frac{G}{E}`) - Name Type Description - ------------- --------- --------------------------------- - ``value`` ``float`` Lower bound of shear modulus ratio - -7-dimensional problem domain ----------------------------- - -``ratio_area`` (:math:`\frac{A}{D_1^2}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of the area ratio - ``high`` ``float`` Upper bound of the area ratio - -``ratio_Ixx`` (:math:`\frac{I_x}{D_1^4}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of the :math:`I_{xx}` ratio - ``high`` ``float`` Upper bound of the :math:`I_{xx}` ratio - -``ratio_Iyy`` (:math:`\frac{I_y}{D_1^4}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of the :math:`I_{yy}` ratio - ``high`` ``float`` Upper bound of the :math:`I_{yy}` ratio - -``ratio_J`` (:math:`\frac{J}{D_1^4}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of the :math:`J` ratio - ``high`` ``float`` Upper bound of the :math:`J` ratio - -``ratio_shear_modulus`` (:math:`\frac{G}{E}`) - Name Type Description - ------------- --------- --------------------------------- - ``low`` ``float`` Lower bound of shear modulus ratio - ``high`` ``float`` Upper bound of shear modulus ratio - -``circular`` - Name Type Description - ------------- --------- --------------------------------- - ``value`` ``bool`` If the design is simplified or not - -Experiment Data ---------------- - -``from Sampling`` - Name Type Description - ------------- -------------------- --------------------------------- - ``sampler`` ``str`` Sampler name - ``seed`` ``int`` Seed value - ``n_samples`` ``int`` Number of samples - ``domain`` ``f3dasm.Domain`` ``f3dasm`` Domain object - -``mode`` - Name Type Description - ------------- --------- --------------------------------- - ``mode`` ``string`` Evaluation mode of ``f3dasm`` - -``hpc`` - Name Type Description - ------------- --------- --------------------------------- - ``jobid`` ``int`` Job ID of the array-job, automatically - overwritten by scheduler bash script - -.. [#] When running on a local machine, this value will be left as the default: -1. - -``imperfection`` - Name Type Description - ------------- --------- --------------------------------- - ``mean`` ``float`` Mean value of lognormal distribution - ``std`` ``float`` Standard deviation value of lognormal distribution - -``scripts`` - Name Type Description - ------------------ --------- --------------------------------------- - ``lin_buckle_pre`` ``str`` Absolute path of linear buckling script - ``lin_buckle_post````str`` Absolute path of linear buckling post-processing script - ``riks_pre`` ``str`` Absolute path of RIKS analysis script - ``riks_post`` ``str`` Absolute path of RIKS analysis post-processing script - -Logging -------- - -``log_level`` - Name Type Description - ------------- --------- --------------------------------- - ``log_level`` ``int`` Log level value diff --git a/examples/001_domain/001_domain_creation.py b/examples/001_domain/001_domain_creation.py index 5e6d6b32..fcc29c61 100644 --- a/examples/001_domain/001_domain_creation.py +++ b/examples/001_domain/001_domain_creation.py @@ -2,7 +2,7 @@ Introduction to domain and parameters ===================================== -This section will give you information on how to set up your search space with the :ref:`domain ` class and the :ref:`parameters ` +This section will give you information on how to set up your search space with the :class:`~f3dasm.design.Domain` class and the paramaters The :class:`~f3dasm.design.Domain` is a set of parameter instances that make up the feasible search space. """ ############################################################################### @@ -21,25 +21,25 @@ # Now we well add some input parameters: # There are four types of parameters that can be created: # -# - :ref:`floating point ` parameters +# - floating point parameters domain.add_float(name='x1', low=0.0, high=100.0) domain.add_float(name='x2', low=0.0, high=4.0) ############################################################################### -# - :ref:`discrete integer pramaters ` +# - discrete integer parameters domain.add_int(name='x3', low=2, high=4) domain.add_int(name='x4', low=74, high=99) ############################################################################### -# - :ref:`categorical parameters ` +# - categorical parameters domain.add_category(name='x5', categories=['test1', 'test2', 'test3', 'test4']) domain.add_category(name='x6', categories=[0.9, 0.2, 0.1, -2]) ############################################################################### -# - :ref:`constant parameters` +# - constant parameters domain.add_constant(name='x7', value=0.9) @@ -61,7 +61,6 @@ ############################################################################### # The :code:`to_disk` argument can be set to :code:`True` to store the output parameter on disk. A reference to the file is stored in the :class:`~f3dasm.ExperimentData` object. # This is useful when the output data is very large, or when the output data is an array-like object. -# More information on storing output can be found in :ref:`this section ` ############################################################################### # Filtering the domain diff --git a/examples/002_experimentdata/001_experimentdata.py b/examples/002_experimentdata/001_experimentdata.py index e86088eb..05e3b90c 100644 --- a/examples/002_experimentdata/001_experimentdata.py +++ b/examples/002_experimentdata/001_experimentdata.py @@ -9,17 +9,17 @@ The :class:`~f3dasm.ExperimentData` object consists of the following attributes: -- :ref:`domain `: The feasible :class:`~f3dasm.design.Domain` of the Experiment. Used for sampling and optimization. -- :ref:`input_data `: Tabular data containing the input variables of the experiments as column and the experiments as rows. -- :ref:`output_data `: Tabular data containing the tracked outputs of the experiments. -- :ref:`project_dir `: A user-defined project directory where all files related to your data-driven process will be stored. +- domain: The feasible :class:`~f3dasm.design.Domain` of the Experiment. Used for sampling and optimization. +- input_data: Tabular data containing the input variables of the experiments as column and the experiments as rows. +- output_data: Tabular data containing the tracked outputs of the experiments. +- project_dir: A user-defined project directory where all files related to your data-driven process will be stored. """ ############################################################################### # The :class:`~f3dasm.ExperimentData` object can be constructed in several ways: # -# You can construct a :class:`~f3dasm.ExperimentData` object by providing it :ref:`input_data `, -# :ref:`output_data `, a :ref:`domain ` object and a :ref:`project_dir `. +# You can construct a :class:`~f3dasm.ExperimentData` object by providing it input data, +# output data, a :class:`~f3dasm.design.Domain` object and a project directory. import numpy as np import pandas as pd diff --git a/examples/002_experimentdata/002_experimentdata_storing.py b/examples/002_experimentdata/002_experimentdata_storing.py index 40279fa2..39d66a39 100644 --- a/examples/002_experimentdata/002_experimentdata_storing.py +++ b/examples/002_experimentdata/002_experimentdata_storing.py @@ -10,7 +10,7 @@ # project directory # ^^^^^^^^^^^^^^^^^ # -# The ``project_dir`` argument is used to :ref:`store the ExperimentData to disk ` +# The ``project_dir`` argument is used to store the ExperimentData to disk # You can provide a string or a path to a directory. This can either be a relative or absolute path. # If the directory does not exist, it will be created. diff --git a/examples/005_workflow/001_cluster_computing.py b/examples/005_workflow/001_cluster_computing.py index 54910178..9400a330 100644 --- a/examples/005_workflow/001_cluster_computing.py +++ b/examples/005_workflow/001_cluster_computing.py @@ -23,11 +23,11 @@ from f3dasm.design import make_nd_continuous_domain ############################################################################### -# The following example is the same as in section :ref:`workflow`; only now we are omiting the optimization part and only parallelize the data generation: +# We will create the following data-driven process: # # * Create a 20D continuous :class:`~f3dasm.design.Domain` # * Sample from the domain using a the Latin-hypercube sampler -# * With multiple nodes; use a data generation function, which will be the ``"Ackley"`` function a from the :ref:`benchmark-functions` +# * With multiple nodes; use a data generation function, which will be the ``"Ackley"`` function a from the benchmark functions # # # .. image:: ../../img/f3dasm-workflow-example-cluster.png diff --git a/examples/006_hydra/002_cluster_hydra.py b/examples/006_hydra/002_cluster_hydra.py index 114862a2..276784d9 100644 --- a/examples/006_hydra/002_cluster_hydra.py +++ b/examples/006_hydra/002_cluster_hydra.py @@ -6,11 +6,11 @@ """ ############################################################################### # -# The following example is the same as in section :ref:`workflow`; we will create a workflow for the following data-driven process: +# The following example is the same as in workflow section; we will create a workflow for the following data-driven process: # # * Create a 2D continuous :class:`~f3dasm.design.Domain` # * Sample from the domain using a the Latin-hypercube sampler -# * Use a data generation function, which will be the ``"Ackley"`` function a from the :ref:`benchmark-functions` +# * Use a data generation function, which will be the ``"Ackley"`` function a from the benchmark functions # # .. image:: ../../img/f3dasm-workflow-example-cluster.png # :width: 70% diff --git a/paper/data-driven-process.png b/paper/data-driven-process.png new file mode 100644 index 00000000..c6be6a30 Binary files /dev/null and b/paper/data-driven-process.png differ diff --git a/paper/f3dasm-blocks.png b/paper/f3dasm-blocks.png deleted file mode 100644 index 071ae942..00000000 Binary files a/paper/f3dasm-blocks.png and /dev/null differ diff --git a/paper/f3dasm-blocks.svg b/paper/f3dasm-blocks.svg deleted file mode 100644 index 3d0787ad..00000000 --- a/paper/f3dasm-blocks.svg +++ /dev/null @@ -1,1845 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - Design - - - - - - - - - - - - - - - - - - - - - - - - Continuous - - - - - - - Categorical - - - - - - - Discrete - - - - - - - - - - - Parameter - - - - - - - - - Machine - - - - - - - Learning - - - - - - - - - - - Model - - - - - - - - - - Regressor - - - - - - - MLP - - - - - - - - - Sampling - - - - - - - - - - - Sampler - - - - - - - - - - LHS - - - - - - - - - - - - - - - sampling - - - - - - - Sobol sequence - - - - - - - Random uniform - - - - - - - - - - - - 1. Design of experiments - - - - - - - - - Optimization - - - - - - - - - - - Optimizer - - - - - - - - - - Gradient - - - - - - - - - - - - - - - based - - - - - - - Gradient - - - - - - - - - - - - - - - free - - - - - - - 3. Machine learning - - - - - - - - - Simulation - - - - - - - - - - - Simulator - - - - - - - - - - Abaqus - - - - - - - Benchmark functions - - - - - - - - - - - - 2. Data generation - - - - - - - - Run optimization on - - - - - - - benchmark functions - - - - - - - - Abaqus simulation on - - - - - - - RVE - - - - - - - - - - - - - - - - - module - - - - - - - interface - - - - - - - implementation - - - - - - - - study - - - - - - - - - - - - - - - - - - 4. Optimization - - - - - - - DesignSpace - - - - - - - - - - - - - - module - - - - - interface - - - implementation - - - - study - - - diff --git a/paper/f3dasm-example.svg b/paper/f3dasm-example.svg deleted file mode 100644 index 51e62cf9..00000000 --- a/paper/f3dasm-example.svg +++ /dev/null @@ -1,665 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - Run optimization on - - - - - - - benchmark functions - - - - - - - - - 20D continuous - - - - - - - space - - - - - - - - - Latin - - - - - - - - - - - - - - - Hypercube - - - - - - - sampling - - - - - - - - - Ackley - - - - - - - benchmark - - - - - - - function - - - - - - - - - CMAES - - - - - - - - - - - design - - - - - - - sampling - - - - - - - machine learning - - - - - - - optimization - - - - - - - diff --git a/paper/f3dasm-gitbranching.png b/paper/f3dasm-gitbranching.png deleted file mode 100644 index d0153a66..00000000 Binary files a/paper/f3dasm-gitbranching.png and /dev/null differ diff --git a/paper/f3dasm-gitbranching.svg b/paper/f3dasm-gitbranching.svg deleted file mode 100644 index 81b25e66..00000000 --- a/paper/f3dasm-gitbranching.svg +++ /dev/null @@ -1,934 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - main - pr/v1.1.0 - - - feature1 - feature2 - - - - - - - - - - - - - - - - - - - - - v1.0.0 - v1.1.0 - - - v1.1.1 - - - pr/v1.2.0 - - - - - feature3 - - - - - hotfix - pr-merge - pr-merge - main-merge - - - - - commit - pull-request - - v1.1.0 - tag - - - diff --git a/paper/f3dasm_bessa_overview.png b/paper/f3dasm_bessa_overview.png deleted file mode 100644 index c11c14ce..00000000 Binary files a/paper/f3dasm_bessa_overview.png and /dev/null differ diff --git a/paper/f3dasm_bessa_overview.svg b/paper/f3dasm_bessa_overview.svg deleted file mode 100644 index b79e67ee..00000000 --- a/paper/f3dasm_bessa_overview.svg +++ /dev/null @@ -1,6360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - M.A.Bessaetal./Comput.MethodsAppl.Mech.Engrg.320(2017)633–667 - - - 635 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fig.1. - - - Schematicofglobalframeworkfordata-drivenmaterialsystemsdesign/modeling. - - - threetypesofdescriptorsareidentifedforthedesignofmaterialsystems:microstructural(geometric)descriptors, - materialpropertydescriptors,andexternal(boundary)conditionsdescriptors. - Theabovedescriptorscontaininformationaboutthematerialbuildingblocksandrespectiveproperties,as - wellastheirgeometricassemblyintomaterialmicrostructures.Thefrstsetofsuchdescriptorsisidentifedvia - microstructurecharacterization[ - 22 - – - 25 - ]andoncedefned,onecanassignspecifcmaterialpropertiesandasetof - boundaryconditions,asillustratedinBox1of - Fig.1 - .Eachmicrostructureischaracterizedbyarepresentativevolume - element - - - 3 - - - (RVE)ormultiplestatisticalvolumeelements - - - 4 - - - (SVEs)thataregeneratedbyafxedsetofmicrostructural - descriptors. - Oncethedescriptorsareselectedtheyformagroupofinputdesignvariables - - - x - - - fortheproblem, - - - x - - - = - - - [ - - - x - - - 1 - - - ,..., - - - x - - - i - - - ,..., - - - x - - - d - - - in - - - ] - (1) - where - - - d - - - in - - - indicatesthetotalnumberofinputdesignvariables - - - x - - - considered.Thesevariablesincludealltheabove - mentioneddescriptors.Forexample,iftheidentifedmicrostructuredescriptorswerevolumefraction - - - V - - - f - - - andnumber - - - - - - 3 - - - Arepresentativevolumeelement(RVE)isamaterialdomainrandomlygeneratedforafxedsetofmaterialdescriptorsthathasthesame - homogenizedbehaviorindependentlyoftherandomizationprocess.Inotherwords,thestochasticityofthemicrostructureisnotrefectedonits - macroscopicresponse. - - - 4 - - - Astatisticalvolumeelement(SVE)isamaterialdomainthatisnotsuffcientlylargetoobtainthesamemacroscopicmaterialresponse. - - - - diff --git a/paper/f3dasm_logo_long.png b/paper/f3dasm_logo_long.png new file mode 100644 index 00000000..369cfadb Binary files /dev/null and b/paper/f3dasm_logo_long.png differ diff --git a/paper/paper.bib b/paper/paper.bib index 5a09b577..76357b1d 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -1,26 +1,3 @@ -@Article{Aage2017, - author = {Aage, Niels and Andreassen, Erik and Lazarov, Boyan S and Sigmund, Ole}, - journal = {Nature}, - title = {Giga-voxel computational morphogenesis for structural design}, - year = {2017}, - number = {7674}, - pages = {84--86}, - volume = {550}, - doi = {10.1038/nature23911}, - publisher = {Nature Publishing Group}, -} - -@MastersThesis{Schelling2021, - author = {Martin van der Schelling}, - school = {Delft University of Technology}, - title = {A data-driven heuristic decision strategy for data-scarce optimization: with an application towards bio-based composites}, - year = {2021}, - month = mar, - type = {mathesis}, - abstract = {Algorithmic optimization is a viable tool for solving complex materials engineering issues. In this study, a data-scarce Bayesian optimization model was developed to research the composition of bio-based composites. The proof-of-concept program adjusts the natural materials' weight ratios to optimize towards user-defined mechanical properties. Preliminary results show that the bio-composites proposed by the program had improved properties compared to existing bulk-moulding compounds. However, the algorithm choice is often arbitrary or based on anecdotal evidence. In parallel, this thesis proposed a data-driven framework for general data-scarce optimization problems to adapt the meta-heuristic during optimization. Guided by the 'No Free Lunch' theorem, we verified that the effectiveness over a selection of algorithms is dependent on problem-specific features and convergence. This effectiveness was captured in a unique identifier metric by optimizing a generated training set of optimization problems. The average solution quality was improved by combining several meta-heuristics in series, based on these problem-specifics. During the optimization of problems in the testing set, the same unique identifier was constructed at predefined stages in the optimization process. Subsequently, the problem was classified, and the meta-heuristic was adapted to the best-performing algorithm based on similar training samples. Experiments with various classifiers and a different number of predefined assessment stages were performed. Results show that the data-driven heuristic decision strategy outperformed the individual optimizers on the testing set. Despite the use of binarization techniques, the classification accuracy was heavily influenced by the imbalanced training set. In terms of computational resources, the various adaptions of the data-driven heuristic strategy are 2.5 times faster in runtime compared to the best-performing meta-heuristic Bayesian Optimization. Lastly, the framework was benchmarked against the 'learning to optimize' study and shows excellent performance on the logistic regression problem compared to the autonomous optimizer. In conclusion, it has been shown that even with the limited information of black-box optimization problems, data-driven optimization effectively improves the current standard of materials engineering processes.}, - url = {https://repository.tudelft.nl/islandora/object/uuid:d58271d6-21bb-470c-a5ee-4584b3b8ee29?collection=education}, -} - @Article{Bessa2017, author = {Bessa, Miguel and Bostanabad, R. and Liu, Z. and Hu, A. and Apley, Daniel W. and Brinson, C. and Chen, W. and Liu, Wing Kam}, journal = {Computer Methods in Applied Mechanics and Engineering}, @@ -36,29 +13,62 @@ @Article{Bessa2017 keywords = {Design of experiments, Machine learning and data mining, Plasticity, Reduced order model, Self-consistent clustering analysis}, } -@Article{Bessa2019, - author = {Bessa, Miguel and Glowacki, Piotr and Houlder, Michael}, - journal = {Advanced Materials}, - title = {{Bayesian Machine Learning in Metamaterial Design: Fragile Becomes Supercompressible}}, - year = {2019}, - issn = {15214095}, - number = {48}, - pages = {1--6}, - volume = {31}, - abstract = {Designing future-proof materials goes beyond a quest for the best. The next generation of materials needs to be adaptive, multipurpose, and tunable. This is not possible by following the traditional experimentally guided trial-and-error process, as this limits the search for untapped regions of the solution space. Here, a computational data-driven approach is followed for exploring a new metamaterial concept and adapting it to different target properties, choice of base materials, length scales, and manufacturing processes. Guided by Bayesian machine learning, two designs are fabricated at different length scales that transform brittle polymers into lightweight, recoverable, and supercompressible metamaterials. The macroscale design is tuned for maximum compressibility, achieving strains beyond 94% and recoverable strengths around 0.1 kPa, while the microscale design reaches recoverable strengths beyond 100 kPa and strains around 80%. The data-driven code is available to facilitate future design and analysis of metamaterials and structures (https://github.com/mabessa/F3DAS).}, - doi = {10.1002/adma.201904845}, - file = {:home/martin/Documents/Mendeley Desktop/Bessa, Glowacki, Houlder - 2019 - Bayesian Machine Learning in Metamaterial Design Fragile Becomes Supercompressible.pdf:pdf}, - keywords = {additive manufacturing, data-driven design, deep learning, machine learning, optimization}, +@article{Sundberg2022, + doi = {10.21105/joss.04364}, + url = {https://doi.org/10.21105/joss.04364}, + year = {2022}, + publisher = {The Open Journal}, + volume = {7}, + number = {75}, + pages = {4364}, + author = {Jack D. Sundberg and Siona S. Benjamin and Lauren M. McRae and Scott C. Warren}, + title = {Simmate: a framework for materials science}, + journal = {Journal of Open Source Software} } -@Article{Shin2022, - author = {Shin, Dongil and Cupertino, Andrea and de Jong, Matthijs HJ and Steeneken, Peter G and Bessa, Miguel A and Norte, Richard A}, - journal = {Advanced Materials}, - title = {Spiderweb nanomechanical resonators via bayesian optimization: inspired by nature and guided by machine learning}, - year = {2022}, - number = {3}, - pages = {2106248}, - volume = {34}, - eprint = {2108.04809}, - publisher = {Wiley Online Library}, +@article{Pietka2022, + doi = {10.21105/joss.04719}, + url = {https://doi.org/10.21105/joss.04719}, + year = {2022}, + publisher = {The Open Journal}, + volume = {7}, + number = {79}, + pages = {4719}, + author = {Isabel Pietka and Ralf Drautz and Thomas Hammerschmidt}, + title = {strucscan: A lightweight Python-based framework for high-throughput material simulation}, + journal = {Journal of Open Source Software} } + +@article{Biscani2020, + doi = {10.21105/joss.02338}, + url = {https://doi.org/10.21105/joss.02338}, + year = {2020}, + publisher = {The Open Journal}, + volume = {5}, + number = {53}, + pages = {2338}, + author = {Francesco Biscani and Dario Izzo}, + title = {A parallel global multiobjective framework for optimization: pagmo}, + journal = {Journal of Open Source Software} +} + +@inproceedings{Akiba2019, + title={Optuna: A next-generation hyperparameter optimization framework}, + author={Akiba, Takuya and Sano, Shotaro and Yanase, Toshihiko and Ohta, Takeru and Koyama, Masanori}, + booktitle={Proceedings of the 25th ACM SIGKDD international conference on knowledge discovery \& data mining}, + pages={2623--2631}, + year={2019} +} + +@article{Ferreira2023, + doi = {10.21105/joss.05594}, + url = {https://doi.org/10.21105/joss.05594}, + year = {2023}, + publisher = {The Open Journal}, + volume = {8}, + number = {87}, + pages = {5594}, + author = {Bernardo P. Ferreira and F. M. Andrade Pires and Miguel A. Bessa}, + title = {CRATE: A Python package to perform fast material simulations}, + journal = {Journal of Open Source Software} +} \ No newline at end of file diff --git a/paper/paper.md b/paper/paper.md index cbc6b9ba..881e252e 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -1,5 +1,5 @@ --- -title: 'F3DASM: open Framework for Data-Driven Design & Analysis of Structures & Materials' +title: 'f3dasm: Framework for Data-Driven Design & Analysis of Structures & Materials' tags: - Python - data-driven @@ -7,142 +7,56 @@ tags: - framework - machine learning authors: - - name: Martin van der Schelling + - name: M. P. van der Schelling orcid: 0000-0003-3602-0452 - # equal-contrib: true - affiliation: 1 # (Multiple affiliations must be quoted) - - name: Deepesh Toshniwal - orcid: 0000-0002-7142-7904 affiliation: 1 - - name: Miguel Bessa + - name: B.P. Ferreira + orcid: 0000-0001-5956-3877 + affiliation: 2 + - name: M. A. Bessa + corresponding: true orcid: 0000-0002-6216-0355 affiliation: 2 -# - name: Author Without ORCID -# equal-contrib: true # (This is how you can denote equal contributions between multiple authors) -# affiliation: 2 -# - name: Author with no affiliation -# corresponding: true # (This is how to denote the corresponding author) -# affiliation: 3 affiliations: - - name: Delft University of Technology, Materials Science, the Netherlands + - name: Materials Science & Engineering, Delft University of Technology, the Netherlands index: 1 - - name: Brown University, United States of America + - name: School of Engineering, Brown University, United States of America index: 2 -# - name: Independent Researcher, Country -# index: 3 -date: 7 April 2023 +date: 31 May 2024 bibliography: paper.bib -# Optional fields if submitting to a AAS journal too, see this blog post: -# https://blog.joss.theoj.org/2018/12/a-new-collaboration-with-aas-publishing -# aas-doi: 10.3847/xxxxx <- update this with the DOI from AAS once you know it. -# aas-journal: Astrophysical Journal <- The name of the AAS journal. --- # Summary - - -The process of structural and materials design involves a continuous search for the most efficient design based on specific criteria. The different boundary conditions applied to the material or structure can result in vastly different optimal compositions. In particular, the design of materials is faced with a high-dimensional solution space due to the overwhelming number of potential combinations that can create distinct materials. Unfortunately, this extensive design space poses a significant challenge to accelerate the optimization process. The immense number of possibilities makes it impractical to conduct experimental investigations of each concept. As a result, data-driven computational analyses have been a method of interest for efficiently traversing these design spaces. - -Most applied fields such as Mechanical Engineering have remained conservative when it comes to openly sharing databases and software [can you find a couple of references backing this up?]. This is in sharp contrast to the Computer Science community, especially within the sub-field of machine learning. Understandably, the entry barrier is higher for researchers and practitioners from applied fields that are not specialized in software development. In this work we developed a general and user-friendly data-driven package for researchers and practitioners working on design and analysis of materials and structures. The package is called `f3dasm` (framework for data-driven design & analysis of structures and materials) and it aims at democratizing the data-driven process and making it easier to replicate research articles in this field, as well as sharing new work with the community. This work generalizes the original closed-source framework proposed by the senior author and his co-workers [@Bessa2017], making it more flexible and adaptable to different applications, allowing to integrate different choices of software packages needed in the different steps of the data-driven process: (1) design of experiments; (2) data generation; (3) machine learning; and (4) optimization. - - - - +![Logo of [`f3dasm`](https://github.com/bessagroup/f3dasm). \label{fig:f3dasm_logo}](f3dasm_logo_long.png) # Statement of need - - -The use of state-of-the-art data-driven methods for innovative structural and materials design has demonstrated their potential in various studies [@Aage2017; @Schelling2021]. The `f3dasm` framework is aimed at researchers who seek to develop such data-driven methods for structural & material modeling by incorporating modern machine learning tools. The framework integrates the following fields: - -- Design of experiments, in which input variables describing the microstructure, structure, properties and external conditions of the system to be evaluated are determined and sampled; -- Data generation, typically through computational analysis, resulting in the creation of a material response database; -- Machine learning, in which a surrogate model is trained to fit experimental findings; -- Optimization, where we try to iteratively improve the model to obtain a superior design. - -`f3dasm` is designed as a *modular* framework, consisting of both core and extended features, allowing user-specific instantiations of one or more steps from the above list. - -The *core* functionality of the framework comprises the following features: - -- provide a way to parametrize experiments with the design-of-experiments functionalities; -- enabling experiment exploration by means of sampling and design optimization; -- provide the user tools for parallelizing their program and ordering their data; -- Allowing users to deploy experiments on high-performance computer systems (TORQUE Resource Manager). - -The *extensions* can be installed on the fly and contain the following features: - -- provide various implementations to accommodate common data-driven workflows; -- adapter classes that link popular machine learning libraries to be used as implementations in `f3dasm`. - -The Python package includes extensive implementations for each of the provided fields and are organized in an object-oriented way. \autoref{fig:f3dasm-blocks} illustrates the package organization and levels of abstraction. Studies represent reproducible programs that uses the data-driven process with specific implementations in a script-like manner. - -![Overview of the different layers of abstraction in the `f3dasm` package.\label{fig:f3dasm-blocks}](f3dasm-blocks.png) - -The effectiveness of the pre-released version of the `f3dasm` framework [@Bessa2017] has been demonstrated in various computational mechanics and materials studies, such as the design of a super-compressible meta-material [@Bessa2019] and a spiderweb nano-mechanical resonator inspired by nature and guided by machine learning [@Shin2022]. - - - - - - - - - - - - - - -# Documentation and collaborative development - -To improve the usability of the `f3dasm` frame­work, thorough documentation has been included with the Sphinx package. Documentation for this package can be accessed on [the homepage](https://f3dasm.readthedocs.io/en/latest/) and will be kept up to date with the latest release of the package. - -The `f3dasm` framework relies on the collaborative efforts of scientists and developers to expand its capabilities. Therefore, it is essential to have a well-defined software development process in place. This is achieved by maintaining strict branching policies, and incorporating comprehensive testing suites and automatic continuous integration with GitHub Workflows. - - +![Illustration of the `f3dasm` data-driven process. \label{fig:data-driven-process}](data-driven-process.png) -# Availability +[`f3dasm`](https://github.com/bessagroup/f3dasm) is an [open-source Python package](https://pypi.org/project/f3dasm/) compatible with Python 3.8 or later. The library includes a suite of benchmark functions, optimization algorithms, and sampling strategies to serve as default implementations. Furthermore, [`f3dasm`](https://github.com/bessagroup/f3dasm) offers automatic data management for experiments, easy integration with high-performance computing systems, and compatibility with the hydra configuration manager. Comprehensive [online documentation](https://f3dasm.readthedocs.io/en/latest/) is also available to assist users and developers of the framework. -`f3dasm` and its extensions are available as a `pip` package and is compatible with Python 3.8 to 3.10 and all major operating systems (Linux, MacOS and Windows). Detailed installation instructions can be found on the ['Getting Started'](https://f3dasm.readthedocs.io/en/latest/rst_doc_files/general/gettingstarted.html) documentation page. +In a similar scope, it is worth mentioning the projects [simmate](https://github.com/jacksund/simmate) [@Sundberg2022] and [strucscan](https://github.com/ICAMS/strucscan), as they provide tools for the management of materials science simulation and databases. However, these projects focus on the generation and retrieval of materials properties and do not include machine learning or optimization interfaces. In recent years, numerous optimization frameworks have been developed to facilitate data-driven design. [Optuna](https://optuna.org/) is a hyperparameter optimization framework that combines a variety of optimization algorithms with dynamically constructed search space [@Akiba2019] and [pygmo](https://github.com/esa/pagmo2) provides unified interfaces for parallel global optimization [@Biscani2020]. Interfaces to these and many other optimization frameworks have been integrated into a separate package [`f3dasm_optimize`](https://github.com/bessagroup/f3dasm_optimize), and can be used in conjunction with [`f3dasm`](https://github.com/bessagroup/f3dasm). # Acknowledgements -We would express our gratitude to Jiaxiang Yi for his contributions to writing an interface with Abaqus simulation software. +We would express our gratitude to Jiaxiang Yi for his contributions to writing an interface with the ABAQUS simulation software and to Deepesh Toshniwal for providing valuable feedback. -# References \ No newline at end of file +# References diff --git a/setup.cfg b/setup.cfg index 8a2f9446..6eccf5fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = f3dasm version = file: VERSION -description = f3dasm - Framework for Data-driven development and Analysis of Structures and Materials +description = f3dasm - Framework for Data-driven Development and Analysis of Structures and Materials long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/bessagroup/f3dasm diff --git a/src/f3dasm/__init__.py b/src/f3dasm/__init__.py index fe9c2c53..7dee4105 100644 --- a/src/f3dasm/__init__.py +++ b/src/f3dasm/__init__.py @@ -19,9 +19,6 @@ from ._src.experimentdata.experimentdata import ExperimentData from ._src.experimentdata.experimentsample import ExperimentSample from ._src.logger import DistributedFileHandler, logger -from ._src.run_optimization import (OptimizationResult, calculate_mean_std, - run_multiple_realizations, - run_optimization) # Authorship and Credits # ============================================================================= @@ -40,10 +37,5 @@ 'ExperimentSample', 'DistributedFileHandler', 'logger', - 'OptimizationResult', - 'run_multiple_realizations', - 'run_optimization', 'HPC_JOBID', - 'calculate_mean_std', - 'StoreProtocol', ] diff --git a/src/f3dasm/__version__.py b/src/f3dasm/__version__.py index 2d411c18..6934fce6 100644 --- a/src/f3dasm/__version__.py +++ b/src/f3dasm/__version__.py @@ -1 +1 @@ -__version__: str = "1.4.8" +__version__: str = "1.5.0" diff --git a/src/f3dasm/_src/datageneration/__init__.py b/src/f3dasm/_src/datageneration/__init__.py index 233b0c12..bcd5928a 100644 --- a/src/f3dasm/_src/datageneration/__init__.py +++ b/src/f3dasm/_src/datageneration/__init__.py @@ -8,7 +8,6 @@ from typing import List # Local -from .abaqus.abaqus_simulator import AbaqusSimulator from .datagenerator import DataGenerator from .functions import pybenchfunction from .functions.adapters.augmentor import (FunctionAugmentor, Noise, Offset, @@ -41,6 +40,5 @@ 'Offset', 'Scale', 'DATAGENERATORS', - 'AbaqusSimulator', *pybenchfunction.__all__ ] diff --git a/src/f3dasm/_src/datageneration/abaqus/__init__.py b/src/f3dasm/_src/datageneration/abaqus/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/f3dasm/_src/datageneration/abaqus/abaqus_functions.py b/src/f3dasm/_src/datageneration/abaqus/abaqus_functions.py deleted file mode 100644 index 348e7383..00000000 --- a/src/f3dasm/_src/datageneration/abaqus/abaqus_functions.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -This file contains the functions that are used to run Abaqus simulations. - -Note ----- -I'm assuming that every process could have a different pre-processing, -execution and post-processing function. -But they are used in that order and only once. -If multiple e.g. pre_process steps need to be taken, then they should -be combined in one function. -""" - -# Modules -# ============================================================================= - -# Standard -import os -import pickle -from pathlib import Path - -# Local -from ...experimentdata.experimentsample import ExperimentSample - -# Authorship & Credits -# ============================================================================= -__author__ = "Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)" -__credits__ = ["Martin van der Schelling"] -__status__ = "Alpha" -# ============================================================================= -# -# ============================================================================= - - -def pre_process( - experiment_sample: ExperimentSample, folder_path: str, - python_file: str, function_name: str = "main", name: str = "job", - remove_temp_files: bool = True, **kwargs) -> None: - """Function that handles the pre-processing of Abaqus with a Python script - - Parameters - ---------- - experiment_sample : ExperimentSample - The design to run the data generator on. Will be handled by - the pipeline. - folder_path : str - Path of the folder where the python script is located - python_file : str - Name of the python file to be executed - function_name : str, optional - Name of the function within the python file to be executed, - by default "main" - name : str, optional - Name of the job, by default "job" - remove_temp_files : bool, optional - - Note - ---- - The python file should create an .inp input-file based on the - information of the experiment sample named .inp. - """ - - sim_info = kwargs - working_dir = Path("datageneration") / \ - Path(f"{name}_{experiment_sample.job_number}") - - # Updating simulation info with experiment sample data - sim_info.update(experiment_sample.to_dict()) - - filename = working_dir / "sim_info.pkl" - with open(filename, "wb") as fp: - pickle.dump(sim_info, fp, protocol=0) - - with open(f"{working_dir / 'preprocess.py'}", "w") as f: - f.write("import os\n") - f.write("import sys\n") - f.write("import pickle\n") - f.write(f"sys.path.extend([r'{folder_path}'])\n") - f.write( - f"from {python_file} import {function_name}\n" - ) - f.write(f"with open(r'{filename}', 'rb') as f:\n") - f.write(" dict = pickle.load(f)\n") - f.write(f"os.chdir(r'{working_dir}')\n") - f.write(f"{function_name}(dict)\n") - - os.system(f"abaqus cae noGUI={working_dir / 'preprocess.py'} -mesa") - - if remove_temp_files: - Path(working_dir / "preprocess.py").unlink(missing_ok=True) - Path(working_dir / "sim_info.pkl").unlink(missing_ok=True) - - -def post_process( - experiment_sample: ExperimentSample, folder_path: str, - python_file: str, function_name: str = "main", name: str = "job", - remove_temp_files: bool = True, **kwargs) -> None: - """Function that handles the post-processing of Abaqus with a Python script - - Parameters - ---------- - experiment_sample : ExperimentSample - The design to run the data generator on. - Will be handled by the pipeline. - folder_path : str - Path of the folder where the python script is located - python_file : str - Name of the python file to be executed - function_name : str, optional - Name of the function within the python file to be executed, - by default "main" - name : str, optional - Name of the job, by default "job" - remove_temp_files : bool, optional - Whether to remove the temporary files, by default True - - Note - ---- - The post-processing python file should write the results of your - simulation to a pickle file - with the name: results.pkl. This file will be handled by the pipeline. - """ - - working_dir = Path("datageneration") / \ - Path(f"{name}_{experiment_sample.job_number}") - - with open(f"{working_dir / 'post.py'}", "w") as f: - f.write("import os\n") - f.write("import sys\n") - f.write("from abaqus import session\n") - f.write(f"sys.path.extend([r'{folder_path}'])\n") - f.write( - f"from {python_file} import {function_name}\n" - ) - f.write( - f"odb = session.openOdb(\ - name=r'{working_dir / str(experiment_sample.job_number)}\ - .odb')\n") - f.write(f"os.chdir(r'{working_dir}')\n") - f.write(f"{function_name}(odb)\n") - - os.system(f"abaqus cae noGUI={working_dir / 'post.py'} -mesa") - if remove_temp_files: - Path(working_dir / "post.py").unlink(missing_ok=True) diff --git a/src/f3dasm/_src/datageneration/abaqus/abaqus_simulator.py b/src/f3dasm/_src/datageneration/abaqus/abaqus_simulator.py deleted file mode 100644 index 4db337d9..00000000 --- a/src/f3dasm/_src/datageneration/abaqus/abaqus_simulator.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Abaqus simulator class -""" - -# Modules -# ============================================================================= - -# Standard - -import os -import pickle -from pathlib import Path -from typing import Any, Dict - -# Local -from ...logger import logger -from ..datagenerator import DataGenerator -from .utils import remove_files - -# Authorship & Credits -# ============================================================================= -__author__ = "Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)" -__credits__ = ["Martin van der Schelling"] -__status__ = "Alpha" -# ============================================================================= -# -# ============================================================================= - - -class AbaqusSimulator(DataGenerator): - def __init__(self, name: str = "job", num_cpus: int = 1, - delete_odb: bool = False, delete_temp_files: bool = True): - """Abaqus simulator class - - Parameters - ---------- - name : str, optional - Name of the job, by default "job" - num_cpus : int, optional - Number of CPUs to use, by default 1 - delete_odb : bool, optional - Set true if you want to delete the original .odb file after - post-processing, by default True - delete_temp_files : bool, optional - Set true if you want to delete the temporary files after - post-processing, by default True - - Note - ---- - The kwargs are saved as attributes to the class. This is useful for the - simulation script to access the parameters. - """ - self.name = name - self.num_cpus = num_cpus - # TODO: Where do I specify this in the execution of abaqus? - self.delete_odb = delete_odb - self.delete_temp_files = delete_temp_files - - def _pre_simulation(self, **kwargs) -> None: - """Setting up the abaqus simulator - - Create working directory: datageneration/_ - """ - # Create working directory - self.working_dir = Path( - "datageneration") / Path(f"{self.name}_\ - {self.experiment_sample.job_number}") - self.working_dir.mkdir(parents=True, exist_ok=True) - - def execute(self) -> None: - """Submit the .inp file to run the ABAQUS simulator, - creating an .odb file - - Note - ---- - This will execute the simulation and create an .odb file with - name: .odb - """ - filename = self.working_dir / "execute.py" - logger.info( - f"Executing ABAQUS simulator '{self.name}' for sample: \ - {self.experiment_sample.job_number}") - - with open(f"{filename}", "w") as f: - f.write("from abaqus import mdb\n") - f.write("import os\n") - f.write("from abaqusConstants import OFF\n") - f.write(f"os.chdir(r'{self.working_dir}')\n") - f.write( - f"modelJob = mdb.JobFromInputFile(inputFileName=" - f"r'{self.experiment_sample.job_number}.inp'," - f"name='{self.experiment_sample.job_number}'," - f"numCpus={self.num_cpus})\n") - f.write("modelJob.submit(consistencyChecking=OFF)\n") - f.write("modelJob.waitForCompletion()\n") - - os.system(f"abaqus cae noGUI={filename} -mesa") - if self.delete_temp_files: - filename.unlink(missing_ok=True) - - def _post_simulation(self): - """Opening the results.pkl file and storing the data to the - ExperimentData object - - Note - ---- - Temporary files will be removed. If the simulator has its argument - 'delete_odb' to True, - the .odb file will be removed as well to save storage space. - - After the post-processing, the working directory will be changed - back to directory - before running the simulator. - - Raises - ------ - FileNotFoundError - When results.pkl is not found in the working directory - """ - if self.delete_temp_files: - remove_files(directory=self.working_dir) - - # remove the odb file to save memory - if self.delete_odb: - remove_files(directory=self.working_dir, file_types=[".odb"]) - - # Check if path exists - if not Path(self.working_dir / "results.pkl").exists(): - raise FileNotFoundError( - f"{Path(self.working_dir) / 'results.pkl'}") - - # Load the results - with open(Path(self.working_dir / "results.pkl"), "rb") as fd: - results: Dict[str, Any] = pickle.load( - fd, fix_imports=True, encoding="latin1") - - # for every key in self.results, store the value - # in the ExperimentSample object - for key, value in results.items(): - # Check if value is of one of these types: int, float, str - if isinstance(value, (int, float, str)): - self.experiment_sample.store( - object=value, name=key, to_disk=False) - - else: - self.experiment_sample.store( - object=value, name=key, to_disk=True) - - # Remove the results.pkl file - if self.delete_temp_files: - Path(self.working_dir / "results.pkl").unlink(missing_ok=True) diff --git a/src/f3dasm/_src/datageneration/abaqus/utils.py b/src/f3dasm/_src/datageneration/abaqus/utils.py deleted file mode 100644 index 096763e8..00000000 --- a/src/f3dasm/_src/datageneration/abaqus/utils.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Utility functions for abaqus datagenerator -""" - -# Modules -# ============================================================================= - -# Standard -from pathlib import Path - -# Authorship & Credits -# ============================================================================= -__author__ = "Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)" -__credits__ = ["Martin van der Schelling"] -__status__ = "Alpha" -# ============================================================================= -# -# ============================================================================= - - -def remove_files( - directory: str, - file_types: list = [".log", ".lck", ".SMABulk", - ".rec", ".SMAFocus", - ".exception", ".simlog", ".023", ".exception"], -) -> None: - """Remove files of specified types in a directory. - - Parameters - ---------- - directory : str - Target folder. - file_types : list - List of file extensions to be removed. - """ - # Create a Path object for the directory - dir_path = Path(directory) - - for target_file in file_types: - # Use glob to find files matching the target extension - target_files = dir_path.glob(f"*{target_file}") - - # Remove the target files if they exist - for file in target_files: - if file.is_file(): - file.unlink() diff --git a/src/f3dasm/_src/datageneration/datagenerator.py b/src/f3dasm/_src/datageneration/datagenerator.py index 83adcb78..7705d3fc 100644 --- a/src/f3dasm/_src/datageneration/datagenerator.py +++ b/src/f3dasm/_src/datageneration/datagenerator.py @@ -11,7 +11,6 @@ # Standard import inspect from abc import abstractmethod -from functools import partial from typing import Any, Callable, Dict, List, Optional # Third-party @@ -19,10 +18,8 @@ # Local from ..design.domain import Domain -# from ..experimentdata._io import StoreProtocol from ..experimentdata.experimentsample import (ExperimentSample, _experimentsample_factory) -from ..logger import time_and_log # Authorship & Credits # ============================================================================= @@ -36,23 +33,6 @@ class DataGenerator: """Base class for a data generator""" - - def pre_process( - self, experiment_sample: ExperimentSample, **kwargs) -> None: - """Interface function that handles the pre-processing of - the data generator - - Note - ---- - If not implemented the function will be skipped. - - The experiment_sample is cached inside the data generator. This \ - allows the user to access the experiment_sample in the pre_process, \ - execute and post_process functions as a class variable called \ - self.experiment_sample. - """ - ... - @abstractmethod def execute(self, **kwargs) -> None: """Interface function that handles the execution of the data generator @@ -64,52 +44,23 @@ def execute(self, **kwargs) -> None: Note ---- - The experiment_sample is cached inside the data generator. This \ - allows the user to access the experiment_sample in \ - the pre_process, execute and post_process functions as a class \ - variable called self.experiment_sample. - """ - - ... - - def post_process( - self, experiment_sample: ExperimentSample, **kwargs) -> None: - """Interface function that handles the post-processing \ - of the data generator - - Note - ---- - If not implemented the function will be skipped. - - The experiment_sample is cached inside the data generator. This \ - allows the user to access the experiment_sample in the \ - pre_process, execute and post_process functions as a class variable \ - called self.experiment_sample. + The experiment_sample is cached inside the data generator. This + allows the user to access the experiment_sample in + the execute and function as a class variable called + self.experiment_sample. """ ... - @time_and_log def _run( self, experiment_sample: ExperimentSample | np.ndarray, domain: Optional[Domain] = None, **kwargs) -> ExperimentSample: """ Run the data generator. - This function chains the following methods together - - * pre_process(); to combine the experiment_sample and the parameters \ - of the data generator to an input file that can be used to run the \ - data generator. - * execute(); to run the data generator and generate the response of \ - the experiment - - * post_process(); to process the response of the experiment and store \ - it back in the experiment_sample - - The function also caches the experiment_sample in the data generator. \ - This allows the user to access the experiment_sample in the \ - pre_process, execute and post_process functions as a class variable \ + The function also caches the experiment_sample in the data generator. + This allows the user to access the experiment_sample in the + execute function as a class variable called self.experiment_sample. Parameters @@ -133,46 +84,10 @@ def _run( self.experiment_sample: ExperimentSample = _experimentsample_factory( experiment_sample=experiment_sample, domain=domain) - self._pre_simulation() - - self.pre_process(self.experiment_sample, **kwargs) self.execute(**kwargs) - self.post_process(self.experiment_sample, **kwargs) - - self._post_simulation() return self.experiment_sample - def _pre_simulation(self) -> None: - ... - - def _post_simulation(self) -> None: - ... - - def add_pre_process(self, func: Callable, **kwargs): - """Add a pre-processing function to the data generator - - Parameters - ---------- - func : Callable - The function to add to the pre-processing - kwargs : dict - The keyword arguments to pass to the pre-processing function - """ - self.pre_process = partial(func, **kwargs) - - def add_post_process(self, func: Callable, **kwargs): - """Add a post-processing function to the data generator - - Parameters - ---------- - func : Callable - The function to add to the post-processing - kwargs : dict - The keyword arguments to pass to the post-processing function - """ - self.post_process = partial(func, **kwargs) - def convert_function(f: Callable, output: Optional[List[str]] = None, diff --git a/src/f3dasm/_src/datageneration/functions/function.py b/src/f3dasm/_src/datageneration/functions/function.py index 5379a328..1b8bb71c 100644 --- a/src/f3dasm/_src/datageneration/functions/function.py +++ b/src/f3dasm/_src/datageneration/functions/function.py @@ -13,13 +13,7 @@ from __future__ import annotations # Standard -import sys -from typing import Optional, Tuple - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Protocol # NOQA -else: - from typing import Protocol +from typing import Optional, Protocol, Tuple # Third-party core import autograd.numpy as np @@ -293,9 +287,9 @@ def plot( rstride=1, cstride=1, edgecolor="none", - alpha=0.8, + alpha=0.9, cmap="viridis", - norm=mcol.LogNorm(), # mcol.LogNorm() + # norm=mcol.LogNorm(), # mcol.LogNorm() zorder=1, ) ax.set_zlabel("$f(X)$", fontsize=16) diff --git a/src/f3dasm/_src/design/domain.py b/src/f3dasm/_src/design/domain.py index ea97d4e7..172d9ce1 100644 --- a/src/f3dasm/_src/design/domain.py +++ b/src/f3dasm/_src/design/domain.py @@ -11,16 +11,10 @@ # Standard import math import pickle -import sys from dataclasses import dataclass, field from pathlib import Path -from typing import (Any, Dict, Iterable, Iterator, List, Optional, Sequence, - Type) - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Literal # NOQA -else: - from typing import Literal +from typing import (Any, Dict, Iterable, Iterator, List, Literal, Optional, + Sequence, Type) # Third-party core import numpy as np diff --git a/src/f3dasm/_src/experimentdata/experimentdata.py b/src/f3dasm/_src/experimentdata/experimentdata.py index 97584f24..f8dd6705 100644 --- a/src/f3dasm/_src/experimentdata/experimentdata.py +++ b/src/f3dasm/_src/experimentdata/experimentdata.py @@ -9,20 +9,15 @@ from __future__ import annotations -import inspect # Standard -import sys +import inspect import traceback +from copy import copy from functools import wraps from pathlib import Path from time import sleep -from typing import (Any, Callable, Dict, Iterable, Iterator, List, Optional, - Tuple, Type) - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Literal # NOQA -else: - from typing import Literal +from typing import (Any, Callable, Dict, Iterable, Iterator, List, Literal, + Optional, Tuple, Type) # Third-party import numpy as np @@ -1440,18 +1435,21 @@ def optimize(self, optimizer: Optimizer | str, data_generator=data_generator, domain=self.domain, kwargs=kwargs) + # Create a copy of the optimizer object + _optimizer = copy(optimizer) + # Create the optimizer object if a string reference is passed - if isinstance(optimizer, str): - optimizer: Optimizer = _optimizer_factory( - optimizer, self.domain, hyperparameters) + if isinstance(_optimizer, str): + _optimizer: Optimizer = _optimizer_factory( + _optimizer, self.domain, hyperparameters) # Create the sampler object if a string reference is passed if isinstance(sampler, str): sampler: Sampler = _sampler_factory(sampler, self.domain) - if optimizer.type == 'scipy': + if _optimizer.type == 'scipy': self._iterate_scipy( - optimizer=optimizer, data_generator=data_generator, + optimizer=_optimizer, data_generator=data_generator, iterations=iterations, kwargs=kwargs, x0_selection=x0_selection, sampler=sampler, @@ -1459,7 +1457,7 @@ def optimize(self, optimizer: Optimizer | str, callback=callback) else: self._iterate( - optimizer=optimizer, data_generator=data_generator, + optimizer=_optimizer, data_generator=data_generator, iterations=iterations, kwargs=kwargs, x0_selection=x0_selection, sampler=sampler, @@ -1523,18 +1521,18 @@ def _iterate(self, optimizer: Optimizer, data_generator: DataGenerator, if isinstance(x0_selection, str): if x0_selection == 'new': - if iterations < optimizer.hyperparameters.population: + if iterations < optimizer._population: raise ValueError( f'For creating new samples, the total number of ' f'requested iterations ({iterations}) cannot be ' f'smaller than the population size ' - f'({optimizer.hyperparameters.population})') + f'({optimizer._population})') init_samples = ExperimentData.from_sampling( domain=self.domain, sampler=sampler, - n_samples=optimizer.hyperparameters.population, - seed=optimizer.seed) + n_samples=optimizer._population, + seed=optimizer._seed) init_samples.evaluate( data_generator=data_generator, kwargs=kwargs, @@ -1554,11 +1552,11 @@ def _iterate(self, optimizer: Optimizer, data_generator: DataGenerator, self.add_experiments(init_samples) x0_selection = 'last' - iterations -= optimizer.hyperparameters.population + iterations -= optimizer._population x0 = x0_factory(experiment_data=self, mode=x0_selection, - n_samples=optimizer.hyperparameters.population) - optimizer.set_data(x0) + n_samples=optimizer._population) + optimizer._set_data(x0) optimizer._check_number_of_datapoints() @@ -1566,7 +1564,7 @@ def _iterate(self, optimizer: Optimizer, data_generator: DataGenerator, for _ in range(number_of_updates( iterations, - population=optimizer.hyperparameters.population)): + population=optimizer._population)): new_samples = optimizer.update_step(data_generator) # If new_samples is a tuple of input_data and output_data @@ -1592,15 +1590,16 @@ def _iterate(self, optimizer: Optimizer, data_generator: DataGenerator, else: self.add_experiments(new_samples) - optimizer.set_data(self) + optimizer._set_data(self) if not overwrite: # Remove overiterations self.remove_rows_bottom(number_of_overiterations( - iterations, population=optimizer.hyperparameters.population)) + iterations, + population=optimizer._population)) # Reset the optimizer - optimizer.reset(ExperimentData(domain=self.domain)) + # optimizer.reset(ExperimentData(domain=self.domain)) def _iterate_scipy(self, optimizer: Optimizer, data_generator: DataGenerator, @@ -1663,18 +1662,18 @@ def _iterate_scipy(self, optimizer: Optimizer, if isinstance(x0_selection, str): if x0_selection == 'new': - if iterations < optimizer.hyperparameters.population: + if iterations < optimizer._population: raise ValueError( f'For creating new samples, the total number of ' f'requested iterations ({iterations}) cannot be ' f'smaller than the population size ' - f'({optimizer.hyperparameters.population})') + f'({optimizer._population})') init_samples = ExperimentData.from_sampling( domain=self.domain, sampler=sampler, - n_samples=optimizer.hyperparameters.population, - seed=optimizer.seed) + n_samples=optimizer._population, + seed=optimizer._seed) init_samples.evaluate( data_generator=data_generator, kwargs=kwargs, @@ -1696,8 +1695,8 @@ def _iterate_scipy(self, optimizer: Optimizer, x0_selection = 'last' x0 = x0_factory(experiment_data=self, mode=x0_selection, - n_samples=optimizer.hyperparameters.population) - optimizer.set_data(x0) + n_samples=optimizer._population) + optimizer._set_data(x0) optimizer._check_number_of_datapoints() @@ -1740,7 +1739,7 @@ def _iterate_scipy(self, optimizer: Optimizer, self.evaluate(data_generator, mode='sequential', kwargs=kwargs) # Reset the optimizer - optimizer.reset(ExperimentData(domain=self.domain)) + # optimizer.reset(ExperimentData(domain=self.domain)) # Sampling # ========================================================================= diff --git a/src/f3dasm/_src/experimentdata/experimentsample.py b/src/f3dasm/_src/experimentdata/experimentsample.py index cb0e550d..14d46a09 100644 --- a/src/f3dasm/_src/experimentdata/experimentsample.py +++ b/src/f3dasm/_src/experimentdata/experimentsample.py @@ -9,15 +9,8 @@ from __future__ import annotations # Standard -import sys from pathlib import Path - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Literal # NOQA -else: - from typing import Literal - -from typing import Any, Dict, Optional, Tuple, Type +from typing import Any, Dict, Literal, Optional, Tuple, Type # Third-party import autograd.numpy as np diff --git a/src/f3dasm/_src/experimentdata/samplers.py b/src/f3dasm/_src/experimentdata/samplers.py index 4347d615..307a58ec 100644 --- a/src/f3dasm/_src/experimentdata/samplers.py +++ b/src/f3dasm/_src/experimentdata/samplers.py @@ -6,15 +6,8 @@ from __future__ import annotations # Standard -import sys from itertools import product - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Literal, Protocol # NOQA -else: - from typing import Literal, Protocol - -from typing import Dict, Optional +from typing import Dict, Literal, Optional, Protocol # Third-party import numpy as np diff --git a/src/f3dasm/_src/logger.py b/src/f3dasm/_src/logger.py index 37d858c0..7c6c336a 100644 --- a/src/f3dasm/_src/logger.py +++ b/src/f3dasm/_src/logger.py @@ -16,10 +16,6 @@ else: import fcntl -from functools import partial, wraps -from time import perf_counter -from typing import Any, Callable - # Authorship & Credits # ============================================================================= __author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' @@ -119,21 +115,3 @@ def _unlock_file(file): msvcrt.locking(file.fileno(), msvcrt.LK_UNLCK, 1) else: # for Unix fcntl.flock(file, fcntl.LOCK_UN) - - -def _time_and_log( - func: Callable, logger=logging.Logger -) -> Callable: - @wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: - start_time = perf_counter() - value = func(*args, **kwargs) - logger.debug( - f"Called {func.__name__} and time taken: \ - {perf_counter() - start_time:.2f}s") - return value - - return wrapper - - -time_and_log = partial(_time_and_log, logger=logger) diff --git a/src/f3dasm/_src/machinelearning/__init__.py b/src/f3dasm/_src/machinelearning/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/f3dasm/_src/optimization/__init__.py b/src/f3dasm/_src/optimization/__init__.py index 588cdef2..6343b5d4 100644 --- a/src/f3dasm/_src/optimization/__init__.py +++ b/src/f3dasm/_src/optimization/__init__.py @@ -8,11 +8,9 @@ from typing import List # Local -from .cg import CG, CG_Parameters -from .lbfgsb import LBFGSB, LBFGSB_Parameters -from .neldermead import NelderMead, NelderMead_Parameters +from .numpy_implementations import RandomSearch from .optimizer import Optimizer -from .randomsearch import RandomSearch, RandomSearch_Parameters +from .scipy_implementations import CG, LBFGSB, NelderMead # Authorship & Credits # ============================================================================= @@ -29,14 +27,10 @@ __all__ = [ 'CG', - 'CG_Parameters', 'LBFGSB', - 'LBFGSB_Parameters', 'NelderMead', - 'NelderMead_Parameters', 'Optimizer', 'RandomSearch', - 'RandomSearch_Parameters', '_OPTIMIZERS', 'find_optimizer', ] diff --git a/src/f3dasm/_src/optimization/adapters/scipy_implementations.py b/src/f3dasm/_src/optimization/adapters/scipy_implementations.py index 1839c8b5..832b9258 100644 --- a/src/f3dasm/_src/optimization/adapters/scipy_implementations.py +++ b/src/f3dasm/_src/optimization/adapters/scipy_implementations.py @@ -10,6 +10,7 @@ # Locals from ...datageneration.datagenerator import DataGenerator +from ...design.domain import Domain from ...experimentdata.experimentsample import ExperimentSample from ..optimizer import Optimizer @@ -29,6 +30,11 @@ class _SciPyOptimizer(Optimizer): type: str = 'scipy' + def __init__(self, domain: Domain, method: str, **hyperparameters): + self.domain = domain + self.method = method + self.options = {**hyperparameters} + def _callback(self, xk: np.ndarray, *args, **kwargs) -> None: self.data.add_experiments( ExperimentSample.from_numpy(xk, domain=self.domain)) @@ -57,7 +63,7 @@ def fun(x): _, y = sample.to_numpy() return float(y) - self.hyperparameters.maxiter = iterations + self.options['maxiter'] = iterations minimize( fun=fun, @@ -65,9 +71,7 @@ def fun(x): jac=data_generator.dfdx, x0=self.data.get_n_best_output(1).to_numpy()[0].ravel(), callback=self._callback, - options=self.hyperparameters.__dict__, + options=self.options, bounds=self.domain.get_bounds(), tol=0.0, ) - - # self.data.evaluate(data_generator=data_generator) diff --git a/src/f3dasm/_src/optimization/cg.py b/src/f3dasm/_src/optimization/cg.py deleted file mode 100644 index 84fbc8b8..00000000 --- a/src/f3dasm/_src/optimization/cg.py +++ /dev/null @@ -1,36 +0,0 @@ -# Modules -# ============================================================================= - -# Standard -from dataclasses import dataclass -from typing import List - -# Locals -from .adapters.scipy_implementations import _SciPyOptimizer -from .optimizer import OptimizerParameters - -# Authorship & Credits -# ============================================================================= -__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' -__credits__ = ['Martin van der Schelling'] -__status__ = 'Stable' -# ============================================================================= -# -# ============================================================================= - - -@dataclass -class CG_Parameters(OptimizerParameters): - """CG Parameters""" - - gtol: float = 0.0 - - -class CG(_SciPyOptimizer): - """CG""" - require_gradients: bool = True - method: str = "CG" - hyperparameters: CG_Parameters = CG_Parameters() - - def get_info(self) -> List[str]: - return ['Stable', 'First-Order', 'Single-Solution'] diff --git a/src/f3dasm/_src/optimization/lbfgsb.py b/src/f3dasm/_src/optimization/lbfgsb.py deleted file mode 100644 index 071fbb63..00000000 --- a/src/f3dasm/_src/optimization/lbfgsb.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -L-BFGS-B optimizer -""" - -# Modules -# ============================================================================= - -# Standard -from dataclasses import dataclass -from typing import List - -from .adapters.scipy_implementations import _SciPyOptimizer -# Locals -from .optimizer import OptimizerParameters - -# Authorship & Credits -# ============================================================================= -__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' -__credits__ = ['Martin van der Schelling'] -__status__ = 'Stable' -# ============================================================================= -# -# ============================================================================= - - -@dataclass -class LBFGSB_Parameters(OptimizerParameters): - """Hyperparameters for LBFGSB optimizer""" - - ftol: float = 0.0 - gtol: float = 0.0 - - -class LBFGSB(_SciPyOptimizer): - """L-BFGS-B""" - require_gradients: bool = True - method: str = "L-BFGS-B" - hyperparameters: LBFGSB_Parameters = LBFGSB_Parameters() - - def get_info(self) -> List[str]: - return ['Stable', 'First-Order', 'Single-Solution'] diff --git a/src/f3dasm/_src/optimization/neldermead.py b/src/f3dasm/_src/optimization/neldermead.py deleted file mode 100644 index f3a9df93..00000000 --- a/src/f3dasm/_src/optimization/neldermead.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Nelder-Mead optimizer -""" -# Modules -# ============================================================================= - -# Standard -from dataclasses import dataclass -from typing import List - -# Locals -from .adapters.scipy_implementations import _SciPyOptimizer -from .optimizer import OptimizerParameters - -# Authorship & Credits -# ============================================================================= -__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' -__credits__ = ['Martin van der Schelling'] -__status__ = 'Stable' -# ============================================================================= -# -# ============================================================================= - - -@dataclass -class NelderMead_Parameters(OptimizerParameters): - """Hyperparameters for NelderMead optimizer""" - - xatol: float = 0.0 - fatol: float = 0.0 - adaptive: bool = False - - -class NelderMead(_SciPyOptimizer): - """Nelder-Mead""" - require_gradients: bool = False - method: str = "Nelder-Mead" - hyperparameters: NelderMead_Parameters = NelderMead_Parameters() - - def get_info(self) -> List[str]: - return ['Fast', 'Global', 'First-Order', 'Single-Solution'] diff --git a/src/f3dasm/_src/optimization/randomsearch.py b/src/f3dasm/_src/optimization/numpy_implementations.py similarity index 71% rename from src/f3dasm/_src/optimization/randomsearch.py rename to src/f3dasm/_src/optimization/numpy_implementations.py index 445cd851..a79a4415 100644 --- a/src/f3dasm/_src/optimization/randomsearch.py +++ b/src/f3dasm/_src/optimization/numpy_implementations.py @@ -1,20 +1,20 @@ """ -Random Search optimizer +Optimizers based from the numpy library """ # Modules # ============================================================================= # Standard -from dataclasses import dataclass -from typing import List, Tuple +from typing import List, Optional, Tuple # Third-party core -import autograd.numpy as np +import numpy as np # Locals from ..datageneration.datagenerator import DataGenerator -from .optimizer import Optimizer, OptimizerParameters +from ..design.domain import Domain +from .optimizer import Optimizer # Authorship & Credits # ============================================================================= @@ -26,27 +26,24 @@ # ============================================================================= -@dataclass -class RandomSearch_Parameters(OptimizerParameters): - """Hyperparameters for RandomSearch optimizer""" - - pass - - class RandomSearch(Optimizer): """Naive random search""" require_gradients: bool = False - hyperparameters: RandomSearch_Parameters = RandomSearch_Parameters() - def set_seed(self): - np.random.seed(self.seed) + def __init__(self, domain: Domain, seed: Optional[int] = None, **kwargs): + self.domain = domain + self.seed = seed + self._set_algorithm() + + def _set_algorithm(self): + self.algorithm = np.random.default_rng(self.seed) def update_step( self, data_generator: DataGenerator ) -> Tuple[np.ndarray, np.ndarray]: x_new = np.atleast_2d( [ - np.random.uniform( + self.algorithm.uniform( low=self.domain.get_bounds()[d, 0], high=self.domain.get_bounds()[d, 1]) for d in range(len(self.domain)) @@ -56,5 +53,5 @@ def update_step( # return the data return x_new, None - def get_info(self) -> List[str]: + def _get_info(self) -> List[str]: return ['Fast', 'Single-Solution'] diff --git a/src/f3dasm/_src/optimization/optimizer.py b/src/f3dasm/_src/optimization/optimizer.py index 7f2ceb64..adf5cb85 100644 --- a/src/f3dasm/_src/optimization/optimizer.py +++ b/src/f3dasm/_src/optimization/optimizer.py @@ -8,14 +8,7 @@ from __future__ import annotations # Standard -import sys -from dataclasses import dataclass -from typing import ClassVar, Iterable, List, Optional, Tuple - -if sys.version_info < (3, 8): # NOQA - from typing_extensions import Protocol # NOQA -else: - from typing import Protocol +from typing import ClassVar, Iterable, List, Protocol, Tuple # Third-party core import numpy as np @@ -23,7 +16,6 @@ # Locals from ..datageneration.datagenerator import DataGenerator -from ..design.domain import Domain # Authorship & Credits # ============================================================================= @@ -36,8 +28,6 @@ class ExperimentData(Protocol): - domain: Domain - @property def index(self, index) -> pd.Index: ... @@ -45,161 +35,201 @@ def index(self, index) -> pd.Index: def get_n_best_output(self, n_samples: int) -> ExperimentData: ... - def _reset_index() -> None: - ... - def to_numpy() -> Tuple[np.ndarray, np.ndarray]: ... def select(self, indices: int | slice | Iterable[int]) -> ExperimentData: ... - -@dataclass -class OptimizerParameters: - """Interface of a continuous benchmark function - - Parameters - ---------- - population : int - population of the optimizer update step - force_bounds : bool - force the optimizer to not exceed the boundaries of the domain - """ - - population: int = 1 - force_bounds: bool = True +# ============================================================================= class Optimizer: + """ + Abstract class for optimization algorithms + To implement a new optimizer, inherit from this class and implement the + update_step method. + + Note + ---- + The update_step method should have the following signature: + + ''' + def update_step(self, data_generator: DataGenerator) + -> Tuple[np.ndarray, np.ndarray]: + ''' + + The method should return a tuple containing the new samples and the + corresponding objective values. The new samples should be a numpy array. + + If the optimizer requires gradients, set the require_gradients attribute to + True. This will ensure that the data_generator will calculate the gradients + of the objective function from the DataGenerator.dfdx method. + + Hyperparameters can be set in the __init__ method of the child class. + There are two hyperparameters that have special meaning: + - population: the number of individuals in the population + - seed: the seed of the random number generator + + You can create extra methods in your child class as you please, however + it is advised not to create private methods (methods starting with an + underscore) as these might be used in the base class. + """ type: ClassVar[str] = 'any' require_gradients: ClassVar[bool] = False - hyperparameters: OptimizerParameters = OptimizerParameters() - def __init__( - self, domain: Domain, seed: Optional[int] = None, - name: Optional[str] = None, **hyperparameters): - """Optimizer class for the optimization of a data-driven process +# Private Properties +# ============================================================================= - Parameters - ---------- - domain : Domain - Domain indicating the search-space of the optimization parameters - seed : Optional[int], optional - Seed of the random number generator for stochastic optimization - processes, by default None, set to random - name : Optional[str], optional - Name of the optimization object, by default None, - it will use the name of the class + @property + def _seed(self) -> int: + """ + Property to return the seed of the optimizer + Returns + ------- + int | None + Seed of the optimizer Note ---- + If the seed is not set, the property will return None + This is done to prevent errors when the seed is not an available + attribute in a custom optimizer class. + """ + return self.seed if hasattr(self, 'seed') else None - Any additional keyword arguments will be used to overwrite - the default hyperparameters of the optimizer. + @property + def _population(self) -> int: """ + Property to return the population size of the optimizer - # Check if **hyperparameters is empty - if not hyperparameters: - hyperparameters = {} + Returns + ------- + int + Number of individuals in the population - # Overwrite the default hyperparameters with the given hyperparameters - self.hyperparameters.__init__(**hyperparameters) + Note + ---- + If the population is not set, the property will return 1 + This is done to prevent errors when the population size is not an + available attribute in a custom optimizer class. + """ + return self.population if hasattr(self, 'population') else 1 - # Set the name of the optimizer to the class name if no name is given - if name is None: - name = self.__class__.__name__ +# Public Methods +# ============================================================================= + + def update_step(self, data_generator: DataGenerator) -> ExperimentData: + """Update step of the optimizer. Needs to be implemented + by the child class + + Parameters + ---------- + data_generator : DataGenerator + data generator object to calculate the objective value - # Set the seed to a random number if no seed is given - if seed is None: - seed = np.random.randint(low=0, high=1e5) + Returns + ------- + ExperimentData + ExperimentData object containing the new samples - self.domain = domain - self.seed = seed - self.name = name - self.__post_init__() + Raises + ------ + NotImplementedError + Raises when the method is not implemented by the child class - def __post_init__(self): - self.set_seed() - self.set_algorithm() + Note + ---- + You can access the data attribute of the optimizer to get the + available data points. The data attribute is an + f3dasm.ExperimentData object. + """ + raise NotImplementedError( + "You should implement an update step for your algorithm!") - def set_algorithm(self): - """Set the algorithm attribute to the algorithm of choice""" +# Private Methods +# ============================================================================= + + def _set_algorithm(self): + """ + Method that can be implemented to set the optimization algorithm. + Whenever the reset method is called, this method will be called to + reset the algorithm to its initial state.""" ... def _construct_model(self, data_generator: DataGenerator): + """ + Method that is called before the optimization starts. This method can + be used to construct a model based on the available data or a specific + data generator. + + Parameters + ---------- + data_generator : DataGenerator + DataGenerator object + + Note + ---- + When this method is not implemented, the method will do nothing. + """ ... def _check_number_of_datapoints(self): - """Check if the number of datapoints is sufficient - for the initial population + """ + Check if the number of datapoints is sufficient for the + initial population Raises ------ ValueError - Raises then the number of datapoints is insufficient + Raises when the number of datapoints is insufficient """ - if len(self.data) < self.hyperparameters.population: + if len(self.data) < self._population: raise ValueError( f'There are {len(self.data)} datapoints available, \ - need {self.hyperparameters.population} for initial \ + need {self._population} for initial \ population!' ) - def set_seed(self): - """Set the seed of the random number generator""" - ... - - def reset(self, data: ExperimentData): - """Reset the optimizer to its initial state""" - self.set_data(data) - self.__post_init__() + def _reset(self, data: ExperimentData): + """Reset the optimizer to its initial state - def set_data(self, data: ExperimentData): - """Set the data attribute to the given data""" - self.data = data + Parameters + ---------- + data : ExperimentData + Data to set the optimizer to its initial state - def add_experiments(self, experiments: ExperimentData): - ... + Note + ---- + This method should be called whenever the optimizer is reset + to its initial state. This can be done when the optimizer is + re-initialized or when the optimizer is re-used for a new + optimization problem. + + The following steps are taken when the reset method is called: + - The data attribute is set to the given data (self._set_data) + - The algorithm is set to its initial state (self._set_algorithm) + """ + self._set_data(data) + self._set_algorithm() - def get_name(self) -> str: - """Get the name of the optimizer + def _set_data(self, data: ExperimentData): + """Set the data attribute to the given data - Returns - ------- - str - name of the optimizer + Parameters + ---------- + data : ExperimentData + Data to set the optimizer to its initial state """ - return self.name + self.data = data - def get_info(self) -> List[str]: + def _get_info(self) -> List[str]: """Give a list of characteristic features of this optimizer Returns ------- - List of strings denoting the characteristics of this optimizer + List[str] + List of characteristics of the optimizer """ return [] - - def update_step(self, data_generator: DataGenerator) -> ExperimentData: - """Update step of the optimizer. Needs to be implemented - by the child class - - Parameters - ---------- - data_generator : DataGenerator - data generator object to calculate the objective value - - Returns - ------- - ExperimentData - ExperimentData object containing the new samples - - Raises - ------ - NotImplementedError - Raises when the method is not implemented by the child class - """ - raise NotImplementedError( - "You should implement an update step for your algorithm!") diff --git a/src/f3dasm/_src/optimization/optimizer_factory.py b/src/f3dasm/_src/optimization/optimizer_factory.py index 6ebf48e9..47ec1b68 100644 --- a/src/f3dasm/_src/optimization/optimizer_factory.py +++ b/src/f3dasm/_src/optimization/optimizer_factory.py @@ -4,8 +4,10 @@ # Modules # ============================================================================= +# Standard from typing import Any, Dict, Optional +# Local from ..design.domain import Domain from . import _OPTIMIZERS from .optimizer import Optimizer diff --git a/src/f3dasm/_src/optimization/scipy_implementations.py b/src/f3dasm/_src/optimization/scipy_implementations.py new file mode 100644 index 00000000..42e1bc0a --- /dev/null +++ b/src/f3dasm/_src/optimization/scipy_implementations.py @@ -0,0 +1,72 @@ +""" +Optimizers based from the scipy.optimize library +""" + +# Modules +# ============================================================================= + +# Standard +from typing import List + +# Locals +from ..design.domain import Domain +from .adapters.scipy_implementations import _SciPyOptimizer + +# Authorship & Credits +# ============================================================================= +__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' +__credits__ = ['Martin van der Schelling'] +__status__ = 'Stable' +# ============================================================================= +# +# ============================================================================= + + +class CG(_SciPyOptimizer): + """CG""" + require_gradients: bool = True + + def __init__(self, domain: Domain, gtol: float = 0.0, **kwargs): + super().__init__( + domain=domain, method='CG', gtol=gtol) + self.gtol = gtol + + def _get_info(self) -> List[str]: + return ['Stable', 'First-Order', 'Single-Solution'] + +# ============================================================================= + + +class LBFGSB(_SciPyOptimizer): + """L-BFGS-B""" + require_gradients: bool = True + + def __init__(self, domain: Domain, + ftol: float = 0.0, gtol: float = 0.0, **kwargs): + super().__init__( + domain=domain, method='L-BFGS-B', ftol=ftol, gtol=gtol) + self.ftol = ftol + self.gtol = gtol + + def _get_info(self) -> List[str]: + return ['Stable', 'First-Order', 'Single-Solution'] + +# ============================================================================= + + +class NelderMead(_SciPyOptimizer): + """Nelder-Mead""" + require_gradients: bool = False + + def __init__(self, domain: Domain, + xatol: float = 0.0, fatol: float = 0.0, + adaptive: bool = False, **kwargs): + super().__init__( + domain=domain, method='Nelder-Mead', xatol=xatol, fatol=fatol, + adaptive=adaptive) + self.xatol = xatol + self.fatol = fatol + self.adaptive = adaptive + + def _get_info(self) -> List[str]: + return ['Fast', 'Global', 'First-Order', 'Single-Solution'] diff --git a/src/f3dasm/_src/run_optimization.py b/src/f3dasm/_src/run_optimization.py deleted file mode 100644 index 6c59c670..00000000 --- a/src/f3dasm/_src/run_optimization.py +++ /dev/null @@ -1,279 +0,0 @@ -""" -Module to optimize benchmark optimization functions -""" -# Modules -# ============================================================================= - -from __future__ import annotations - -# Standard -from time import perf_counter -from typing import Any, Callable, Dict, List, Optional - -# Third-party -import numpy as np -import pandas as pd -import xarray as xr -from pathos.helpers import mp - -from f3dasm.design import Domain -from f3dasm.optimization import Optimizer - -# Locals -from .datageneration.datagenerator import DataGenerator -from .datageneration.functions.function_factory import _datagenerator_factory -from .experimentdata.experimentdata import ExperimentData -from .logger import logger, time_and_log -from .optimization.optimizer_factory import _optimizer_factory - -# Authorship & Credits -# ============================================================================= -__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' -__credits__ = ['Martin van der Schelling'] -__status__ = 'Stable' -# ============================================================================= -# -# ============================================================================= - - -class OptimizationResult: - def __init__(self, data: List[ExperimentData], optimizer: Optimizer, - kwargs: Optional[Dict[str, Any]], - data_generator: DataGenerator, - number_of_samples: int, seeds: List[int], - opt_time: float = 0.0): - """Optimization results object - - Parameters - ---------- - data - Data objects for each realization - optimizer - classname of the optimizer used - data_generator - the data_generator to get objective values - kwargs - the kwargs used for the data_generator - number_of_samples - number of initial samples, sampled by the sampling strategy - seeds - list of seeds that were used for each realization - opt_time - total optimization time - """ - self.data = data - self.optimizer = _optimizer_factory( - optimizer=optimizer, domain=self.data[0].domain) - self.data_generator = data_generator - self.kwargs = kwargs, - self.number_of_samples = number_of_samples - self.seeds = seeds - self.opt_time = opt_time - - self.func = _datagenerator_factory( - data_generator=self.data_generator, - domain=self.data[0].domain, kwargs=kwargs) - self._log() - - def _log(self): - # Log - logger.info( - (f"Optimized {self.data_generator} \ - function (seed={self.func.seed}, " - f"dim={len(self.data[0].domain)}, " - f"noise={self.func.noise}) " - f"with {self.optimizer.get_name()} optimizer for " - f"{len(self.data)} realizations ({self.opt_time:.3f} s).") - ) - - def to_xarray(self) -> xr.Dataset: - xarr = xr.concat( - [realization.to_xarray() - for realization in self.data], - dim=xr.DataArray( - np.arange(len(self.data)), dims='realization')) - - xarr.attrs['number_of_samples']: int = self.number_of_samples - xarr.attrs['realization_seeds']: List[int] = list(self.seeds) - - # Benchmark functions - xarr.attrs['function_seed']: int = self.func.seed - xarr.attrs['function_name']: str = self.data_generator - xarr.attrs['function_noise']: str = self.func.noise - xarr.attrs['function_dimensionality']: int = len(self.data[0].domain) - - # Global minimum function - _, g = self.func.get_global_minimum(d=self.func.dimensionality) - xarr.attrs['function_global_minimum']: float = float( - np.array(g if not isinstance(g, list) else g[0])[0, 0]) - return xarr - - -def run_optimization( - optimizer: Optimizer | str, - data_generator: DataGenerator | str, - sampler: Callable | str, - domain: Domain, - iterations: int, - seed: int, - kwargs: Optional[Dict[str, Any]] = None, - hyperparameters: Optional[Dict[str, Any]] = None, - number_of_samples: int = 30, -) -> ExperimentData: - """Run optimization on some benchmark function - - Parameters - ---------- - optimizer - the optimizer used - data_generator - the data_generator to get objective values - sampler - the sampling strategy - domain - the domain - iterations - number of iterations - seed - seed for the random number generator - kwargs - additional keyword arguments for the data generator - hyperparameters - additional keyword arguments for the optimizer - number_of_samples, optional - number of initial samples, sampled by the sampling strategy - - Returns - ------- - Data object with the optimization data results - """ - if kwargs is None: - kwargs = {} - - if hyperparameters is None: - hyperparameters = {} - - # Set function seed - optimizer = _optimizer_factory( - optimizer=optimizer, domain=domain, hyperparameters=hyperparameters) - - optimizer.set_seed() - - # Sample - data = ExperimentData.from_sampling( - sampler=sampler, domain=domain, n_samples=number_of_samples, seed=seed) - - data.evaluate(data_generator, mode='sequential', kwargs=kwargs) - data.optimize(optimizer=optimizer, data_generator=data_generator, - iterations=iterations, kwargs=kwargs, - hyperparameters=hyperparameters) - - return data - - -@time_and_log -def run_multiple_realizations( - optimizer: Optimizer, - data_generator: DataGenerator | str, - sampler: Callable | str, - domain: Domain, - iterations: int, - realizations: int, - kwargs: Optional[Dict[str, Any]] = None, - hyperparameters: Optional[Dict[str, Any]] = None, - number_of_samples: int = 30, - parallelization: bool = True, - verbal: bool = False, - seed: int or Any = None, -) -> OptimizationResult: - """Run multiple realizations of the same algorithm on a benchmark function - - Parameters - ---------- - optimizer - the optimizer used - data_generator - the data_generator to get objective values - sampler - the sampling strategy - domain - the domain - iterations - number of iterations - realizations - number of realizations - kwargs - additional keyword arguments for the data generator - hyperparameters - additional keyword arguments for the optimizer - number_of_samples, optional - number of initial samples, sampled by the sampling strategy - parallelization, optional - set True to enable parallel execution of each realization - verbal, optional - set True to have more debug message - seed, optional - seed for the random number generator - - Returns - ------- - Object with the optimization data results - """ - - start_timer = perf_counter() - - if kwargs is None: - kwargs = {} - - if hyperparameters is None: - hyperparameters = {} - - if seed is None: - seed = np.random.randint(low=0, high=1e5) - - if parallelization: - args = [ - (optimizer, data_generator, sampler, domain, iterations, - seed + index, kwargs, hyperparameters, number_of_samples) - for index, _ in enumerate(range(realizations)) - ] - - with mp.Pool() as pool: - # maybe implement pool.starmap_async ? - results = pool.starmap(run_optimization, args) - - else: - results = [] - for index in range(realizations): - args = { - "optimizer": optimizer, - "data_generator": data_generator, - "sampler": sampler, - "domain": domain, - "iterations": iterations, - "kwargs": kwargs, - "hyperparameters": hyperparameters, - "number_of_samples": number_of_samples, - "seed": seed + index, - } - results.append(run_optimization(**args)) - - opt_time = perf_counter() - start_timer - - return OptimizationResult( - data=results, - optimizer=optimizer, - data_generator=data_generator, - kwargs=kwargs, - number_of_samples=number_of_samples, - seeds=[seed + i for i in range(realizations)], - opt_time=opt_time, - ) - - -def calculate_mean_std(results: OptimizationResult): # OptimizationResult - mean_y = pd.concat([d._output_data.to_dataframe().cummin() - for d in results.data], axis=1).mean(axis=1) - std_y = pd.concat([d._output_data.to_dataframe().cummin() - for d in results.data], axis=1).std(axis=1) - return mean_y, std_y diff --git a/src/f3dasm/datageneration/__init__.py b/src/f3dasm/datageneration/__init__.py index a5275f56..657e0a71 100644 --- a/src/f3dasm/datageneration/__init__.py +++ b/src/f3dasm/datageneration/__init__.py @@ -4,7 +4,7 @@ # Modules # ============================================================================= -from .._src.datageneration.datagenerator import DataGenerator, convert_function +from .._src.datageneration.datagenerator import DataGenerator # Authorship & Credits # ============================================================================= diff --git a/src/f3dasm/datageneration/abaqus.py b/src/f3dasm/datageneration/abaqus.py deleted file mode 100644 index ecf9afea..00000000 --- a/src/f3dasm/datageneration/abaqus.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Module for data-generation with ABAQUS Simulation software -""" -# Modules -# ============================================================================= - -# Local -from .._src.datageneration.abaqus.abaqus_functions import (post_process, - pre_process) -from .._src.datageneration.abaqus.abaqus_simulator import AbaqusSimulator - -# Authorship & Credits -# ============================================================================= -__author__ = 'Martin van der Schelling (M.P.vanderSchelling@tudelft.nl)' -__credits__ = ['Martin van der Schelling'] -__status__ = 'Stable' -# ============================================================================= -# -# ============================================================================= - -__all__ = [ - 'AbaqusSimulator', - 'post_process', - 'pre_process', -] diff --git a/src/f3dasm/design.py b/src/f3dasm/design.py index cfd2b6c0..47fe19fb 100644 --- a/src/f3dasm/design.py +++ b/src/f3dasm/design.py @@ -6,11 +6,7 @@ # Local from ._src.design.domain import Domain, make_nd_continuous_domain -from ._src.design.parameter import (PARAMETERS, _CategoricalParameter, - _ConstantParameter, _ContinuousParameter, - _DiscreteParameter, _Parameter) -from ._src.experimentdata._data import _Data -from ._src.experimentdata._jobqueue import NoOpenJobsError, Status, _JobQueue +from ._src.experimentdata._jobqueue import NoOpenJobsError, Status # Authorship & Credits # ============================================================================= @@ -22,16 +18,8 @@ # ============================================================================= __all__ = [ - '_CategoricalParameter', - '_ConstantParameter', - '_ContinuousParameter', - '_DiscreteParameter', 'Domain', 'make_nd_continuous_domain', 'NoOpenJobsError', - 'PARAMETERS', - '_Parameter', 'Status', - '_Data', - '_JobQueue', ] diff --git a/src/f3dasm/optimization.py b/src/f3dasm/optimization.py index 507ec636..d5dfb49a 100644 --- a/src/f3dasm/optimization.py +++ b/src/f3dasm/optimization.py @@ -5,19 +5,8 @@ # ============================================================================= # Local -# from ._src._imports import try_import -from ._src.optimization.cg import CG, CG_Parameters -from ._src.optimization.lbfgsb import LBFGSB, LBFGSB_Parameters -from ._src.optimization.neldermead import NelderMead, NelderMead_Parameters -from ._src.optimization.optimizer import Optimizer, OptimizerParameters +from ._src.optimization.optimizer import Optimizer from ._src.optimization.optimizer_factory import OPTIMIZERS -from ._src.optimization.randomsearch import (RandomSearch, - RandomSearch_Parameters) - -# # Try importing f3dasm_optimize package -# with try_import('f3dasm_optimize') as _imports: -# import f3dasm_optimize - # Authorship & Credits # ============================================================================= @@ -29,19 +18,6 @@ # ============================================================================= __all__ = [ - 'CG', - 'CG_Parameters', - 'LBFGSB', - 'LBFGSB_Parameters', - 'NelderMead', - 'NelderMead_Parameters', 'Optimizer', - 'OptimizerParameters', 'OPTIMIZERS', - 'RandomSearch', - 'RandomSearch_Parameters', ] - -# # Add the optimizers from f3dasm_optimize if applicable -# if _imports.is_successful(): -# OPTIMIZERS.extend(f3dasm_optimize.OPTIMIZERS) diff --git a/tests/datageneration/test_datagenerator.py b/tests/datageneration/test_datagenerator.py index beb2bd08..5a757ebe 100644 --- a/tests/datageneration/test_datagenerator.py +++ b/tests/datageneration/test_datagenerator.py @@ -3,7 +3,8 @@ import pytest from f3dasm import ExperimentData -from f3dasm.datageneration import DataGenerator, convert_function +from f3dasm._src.datageneration.datagenerator import convert_function +from f3dasm.datageneration import DataGenerator pytestmark = pytest.mark.smoke diff --git a/tests/design/conftest.py b/tests/design/conftest.py index 675c0328..4b749991 100644 --- a/tests/design/conftest.py +++ b/tests/design/conftest.py @@ -1,8 +1,10 @@ import pandas as pd import pytest -from f3dasm.design import (Domain, _CategoricalParameter, _ContinuousParameter, - _DiscreteParameter) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ContinuousParameter, + _DiscreteParameter) +from f3dasm.design import Domain @pytest.fixture(scope="package") diff --git a/tests/design/test_data.py b/tests/design/test_data.py index 7fd5e7e5..0d546ccd 100644 --- a/tests/design/test_data.py +++ b/tests/design/test_data.py @@ -3,7 +3,8 @@ import pandas as pd import pytest -from f3dasm.design import Domain, _Data +from f3dasm._src.experimentdata._data import _Data +from f3dasm.design import Domain pytestmark = pytest.mark.smoke diff --git a/tests/design/test_designofexperiments.py b/tests/design/test_designofexperiments.py index 8641b6f7..29354121 100644 --- a/tests/design/test_designofexperiments.py +++ b/tests/design/test_designofexperiments.py @@ -2,8 +2,10 @@ import pandas as pd import pytest -from f3dasm.design import (Domain, _CategoricalParameter, _ContinuousParameter, - _DiscreteParameter, make_nd_continuous_domain) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ContinuousParameter, + _DiscreteParameter) +from f3dasm.design import Domain, make_nd_continuous_domain pytestmark = pytest.mark.smoke diff --git a/tests/design/test_jobqueue.py b/tests/design/test_jobqueue.py index 336d79e6..efc4b8e3 100644 --- a/tests/design/test_jobqueue.py +++ b/tests/design/test_jobqueue.py @@ -1,9 +1,8 @@ -from typing import Iterable - import pandas as pd import pytest -from f3dasm.design import NoOpenJobsError, Status, _JobQueue +from f3dasm._src.experimentdata._jobqueue import _JobQueue +from f3dasm.design import NoOpenJobsError, Status pytestmark = pytest.mark.smoke diff --git a/tests/design/test_space.py b/tests/design/test_space.py index 47df8f37..5670fa77 100644 --- a/tests/design/test_space.py +++ b/tests/design/test_space.py @@ -1,8 +1,10 @@ import numpy as np import pytest -from f3dasm.design import (_CategoricalParameter, _ConstantParameter, - _ContinuousParameter, _DiscreteParameter) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ConstantParameter, + _ContinuousParameter, + _DiscreteParameter) pytestmark = pytest.mark.smoke diff --git a/tests/experimentdata/conftest.py b/tests/experimentdata/conftest.py index b6d59d67..f2b70947 100644 --- a/tests/experimentdata/conftest.py +++ b/tests/experimentdata/conftest.py @@ -1,15 +1,15 @@ - from __future__ import annotations import numpy as np import pandas as pd import pytest import xarray as xr -from omegaconf import OmegaConf from f3dasm import ExperimentData -from f3dasm.design import (Domain, _CategoricalParameter, _ContinuousParameter, - _DiscreteParameter, make_nd_continuous_domain) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ContinuousParameter, + _DiscreteParameter) +from f3dasm.design import Domain, make_nd_continuous_domain SEED = 42 diff --git a/tests/experimentdata/test_experimentdata.py b/tests/experimentdata/test_experimentdata.py index 582aa401..4b682997 100644 --- a/tests/experimentdata/test_experimentdata.py +++ b/tests/experimentdata/test_experimentdata.py @@ -11,9 +11,10 @@ import xarray as xr from f3dasm import ExperimentData, ExperimentSample +from f3dasm._src.design.parameter import _ContinuousParameter from f3dasm._src.experimentdata._data import DataTypes, _Data -from f3dasm.design import (Domain, Status, _ContinuousParameter, _JobQueue, - make_nd_continuous_domain) +from f3dasm._src.experimentdata._jobqueue import _JobQueue +from f3dasm.design import Domain, Status, make_nd_continuous_domain pytestmark = pytest.mark.smoke diff --git a/tests/optimization/conftest.py b/tests/optimization/conftest.py index 01564261..17884367 100644 --- a/tests/optimization/conftest.py +++ b/tests/optimization/conftest.py @@ -1,7 +1,8 @@ import pytest from f3dasm import ExperimentData -from f3dasm.design import Domain, _ContinuousParameter +from f3dasm._src.design.parameter import _ContinuousParameter +from f3dasm.design import Domain @pytest.fixture(scope="package") diff --git a/tests/optimization/test_all_optimizers.py b/tests/optimization/test_all_optimizers.py index ef1c23a8..b8005aa3 100644 --- a/tests/optimization/test_all_optimizers.py +++ b/tests/optimization/test_all_optimizers.py @@ -19,7 +19,7 @@ @pytest.mark.parametrize("optimizer", OPTIMIZERS) def test_get_info(data: ExperimentData, optimizer: str): opt: Optimizer = _optimizer_factory(optimizer, data.domain) - characteristics = opt.get_info() + characteristics = opt._get_info() assert isinstance(characteristics, List) @@ -115,7 +115,7 @@ def test_optimizer_iterations(iterations: int, data_generator: str, _optimizer = _optimizer_factory(optimizer, domain=domain) - if x0_selection == "new" and iterations < _optimizer.hyperparameters.population: + if x0_selection == "new" and iterations < _optimizer._population: with pytest.raises(ValueError): data.optimize(optimizer=optimizer, data_generator=data_generator, iterations=iterations, kwargs={'seed': seed, 'noise': None, diff --git a/tests/optimization/test_run_optimization.py b/tests/optimization/test_run_optimization.py index 86c6ace5..3aa3639e 100644 --- a/tests/optimization/test_run_optimization.py +++ b/tests/optimization/test_run_optimization.py @@ -1,12 +1,247 @@ +from __future__ import annotations + import os +# Standard +from time import perf_counter +from typing import Any, Callable, Dict, List, Optional +# Third-party import numpy as np import pytest +import xarray as xr +from pathos.helpers import mp -from f3dasm import run_multiple_realizations +# Locals +from f3dasm import ExperimentData, logger +from f3dasm._src.datageneration.functions.function_factory import \ + _datagenerator_factory +from f3dasm._src.optimization.optimizer_factory import _optimizer_factory +from f3dasm.datageneration import DataGenerator from f3dasm.datageneration.functions import FUNCTIONS_2D, FUNCTIONS_7D -from f3dasm.design import make_nd_continuous_domain -from f3dasm.optimization import OPTIMIZERS +from f3dasm.design import Domain, make_nd_continuous_domain +from f3dasm.optimization import OPTIMIZERS, Optimizer + + +class OptimizationResult: + def __init__(self, data: List[ExperimentData], optimizer: Optimizer, + kwargs: Optional[Dict[str, Any]], + data_generator: DataGenerator, + number_of_samples: int, seeds: List[int], + opt_time: float = 0.0): + """Optimization results object + + Parameters + ---------- + data + Data objects for each realization + optimizer + classname of the optimizer used + data_generator + the data_generator to get objective values + kwargs + the kwargs used for the data_generator + number_of_samples + number of initial samples, sampled by the sampling strategy + seeds + list of seeds that were used for each realization + opt_time + total optimization time + """ + self.data = data + self.optimizer = _optimizer_factory( + optimizer=optimizer, domain=self.data[0].domain) + self.data_generator = data_generator + self.kwargs = kwargs, + self.number_of_samples = number_of_samples + self.seeds = seeds + self.opt_time = opt_time + + self.func = _datagenerator_factory( + data_generator=self.data_generator, + domain=self.data[0].domain, kwargs=kwargs) + self._log() + + def _log(self): + # Log + logger.info( + (f"Optimized {self.data_generator} \ + function (seed={self.func.seed}, " + f"dim={len(self.data[0].domain)}, " + f"noise={self.func.noise}) " + f"with {self.optimizer.__class__.__name__} optimizer for " + f"{len(self.data)} realizations ({self.opt_time:.3f} s).") + ) + + def to_xarray(self) -> xr.Dataset: + xarr = xr.concat( + [realization.to_xarray() + for realization in self.data], + dim=xr.DataArray( + np.arange(len(self.data)), dims='realization')) + + xarr.attrs['number_of_samples']: int = self.number_of_samples + xarr.attrs['realization_seeds']: List[int] = list(self.seeds) + + # Benchmark functions + xarr.attrs['function_seed']: int = self.func.seed + xarr.attrs['function_name']: str = self.data_generator + xarr.attrs['function_noise']: str = self.func.noise + xarr.attrs['function_dimensionality']: int = len(self.data[0].domain) + + # Global minimum function + _, g = self.func.get_global_minimum(d=self.func.dimensionality) + xarr.attrs['function_global_minimum']: float = float( + np.array(g if not isinstance(g, list) else g[0])[0, 0]) + return xarr + + +def run_optimization( + optimizer: Optimizer | str, + data_generator: DataGenerator | str, + sampler: Callable | str, + domain: Domain, + iterations: int, + seed: int, + kwargs: Optional[Dict[str, Any]] = None, + hyperparameters: Optional[Dict[str, Any]] = None, + number_of_samples: int = 30, +) -> ExperimentData: + """Run optimization on some benchmark function + + Parameters + ---------- + optimizer + the optimizer used + data_generator + the data_generator to get objective values + sampler + the sampling strategy + domain + the domain + iterations + number of iterations + seed + seed for the random number generator + kwargs + additional keyword arguments for the data generator + hyperparameters + additional keyword arguments for the optimizer + number_of_samples, optional + number of initial samples, sampled by the sampling strategy + + Returns + ------- + Data object with the optimization data results + """ + if kwargs is None: + kwargs = {} + + if hyperparameters is None: + hyperparameters = {} + + # Set function seed + optimizer = _optimizer_factory( + optimizer=optimizer, domain=domain, hyperparameters=hyperparameters) + + # Sample + data = ExperimentData.from_sampling( + sampler=sampler, domain=domain, n_samples=number_of_samples, seed=seed) + + data.evaluate(data_generator, mode='sequential', kwargs=kwargs) + data.optimize(optimizer=optimizer, data_generator=data_generator, + iterations=iterations, kwargs=kwargs, + hyperparameters=hyperparameters) + + return data + + +def run_multiple_realizations( + optimizer: Optimizer, + data_generator: DataGenerator | str, + sampler: Callable | str, + domain: Domain, + iterations: int, + realizations: int, + kwargs: Optional[Dict[str, Any]] = None, + hyperparameters: Optional[Dict[str, Any]] = None, + number_of_samples: int = 30, + parallelization: bool = True, + verbal: bool = False, + seed: int | Any = None, +) -> OptimizationResult: + """Run multiple realizations of the same algorithm on a benchmark function + + Parameters + ---------- + optimizer + the optimizer used + data_generator + the data_generator to get objective values + sampler + the sampling strategy + domain + the domain + iterations + number of iterations + realizations + number of realizations + kwargs + additional keyword arguments for the data generator + hyperparameters + additional keyword arguments for the optimizer + number_of_samples, optional + number of initial samples, sampled by the sampling strategy + parallelization, optional + set True to enable parallel execution of each realization + verbal, optional + set True to have more debug message + seed, optional + seed for the random number generator + + Returns + ------- + Object with the optimization data results + """ + + start_timer = perf_counter() + + if kwargs is None: + kwargs = {} + + if hyperparameters is None: + hyperparameters = {} + + if seed is None: + seed = np.random.randint(low=0, high=1e5) + + if parallelization: + args = [ + (optimizer, data_generator, sampler, domain, iterations, + seed + index, kwargs, hyperparameters, number_of_samples) + for index, _ in enumerate(range(realizations)) + ] + + with mp.Pool() as pool: + # maybe implement pool.starmap_async ? + results = pool.starmap(run_optimization, args) + + else: + results = [] + for index in range(realizations): + args = { + "optimizer": optimizer, + "data_generator": data_generator, + "sampler": sampler, + "domain": domain, + "iterations": iterations, + "kwargs": kwargs, + "hyperparameters": hyperparameters, + "number_of_samples": number_of_samples, + "seed": seed + index, + } + results.append(run_optimization(**args)) + + opt_time = perf_counter() - start_timer @pytest.mark.smoke @@ -26,7 +261,8 @@ def test_run_multiple_realizations(data_generator: str, optimizer: str, dimensio realizations = 3 domain = np.tile([0.0, 1.0], (dimensionality, 1)) - domain = make_nd_continuous_domain(dimensionality=dimensionality, bounds=domain) + domain = make_nd_continuous_domain( + dimensionality=dimensionality, bounds=domain) kwargs = {'scale_bounds': domain.get_bounds()} sampler = 'random' diff --git a/tests/sampling/conftest.py b/tests/sampling/conftest.py index c48278be..dc14fcd6 100644 --- a/tests/sampling/conftest.py +++ b/tests/sampling/conftest.py @@ -1,7 +1,9 @@ import pytest -from f3dasm.design import (_CategoricalParameter, _DiscreteParameter, Domain, - _ContinuousParameter) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ContinuousParameter, + _DiscreteParameter) +from f3dasm.design import Domain @pytest.fixture(scope="package") diff --git a/tests/sampling/test_hypothesis.py b/tests/sampling/test_hypothesis.py index 8beaaa8b..821a1fd9 100644 --- a/tests/sampling/test_hypothesis.py +++ b/tests/sampling/test_hypothesis.py @@ -6,8 +6,10 @@ from hypothesis.strategies import (SearchStrategy, composite, floats, integers, text) -from f3dasm.design import (Domain, _CategoricalParameter, _ContinuousParameter, - _DiscreteParameter, _Parameter) +from f3dasm._src.design.parameter import (_CategoricalParameter, + _ContinuousParameter, + _DiscreteParameter, _Parameter) +from f3dasm.design import Domain pytestmark = pytest.mark.smoke diff --git a/tests/sampling/test_sampling.py b/tests/sampling/test_sampling.py index 419e8169..b542840b 100644 --- a/tests/sampling/test_sampling.py +++ b/tests/sampling/test_sampling.py @@ -4,7 +4,8 @@ from pandas.testing import assert_frame_equal from f3dasm import ExperimentData -from f3dasm.design import Domain, _ContinuousParameter +from f3dasm._src.design.parameter import _ContinuousParameter +from f3dasm.design import Domain pytestmark = pytest.mark.smoke