1 /* Copyright 2017-2021 PaGMO development team
2 
3 This file is part of the PaGMO library.
4 
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8   * the GNU Lesser General Public License as published by the Free
9     Software Foundation; either version 3 of the License, or (at your
10     option) any later version.
11 
12 or
13 
14   * the GNU General Public License as published by the Free Software
15     Foundation; either version 3 of the License, or (at your option) any
16     later version.
17 
18 or both in parallel, as here.
19 
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library.  If not,
27 see https://www.gnu.org/licenses/. */
28 
29 // In this tutorial we learn how to implement the gradients and the hessians
30 // of each of fitness function components.
31 //
32 // Consider the simple problem:
33 // f = x1^2 + x2^2 + x3^2 + x4^2 in the bounds:
34 // -10 <= xi <= 10
35 //
36 
37 // All we need to do is to implement a struct (or class) having the
38 // following mandatory methods:
39 //
40 // vector_double fitness(const vector_double &) const
41 // std::pair<vector_double, vector_double> get_bounds() const
42 //
43 // And add the methods:
44 // vector_double gradient(const vector_double &x) const
45 // std::vector<vector_double> hessians(const vector_double &) const
46 //
47 // Gradient:
48 // In PaGMO, each component fi of the fitness may be associated
49 // with its gradient dfi/dxj. The user may implement the optional
50 // method:
51 //
52 // vector_double gradient(const vector_double &x) const
53 //
54 // returning a vector_double containing dfi/dxj in the order defined by the
55 // gradient sparsity pattern which, by default is dense and is:
56 // [(0,0),(0,1), .., (1,0), (1,1), ..]
57 //
58 // The default dense gradient sparsity can be overridden by
59 // adding one optional method
60 //
61 // sparsity_pattern gradient_sparsity() const
62 //
63 // The gradient sparsity pattern is a std::vector of pairs (i,j)
64 // containing the indeces of non null entries of the gradients.
65 // Note that the dimensions of the sparsity pattern of the gradients
66 // must match that of the value returned by the implemented gradient
67 // method
68 //
69 //
70 // Hessians:
71 // In PaGMO each component fk of the fitness
72 // may be associated to an Hessian containing d^2fk/dxj/dxi.
73 // The user may implement the additional method:
74 //
75 // std::vector<vector_double> hessians(const vector_double &) const
76 //
77 // returning a vector of vector_double. Each vector_double contains
78 // the hessian of the relative fitness component. Each hessian
79 // being symmetric PaGMO only allow the definition of the diagonal and
80 // lower triangular compoents in the order defined by the
81 // hessians sparsity pattern which, by default is dense and is:
82 // [[(0,0),(1,0), (1,1), (2,0), (2,1), ...], [...], ...]
83 //
84 // The default dense hessians sparsity can be overridden by
85 // adding one optional method
86 //
87 // sparsity_pattern hessians_sparsity() const
88 //
89 // In this example we explicitly define the sparsity patterns as
90 // to clarify the above notation.
91 
92 #include <iostream>
93 #include <string>
94 #include <vector>
95 
96 #include <pagmo/io.hpp>
97 #include <pagmo/problem.hpp>
98 #include <pagmo/types.hpp>
99 
100 using namespace pagmo;
101 struct problem_basic_gh {
102     // Mandatory, computes ... well ... the fitness
fitnessproblem_basic_gh103     vector_double fitness(const vector_double &x) const
104     {
105         return {x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3]};
106     }
107 
108     // Mandatory, returns the box-bounds
get_boundsproblem_basic_gh109     std::pair<vector_double, vector_double> get_bounds() const
110     {
111         return {{-10, -10, -10, -10}, {10, 10, 10, 10}};
112     }
113 
114     // Optional, computes the gradients. In this simple case only one
115     // df0/dx0, df0/dx1, df0/dx2, df0/dx3
gradientproblem_basic_gh116     vector_double gradient(const vector_double &x) const
117     {
118         return {2 * x[0], 2 * x[1], 2 * x[2], 2 * x[3]};
119     }
120 
121     // Optional. Returns the sparsity of the problem as a sparsity_pattern
122     // that is pairs (i,j) indicating that the j-th variable
123     // "influences" the i-th component in the fitness.
124     // When not implemented a dense problem is assumed.
gradient_sparsityproblem_basic_gh125     sparsity_pattern gradient_sparsity() const
126     {
127         return {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
128     }
129 
130     // Optional. Returns the Hessians of the various fitness
131     // components fk. That is d^2fk/dxi/dxj. In this case we have only
132     // one fitness component, thus we only need one Hessian which is
133     // also sparse as most of its components are 0.
hessiansproblem_basic_gh134     std::vector<vector_double> hessians(const vector_double &) const
135     {
136         return {{2., 2., 2., 2.}};
137     }
138 
139     // Optional. Returns the sparsity of the hessians.
hessians_sparsityproblem_basic_gh140     std::vector<sparsity_pattern> hessians_sparsity() const
141     {
142         return {{{0, 0}, {1, 1}, {2, 2}, {3, 3}}};
143     }
144 
145     // Optional, provides a name for the problem overrding the default name
get_nameproblem_basic_gh146     std::string get_name() const
147     {
148         return "My Problem with derivatives!!";
149     }
150 
151     // Optional, provides extra information that will be appended after
152     // the default stream operator
get_extra_infoproblem_basic_gh153     std::string get_extra_info() const
154     {
155         std::ostringstream s;
156         s << "This is a simple toy problem with one fitness, " << '\n';
157         s << "no constraint and a fixed dimension of 4."
158           << "\n";
159         s << "The fitness function gradient and hessians are also implemented"
160           << "\n";
161         s << "The sparsity of the gradient and hessians is user provided"
162           << "\n";
163         return s.str();
164     }
165 
166     // Optional methods-data can also be accessed later via
167     // the problem::extract() method
best_knownproblem_basic_gh168     vector_double best_known() const
169     {
170         return {0, 0, 0, 0};
171     }
172 };
173 
main()174 int main()
175 {
176     // Constructing a problem
177     problem p0{problem_basic_gh{}};
178     // Streaming to screen the problem
179     std::cout << p0 << '\n';
180     // Getting its dimensions
181     std::cout << "Calling the dimension getter: " << p0.get_nx() << '\n';
182     std::cout << "Calling the fitness dimension getter: " << p0.get_nobj() << '\n';
183 
184     // Getting the bounds via the pagmo::print eating also std containers
185     pagmo::print("Calling the bounds getter: ", p0.get_bounds(), "\n\n");
186 
187     // As soon as a problem its created its function evaluation counter
188     // is set to zero. Checking its value is easy
189     pagmo::print("fevals: ", p0.get_fevals(), "\n");
190     // Computing one fitness
191     pagmo::print("calling fitness in x=[2,2,2,2]: ", p0.fitness({2, 2, 2, 2}), "\n");
192     // The evaluation counter is now ... well ... 1
193     pagmo::print("fevals: ", p0.get_fevals(), "\n\n");
194 
195     // As soon as a problem its created its gradient evaluation counter
196     // is set to zero. Checking its value is easy
197     pagmo::print("gevals: ", p0.get_gevals(), "\n");
198     // Computing one gradient
199     pagmo::print("gradient implementation detected?: ", p0.has_gradient(), '\n');
200     pagmo::print("calling gradient in x=[2,2,2,2]: ", p0.gradient({2, 2, 2, 2}), "\n");
201     // The evaluation counter is now ... well ... 1
202     pagmo::print("gevals: ", p0.get_gevals(), "\n\n");
203 
204     // As soon as a problem its created its hessian evaluation counter
205     // is set to zero. Checking its value is easy
206     pagmo::print("hevals: ", p0.get_hevals(), "\n");
207     // Computing one gradient
208     pagmo::print("hessians implementation detected?: ", p0.has_hessians(), '\n');
209     pagmo::print("calling hessians in x=[2,2,2,2]: ", p0.hessians({2, 2, 2, 2}), "\n");
210     // The evaluation counter is now ... well ... 1
211     pagmo::print("hevals: ", p0.get_hevals(), "\n\n");
212 
213     pagmo::print("Gradient sparsity pattern: ", p0.gradient_sparsity(), "\n");
214     pagmo::print("Hessians sparsity pattern: ", p0.hessians_sparsity(), "\n\n");
215 
216     // While our problem_basic_gh struct is now hidden inside the pagmo::problem
217     // we can still access its methods / data via the extract interface
218     pagmo::print("Accessing best_known: ", p0.extract<problem_basic_gh>()->best_known(), "\n");
219 }
220