├── LICENSE ├── Makefile ├── README.md ├── ceres.cc └── run.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Igor Babuschkin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | ceres: ceres.cc 3 | clang++ -std=c++11 -g -O0 -shared -o ceres.so ceres.cc -I /usr/local/include/eigen3 -I /usr/local/include -I ./pybind11/include/ -lglog -lceres -L /usr/local/lib -L ~/.anaconda/lib $(shell python3-config --libs --cflags) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # python-ceres 3 | 4 | Pythonic bindings to the ceres-solver minimizer. 5 | -------------------------------------------------------------------------------- /ceres.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace pybind11::literals; 8 | namespace py = pybind11; 9 | 10 | class CustomFunction : public ceres::FirstOrderFunction { 11 | public: 12 | CustomFunction(py::function func, 13 | py::function grad, 14 | unsigned long num_params) 15 | : function_(func), 16 | gradient_(grad), 17 | num_params_(num_params) {} 18 | virtual ~CustomFunction() override {} 19 | virtual bool Evaluate(const double* parameters, 20 | double* cost, 21 | double* gradient) const override { 22 | auto arr = py::array(py::buffer_info( 23 | const_cast(parameters), 24 | sizeof(double), 25 | py::format_descriptor::value, 26 | 1, 27 | { num_params_ }, 28 | { sizeof(double) } 29 | )); 30 | 31 | auto out = function_(arr); 32 | 33 | // If the cost evaluation fails, the function should return None. 34 | // Returning false means that Ceres should try other parameters. 35 | if (out == py::none()) { 36 | return false; 37 | } 38 | 39 | cost[0] = out.cast(); 40 | 41 | // If gradient is not null, we are supposed to 42 | // calculate the gradient. 43 | if (gradient) { 44 | auto out = gradient_(arr); 45 | 46 | // We also check the gradient for None. 47 | if (out == py::none()) { 48 | return false; 49 | } 50 | 51 | auto arr = out.cast>(); 52 | auto buf = static_cast(arr.request().ptr); 53 | for (unsigned long i = 0; i < num_params_; i++) { 54 | gradient[i] = buf[i]; 55 | } 56 | } 57 | return true; 58 | } 59 | virtual int NumParameters() const override { 60 | return num_params_; 61 | } 62 | private: 63 | py::function function_; 64 | py::function gradient_; 65 | unsigned long num_params_; 66 | }; 67 | 68 | py::object optimize(py::function func, py::function grad, py::buffer x0) { 69 | auto buf1 = x0.request(); 70 | if (buf1.ndim != 1) { 71 | throw std::runtime_error("Number of dimensions of x0 must be one"); 72 | } 73 | ceres::GradientProblemSolver::Options options; 74 | //options.minimizer_progress_to_stdout = true; 75 | ceres::GradientProblemSolver::Summary summary; 76 | ceres::GradientProblem problem(new CustomFunction(func, grad, buf1.size)); 77 | 78 | auto result = py::array_t(buf1.size); 79 | auto buf2 = result.request(); 80 | 81 | double* data = static_cast(buf2.ptr); 82 | double* inputs = static_cast(buf1.ptr); 83 | 84 | for (unsigned long i = 0; i < buf1.size; i++) { 85 | data[i] = inputs[i]; 86 | } 87 | 88 | ceres::Solve(options, problem, data, &summary); 89 | 90 | auto iterations = summary.iterations; 91 | int nfev = 0; 92 | int ngev = 0; 93 | for (auto& summ: iterations) { 94 | nfev += summ.line_search_function_evaluations; 95 | ngev += summ.line_search_gradient_evaluations; 96 | } 97 | 98 | auto OptimizeResult = py::module::import("scipy.optimize").attr("OptimizeResult"); 99 | 100 | py::dict out("x"_a = result, 101 | "success"_a = summary.termination_type == ceres::CONVERGENCE || 102 | summary.termination_type == ceres::USER_SUCCESS, 103 | "status"_a = (int)summary.termination_type, 104 | "message"_a = summary.message, 105 | "fun"_a = summary.final_cost, 106 | "nfev"_a = nfev, 107 | "ngev"_a = ngev); 108 | 109 | return OptimizeResult(out); 110 | } 111 | 112 | namespace py = pybind11; 113 | 114 | PYBIND11_PLUGIN(ceres) { 115 | py::module m("ceres", "Python bindings to the Ceres-Solver minimizer."); 116 | google::InitGoogleLogging("ceres"); 117 | 118 | m.def("optimize", &optimize, "Optimizes the function"); 119 | 120 | return m.ptr(); 121 | } 122 | 123 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | 2 | from ceres import optimize 3 | import numpy as np 4 | 5 | def func(ps): 6 | return np.sum(ps**2) 7 | 8 | def grad(ps): 9 | return 2 * ps 10 | 11 | x0 = np.array([1,2,3,4,5,6,7,8,9,10], dtype=np.double) 12 | 13 | print(optimize(func, grad, x0)) 14 | --------------------------------------------------------------------------------