.. Copyright 2021 IRT Saint ExupĂ©ry, https://www.irt-saintexupery.com This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. .. Contributors: :author: Matthias De Lozzo .. _nutshell_design_space: How to deal with design spaces ============================== 1. How is a design space defined? ********************************* 1.a. What is a design space made of? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A design space is defined by: - the number of the variables - the names of the variables - the sizes of the variables - the upper bounds of the variables (optional; by default: :math:-\infty) - the lower bounds of the variables (optional; by default: :math:+\infty) - the normalization policies of the variables: - bounded float variables are normalizable, - bounded interger variables are normalizable, - unbounded float variables are not normalizable, - unbounded integer variables are not normalizable. .. note:: The normalized version of a given variable :math:x is either :math:\frac{x-lb_x}{ub_x-lb_x} or :math:\frac{x}{ub_x-lb_x}. 1.b. How is a design space implemented in |g|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Design space are implemented in |g| through the :class:.DesignSpace class. 1.c. What are the API functions in |g|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A design space can be created from the :meth:~gemseo.api.create_design_space and :meth:~gemseo.api.read_design_space API functions and then, enhanced by methods of the :class:.DesignSpace class. It can be exported to a file by means of the :meth:~gemseo.api.export_design_space. 2. How to read a design space from a file? ****************************************** Case 1: the file contains a header line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's consider the *design_space.txt* file containing the following lines: .. code:: name lower_bound value upper_bound type x1 -1. 0. 1. float x2 5. 6. 8. float We can read this file by means of the :meth:~gemseo.api.read_design_space function API: .. code:: from gemseo.api import read_design_space design_space = read_design_space('design_space.txt') and print it: .. code:: print(design_space) which results in: .. code:: Design Space: +------+-------------+-------+-------------+-------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+-------+ | x1 | -1 | 0 | 1 | float | | x2 | 5 | 6 | 8 | float | +------+-------------+-------+-------------+-------+ Case 2: the file does not contain a header line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now, let's consider the *design_space_without_header.txt* file containing the following lines: .. code:: x1 -1. 0. 1. float x2 5. 6. 8. float We can read this file by means of the :meth:~gemseo.api.read_design_space API function with the list of labels as optional argument: .. code:: from gemseo.api import read_design_space design_space = read_design_space( "design_space_without_header.txt", ["name", "lower_bound", "value", "upper_bound", "type"], ) and print it: .. code:: print(design_space) which results in: .. code:: Design Space: +------+-------------+-------+-------------+-------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+-------+ | x1 | -1 | 0 | 1 | float | | x2 | 5 | 6 | 8 | float | +------+-------------+-------+-------------+-------+ .. warning:: - User must provide the following minimal fields in the file defining the design space: :code:'name', :code:'lower_bound' and :code:'upper_bound'. - The inequality :code:'lower_bound' <= :code:'name' <= :code:'upper_bound' must be satisfied. .. note:: - Available fields are :code:'name', :code:'lower_bound', :code:'upper_bound', :code:'value' and :code:'type'. - The :code:'value' field is optional. By default, it is set at :code:None. - The :code:'type' field is optional. By default, it is set at :code:float. - Each dimension of a variable must be provided. E.g. when the :code:'size' of :code:'x1' is 2: .. code:: name lower_bound value upper_bound type x1 -1. 0. 1. float x1 -3. -1. 1. float x2 5. 6. 8. float .. note:: - Lower infinite bound is encoded :code:-inf' or :code:'-Inf'. - Upper infinite bound is encoded :code:'inf', :code:'Inf', :code:'+inf' or :code:'+Inf'. 3. How to create a design space from scratch? ********************************************* Let's imagine that we want to build a design space with the following requirements: - *x1* is a one-dimensional unbounded float variable, - *x2* is a one-dimensional unbounded integer variable, - *x3* is a two-dimensional unbounded float variable, - *x4* is a one-dimensional float variable with lower bound equal to 1, - *x5* is a one-dimensional float variable with upper bound equal to 1, - *x6* is a one-dimensional unbounded float variable, - *x7* is a two-dimensional bounded integer variable with lower bound equal to -1, upper bound equal to 1 and current values to (0,1), We can create this design space from scratch by means of the :meth:~gemseo.api.create_design_space API function and the :meth:.DesignSpace.add_variable method of the :class:.DesignSpace class: .. code:: from gemseo.api import create_design_space from numpy import ones, array design_space = create_design_space() design_space.add_variable('x1') design_space.add_variable('x2', var_type='integer') design_space.add_variable('x3', size=2) design_space.add_variable('x4', l_b=ones(1)) design_space.add_variable('x5', u_b=ones(1)) design_space.add_variable('x6', value=ones(1)) design_space.add_variable( "x7", size=2, var_type="integer", value=array([0, 1]), l_b=-ones(2), u_b=ones(2) ) and print it: .. code:: print(design_space) which results in: .. code:: Design Space: +------+-------------+-------+-------------+---------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+---------+ | x1 | -inf | None | inf | float | | x2 | -inf | None | inf | integer | | x3 | -inf | None | inf | float | | x3 | -inf | None | inf | float | | x4 | 1 | None | inf | float | | x5 | -inf | None | 1 | float | | x6 | -inf | 1 | inf | float | | x7 | -1 | 0 | 1 | integer | | x7 | -1 | 1 | 1 | integer | +------+-------------+-------+-------------+---------+ .. note:: For a variable whose :code:'size' is greater than 1, each dimension of this variable is printed (e.g. :code:'x3' and :code:'x7'). .. note:: We can get a list of the variable names with theirs indices by means of the :meth:.DesignSpace.get_indexed_variables_names method: .. code:: indexed_variables_names = design_space.get_indexed_variables_names() and :code:print(indexed_variables_names): .. code:: ['x1', 'x2', 'x3!0', 'x3!1', 'x4', 'x5', 'x6', 'x7!0', 'x7!1'] We see that the multidimensional variables have an index (here :code:'0' and :code:'1') preceded by a :code:'!' separator. 4. How to get information about the design space? ************************************************* How to get the size of a design variable? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the size of a variable by means of the :meth:.DesignSpace.get_size method: .. code:: x3_size = design_space.get_size('x3') and :code:print(x3_size) to see the result: .. code:: 1 How to get the type of a design variable? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the type of a variable by means of the :meth:.DesignSpace.get_type method: .. code:: x3_type = design_space.get_type('x3') and :code:print(x3_type) to see the result: .. code:: ['float'] How to get the size of a lower or upper bound for a given variable? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the lower and upper bounds of a variable by means of the :meth:.DesignSpace.get_lower_bound and :meth:.DesignSpace.get_upper_bound methods: .. code:: x3_lb = design_space.get_lower_bound('x3') x3_ub = design_space.get_upper_bound('x3') and :code:print(x3_lb, x3_ub) to see the result: .. code:: [-10.], [ 10.] How to get the size of a lower or upper bound for a set of given variables? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the lower and upper bounds of a set of variables by means of the :meth:.DesignSpace.get_lower_bounds and :meth:.DesignSpace.get_upper_bounds methods: .. code:: x1x3_lb = design_space.get_lower_bounds(['x1', 'x3']) x1x3_ub = design_space.get_upper_bounds(['x1', 'x3']) and :code:print(x1x3_lb, x1x3_ub) to see the result: .. code:: [-10. -10.], [ 10. 10.] How to get the current array value of the design parameter vector? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the current value of the design parameters by means of the :meth:.DesignSpace.get_lower_bounds and :meth:.DesignSpace.get_current_x method: .. code:: current_x = design_space.get_current_x and :code:print(current_x) to see the result: .. code:: [ 3. 1. 1. 1.] How to get the current dictionary value of the design parameter vector? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the current value of the design parameters with :code:dict format by means of the :meth:.DesignSpace.get_lower_bounds and :meth:.DesignSpace.get_current_x_dict method: .. code:: dict_current_x = design_space.get_current_x_dict and :code:print(dict_current_x) to see the result: .. code:: {'x2': array([1.]), 'x3': array([1.]), 'x1': array([3.]), 'x6': array([1.])} How to get the normalized array value of the design parameter vector? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the normalized current value of the design parameters by means of the :meth:.DesignSpace.get_lower_bounds and :meth:.DesignSpace.get_current_x_normalized method: .. code:: normalized_current_x = design_space.get_current_x_normalized and :code:print(normalized_current_x) to see the result: .. code:: [ 0.65 1. 0.55 0.55] How to get the active bounds at the current design parameter vector or at a given one? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can get the active bounds by means of the :meth:.DesignSpace.get_lower_bounds and :meth:.DesignSpace.get_active_bounds method, either at current parameter values: .. code:: active_at_current_x = design_space.get_active_bounds() and :code:print(active_at_current_x) to see the result: .. code:: ({'x2': array([False], dtype=bool), 'x3': array([False], dtype=bool), 'x1': array([False], dtype=bool), 'x6': array([False], dtype=bool)}, {'x2': array([False], dtype=bool), 'x3': array([False], dtype=bool), 'x1': array([False], dtype=bool), 'x6': array([False], dtype=bool)}) or at a given point: .. code:: active_at_given_point = design_space.get_active_bounds(array([1., 10, 1., 1.])) and :code:print(active_at_given_point) to see the result: .. code.. ({'x2': array([False], dtype=bool), 'x3': array([False], dtype=bool), 'x1': array([False], dtype=bool), 'x6': array([False], dtype=bool)}, {'x2': array([ True], dtype=bool), 'x3': array([False], dtype=bool), 'x1': array([False], dtype=bool), 'x6': array([False], dtype=bool)}) 5. How to modify a design space? ******************************** How to remove a variable from a design space? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's consider the previous design space and assume that we want to remove the :code:'x4' variable. For that, we can use the :meth:.DesignSpace.remove_variable method: .. code:: design_space.remove_variable('x4') and :code:print(design_space) to see the result: .. code:: Design Space: +------+-------------+-------+-------------+---------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+---------+ | x1 | -inf | None | inf | float | | x2 | -inf | None | inf | integer | | x3 | -inf | None | inf | float | | x3 | -inf | None | inf | float | | x5 | -inf | None | 1 | float | | x6 | -inf | 1 | inf | float | | x7 | -1 | 0 | 1 | integer | | x7 | -1 | 1 | 1 | integer | +------+-------------+-------+-------------+---------+ How to filter the entries of a design space? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can keep only a subset of variables, e.g. :code:'x1', :code:'x2', :code:'x3' and :code:'x6', by means of the :meth:gemseo.algos.design_space.DesignSpace.filter method: .. code:: design_space.filter(['x1', 'x2', 'x3', 'x6']) # keep the x1, x2, x3 and x6 variables and :code:print(design_space) to see the result: .. code:: Design Space: +------+-------------+-------+-------------+---------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+---------+ | x1 | -inf | None | inf | float | | x2 | -inf | None | inf | integer | | x3 | -inf | None | inf | float | | x3 | -inf | None | inf | float | | x6 | -inf | 1 | inf | float | +------+-------------+-------+-------------+---------+ We can also keep only a subset of components for a given variable, e.g. the first component of the variable :code:'x3', by means of the :meth:gemseo.algos.design_space.DesignSpace.filter_dim method: .. code:: design_space.filer_dim('x3', [0]) # keep the first dimension of x3 and :code:print(design_space) to see the result: .. code:: Design Space: +------+-------------+-------+-------------+---------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+---------+ | x1 | -inf | None | inf | float | | x2 | -inf | None | inf | integer | | x3 | -inf | None | inf | float | | x6 | -inf | 1 | inf | float | +------+-------------+-------+-------------+---------+ How to modify the data values contained in a design space? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can change the current values and bounds contained in a design space by means of the :meth:.DesignSpace.set_current_x, :meth:.DesignSpace.set_current_variable, :meth:.DesignSpace.set_lower_bound and :meth:.DesignSpace.set_upper_bound methods: .. code:: design_space.set_current_x(array([1., 1., 1., 1.])) design_space.set_current_variable('x1', array([3.])) design_space.set_lower_bound('x1', array([-10.])) design_space.set_lower_bound('x2', array([-10.])) design_space.set_lower_bound('x3', array([-10.])) design_space.set_lower_bound('x6', array([-10.])) design_space.set_upper_bound('x1', array([10.])) design_space.set_upper_bound('x2', array([10.])) design_space.set_upper_bound('x3', array([10.])) design_space.set_upper_bound('x6', array([10.])) and :code:print(design_space) to see the result: .. code:: Design Space: +------+-------------+-------+-------------+---------+ | name | lower_bound | value | upper_bound | type | +------+-------------+-------+-------------+---------+ | x1 | -10 | 3 | 10 | float | | x2 | -10 | 1 | 10 | integer | | x3 | -10 | 1 | 10 | float | | x6 | -10 | 1 | 10 | float | +------+-------------+-------+-------------+---------+ 6. How to (un)normalize a parameter vector? ******************************************* Let's consider the parameter vector :code:x_vect = array([1.,10.,1.,1.]). We can normalize this vector by means of the :meth:.DesignSpace.normalize_vect: .. code:: normalized_x_vect = design_space.normalize_vect(x_vect) and :code:print(normalized_x_vect): .. code:: [ 0.55 1. 0.55 0.55] Conversely, we can unnormalize this normalized vector by means of the :meth:.DesignSpace.unnormalize_vect: .. code:: unnormalized_x_vect = design_space.unnormalize_vect(x_vect) .. code:: [ 1. 10. 1. 1.] .. note:: Both methods takes an optional argument denoted :code:'minus_lb' which is :code:True by default. If :code:True, the normalization of the normalizable variables is of the form :code:(x-lb_x)/(ub_x-lb_x). Otherwise, it is of the form :code:x/(ub_x-lb_x). Here, when :code:minus_lb is :code:False, the normalize parameter vector is: .. code:: [ 0.05 0.5 0.05 0.05] 7. How to cast design data? *************************** How to cast a design point from array to dict? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can cast a design point from :code:array to :code:dict by means of the :meth:.DesignSpace.array_to_dict method: .. code:: array_point = array([1, 2, 3, 4]) dict_point = design_space.array_to_dict(array_point) and :code:print(dict_point) to see the result: .. code:: {'x2': array([2]), 'x3': array([3]), 'x1': array([1]), 'x6': array([4])} How to cast a design point from dict to array? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can cast a design point from :code:dict to :code:array by means of the :meth:.DesignSpace.dict_to_array method: .. code:: new_array_point = design_space.dict_to_array(dict_point) and :code:print(new_array_point) to see the result: .. code:: [1, 2, 3, 4] .. note:: - An optional argument denoted :code:'all_vars', which is a boolean and set at :code:True by default, indicates if all design variables shall be in the :code:dict passed in argument. - An optional argument denoted :code:'all_var_list', which is a list of string and set at :code:None by default, list all of the variables to consider. If :code:None, all design variables are considerd. How to cast the current value to complex? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can cast the current value to complex by means of the :meth:.DesignSpace.to_complex method: .. code:: print(design_space.get_current_x()) design_space.to_complex() print(design_space.get_current_x()) and the successive printed messages are: .. code:: [ 3. 1. 1. 1.] [ 3.+0.j 1.+0.j 1.+0.j 1.+0.j] How to cast the right component values of a vector to integer? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For a given vector where some components should be integer, it is possible to round them by means of the :meth:.DesignSpace.round_vect method: .. code:: vector = array([1.3, 3.4,3.6, -1.4]) rounded_vector = design_space.round_vect(vector) and :code:print(rounded_vector) to see the result: .. code:: [ 1.3 3. 3.6 -1.4] 8. How to test if the current value is defined? *********************************************** We can test if the design parameter set has a current :code:'value' by means of the :meth:.DesignSpace.has_current_x: .. code:: print(design_space.has_current_x()) which results in: .. code:: True .. note:: The result returned by :meth:.DesignSpace.has_current_x is :code:False as long as at least one component of one variable is :code:None. 9. How to project a point into bounds? ************************************** Sometimes, components of a design vector are greater than the upper bounds or lower than the upper bounds. For that, it is possible to project the vector into the bounds by means of the :meth:.DesignSpace.project_into_bounds: .. code:: point = array([1.,3,-15.,23.]) p_point = design_space.project_into_bounds(point) and :code:print(p_point) to see the result: .. code:: [ 1. 3. -10. 10.] 10. How to export a design space to a file? ******************************************* When the design space is created, it is possible to export it by means of the :meth:~gemseo.api.export_design_space API function with arguments: - :code:design_space: design space - :code:output_file: output file path - :code:export_hdf: export to a hdf file (True, default) or a txt file (False) - :code:fields: list of fields to export, by default all - :code:append: if :code:True, appends the data in the file - :code:table_options: dictionary of options for the :class:~gemseo.third_party.prettytable.prettytable.PrettyTable For example: .. code:: from gemseo.api import export_design_space export_design_space(design_space, 'new_design_space.txt')