Skip to content

Commit 0f82ad4

Browse files
author
Benjamin Chrétien
committed
Add CachedFunction support.
For now, only differentiable functions are handled.
1 parent 5324a03 commit 0f82ad4

6 files changed

Lines changed: 264 additions & 1 deletion

File tree

src/roboptim/core/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ def impl_jacobian (self, result, x):
180180
jacobian (self._fd, result, x)
181181

182182

183+
class PyCachedFunction(PyDifferentiableFunction):
184+
def __init__ (self, f, size):
185+
PyDifferentiableFunction.__init__ \
186+
(self, f.inputSize (), f.outputSize (), \
187+
self._decodeName (f.name ()))
188+
self._cachedFunction = CachedFunction (f._function, size)
189+
190+
def impl_compute (self, result, x):
191+
compute (self._cachedFunction, result, x)
192+
193+
def impl_gradient (self, result, x, functionId):
194+
gradient (self._cachedFunction, result, x, functionId)
195+
196+
def impl_jacobian (self, result, x):
197+
jacobian (self._cachedFunction, result, x)
198+
199+
183200
class PyProblem(object):
184201
def __init__(self, cost):
185202
self.cost = cost

src/wrap.cc

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,44 @@ namespace roboptim
437437
}
438438

439439

440+
CachedFunction::CachedFunction (boost::shared_ptr<pyFunction_t> f, size_t cache_size)
441+
: function_t (f->inputSize (), f->outputSize (), f->getName ()),
442+
pyFunction_t (f->inputSize (), f->outputSize (), f->getName ()),
443+
cached_f_ (boost::make_shared<cache_t> (f, cache_size))
444+
{
445+
}
446+
447+
CachedFunction::~CachedFunction ()
448+
{
449+
}
450+
451+
void CachedFunction::impl_compute (result_ref result,
452+
const_argument_ref argument)
453+
const
454+
{
455+
(*cached_f_) (result, argument);
456+
}
457+
458+
void CachedFunction::impl_gradient (gradient_ref gradient,
459+
const_argument_ref argument,
460+
size_type functionId)
461+
const
462+
{
463+
cached_f_->gradient (gradient, argument, functionId);
464+
}
465+
466+
void CachedFunction::impl_jacobian (jacobian_ref jacobian,
467+
const_argument_ref argument)
468+
const
469+
{
470+
cached_f_->jacobian (jacobian, argument);
471+
}
472+
473+
std::ostream& CachedFunction::print (std::ostream& o) const
474+
{
475+
return cached_f_->print (o);
476+
}
477+
440478
FunctionPool::~FunctionPool ()
441479
{
442480
}
@@ -472,6 +510,7 @@ using roboptim::core::python::DifferentiableFunction;
472510
using roboptim::core::python::TwiceDifferentiableFunction;
473511
using roboptim::core::python::FiniteDifferenceGradient;
474512
using roboptim::core::python::FunctionPool;
513+
using roboptim::core::python::CachedFunction;
475514

476515
namespace detail
477516
{
@@ -1038,6 +1077,50 @@ createFDWrapper (PyObject*, PyObject* args)
10381077
return fdFunctionPy;
10391078
}
10401079

1080+
static PyObject*
1081+
createCachedFunction (PyObject*, PyObject* args)
1082+
{
1083+
typedef CachedFunction cachedDifferentiableFunction_t;
1084+
1085+
Function* function = 0;
1086+
size_t cache_size = 10;
1087+
1088+
if (!PyArg_ParseTuple(args, "O&|i", &detail::functionConverter, &function, &cache_size))
1089+
return 0;
1090+
1091+
if (!function)
1092+
{
1093+
PyErr_SetString
1094+
(PyExc_TypeError,
1095+
"Failed to retrieve function object");
1096+
return 0;
1097+
}
1098+
1099+
PyObject* cachedFunctionPy = 0;
1100+
1101+
DifferentiableFunction* dfunction =
1102+
dynamic_cast<DifferentiableFunction*> (function);
1103+
if (!dfunction)
1104+
{
1105+
PyErr_SetString
1106+
(PyExc_TypeError,
1107+
"Failed to retrieve differentiable function object");
1108+
return 0;
1109+
}
1110+
1111+
boost::shared_ptr<DifferentiableFunction> dfunction_ptr
1112+
= detail::to_shared_ptr<DifferentiableFunction> (dfunction, PyTuple_GetItem (args, 0));
1113+
assert (dfunction_ptr);
1114+
1115+
cachedDifferentiableFunction_t* cachedFunction
1116+
= new cachedDifferentiableFunction_t (dfunction_ptr, cache_size);
1117+
1118+
cachedFunctionPy = PyCapsule_New (cachedFunction, ROBOPTIM_CORE_FUNCTION_CAPSULE_NAME,
1119+
&detail::destructor<cachedDifferentiableFunction_t>);
1120+
1121+
return cachedFunctionPy;
1122+
}
1123+
10411124
static PyObject*
10421125
inputSize (PyObject*, PyObject* args)
10431126
{
@@ -2872,7 +2955,11 @@ static PyMethodDef RobOptimCoreMethods[] =
28722955
METH_VARARGS, "Create a FiniteDifferenceGradient with forward difference."},
28732956
{"FivePointsFiniteDifferenceGradient",
28742957
createFDWrapper<FiniteDifferenceGradient<fivePointsPolicy_t> >,
2875-
METH_VARARGS, "Create a FiniteDifferenceGradient with the 5-points rule."},
2958+
METH_VARARGS, "Create a FiniteDifferenceGradient with the 5-point rule."},
2959+
2960+
// Filters
2961+
{"CachedFunction", createCachedFunction, METH_VARARGS,
2962+
"Create a cached function."},
28762963

28772964
// Print functions
28782965
{"strFunction", print<Function>, METH_VARARGS,

src/wrap.hh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
#include <boost/variant.hpp>
77
#include <boost/mpl/vector.hpp>
8+
#include <boost/utility/enable_if.hpp>
9+
#include <boost/type_traits/is_same.hpp>
10+
#include <boost/type_traits/is_base_of.hpp>
11+
#include <boost/make_shared.hpp>
812

913
#include <Python.h>
1014

@@ -25,6 +29,8 @@
2529

2630
#include <roboptim/core/callback/multiplexer.hh>
2731

32+
#include <roboptim/core/filter/cached-function.hh>
33+
2834
#include <roboptim/core/detail/utility.hh>
2935

3036

@@ -203,6 +209,41 @@ namespace roboptim
203209
}
204210
};
205211

212+
class CachedFunction
213+
: virtual public ::roboptim::DifferentiableFunction,
214+
public ::roboptim::core::python::DifferentiableFunction
215+
{
216+
public:
217+
typedef ::roboptim::DifferentiableFunction function_t;
218+
typedef DifferentiableFunction pyFunction_t;
219+
220+
typedef ::roboptim::CachedFunction<function_t> cache_t;
221+
222+
FORWARD_TYPEDEFS_ (cache_t);
223+
224+
explicit CachedFunction (boost::shared_ptr<pyFunction_t> f, size_t cache_size);
225+
226+
virtual ~CachedFunction ();
227+
228+
virtual void impl_compute (result_ref result,
229+
const_argument_ref argument)
230+
const;
231+
232+
virtual void impl_gradient (gradient_ref gradient,
233+
const_argument_ref argument,
234+
size_type functionId)
235+
const;
236+
237+
virtual void impl_jacobian (jacobian_ref jacobian,
238+
const_argument_ref argument)
239+
const;
240+
241+
virtual std::ostream& print (std::ostream& o) const;
242+
243+
private:
244+
boost::shared_ptr<cache_t> cached_f_;
245+
};
246+
206247

207248
template <typename S>
208249
class SolverCallback

tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ ADD_TEST(solver_callback
2222
ADD_TEST(finite_differences
2323
"${TEST_COMMAND}" "${CMAKE_CURRENT_SOURCE_DIR}/finite_differences.py")
2424

25+
# Check cached function support.
26+
ADD_TEST(cached_function
27+
"${TEST_COMMAND}" "${CMAKE_CURRENT_SOURCE_DIR}/cached_function.py")
28+
2529
# Check function pool support.
2630
ADD_TEST(function_pool
2731
"${TEST_COMMAND}" "${CMAKE_CURRENT_SOURCE_DIR}/function_pool.py")

tests/cached_function.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
from __future__ import \
4+
print_function, unicode_literals, absolute_import, division
5+
6+
import unittest
7+
import roboptim.core
8+
import numpy, numpy.testing
9+
import math
10+
11+
class Square (roboptim.core.PyDifferentiableFunction):
12+
def __init__ (self):
13+
roboptim.core.PyDifferentiableFunction.__init__ \
14+
(self, 1, 1, "square function")
15+
self.compute_counter = 0
16+
self.gradient_counter = 0
17+
self.jacobian_counter = 0
18+
19+
def reset (self):
20+
self.compute_counter = 0
21+
self.gradient_counter = 0
22+
self.jacobian_counter = 0
23+
24+
def impl_compute (self, result, x):
25+
result[0] = x[0] * x[0]
26+
self.compute_counter += 1
27+
28+
def impl_gradient (self, result, x, f_id):
29+
result[0] = 2. * x[0]
30+
self.gradient_counter += 1
31+
32+
def impl_jacobian (self, result, x):
33+
result[0,0] = 2. * x[0]
34+
self.jacobian_counter += 1
35+
36+
class TestFiniteDifferences(unittest.TestCase):
37+
38+
def test_counters(self):
39+
40+
square = Square()
41+
f = roboptim.core.PyCachedFunction (square, 10)
42+
43+
x = numpy.array ([4.])
44+
res = f (x)
45+
numpy.testing.assert_almost_equal (res, [x[0] * x[0]], 5)
46+
assert square.compute_counter == 1
47+
res2 = f (x)
48+
numpy.testing.assert_almost_equal (res2, [x[0] * x[0]], 5)
49+
assert square.compute_counter == 1
50+
51+
grad = f.gradient (x, 0)
52+
numpy.testing.assert_almost_equal (grad, [2. * x[0]], 5)
53+
assert square.gradient_counter == 1
54+
grad2 = f.gradient (x, 0)
55+
numpy.testing.assert_almost_equal (grad2, [2. * x[0]], 5)
56+
assert square.gradient_counter == 1
57+
58+
jac = f.jacobian (x)
59+
numpy.testing.assert_almost_equal (jac, [[2. * x[0]]], 5)
60+
assert square.jacobian_counter == 1
61+
jac2 = f.jacobian (x)
62+
numpy.testing.assert_almost_equal (jac2, [[2. * x[0]]], 5)
63+
assert square.jacobian_counter == 1
64+
65+
66+
if __name__ == '__main__':
67+
unittest.main()

tests/wrap.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,53 @@ def gradient(result, x, functionId):
178178
roboptim.core.gradient (f, result, x, 0)
179179
numpy.testing.assert_almost_equal (result, result_fd, 4)
180180

181+
def test_cached_function(self):
182+
183+
def compute(result, x):
184+
print("compute()")
185+
result[0] = x[0] * x[0]
186+
def gradient(result, x, functionId):
187+
print("gradient()")
188+
result[0] = 2 * x[0]
189+
190+
f = roboptim.core.DifferentiableFunction (1, 1, "x * x")
191+
roboptim.core.bindCompute(f, compute)
192+
roboptim.core.bindGradient(f, gradient)
193+
194+
# Cached function
195+
cached = roboptim.core.CachedFunction (f, 10)
196+
197+
# Check computation with sequences.
198+
x = [2.,]
199+
result = numpy.array([0.,])
200+
result2 = numpy.array([0.,])
201+
gradient = numpy.array([0.,])
202+
gradient2 = numpy.array([0.,])
203+
204+
roboptim.core.compute (cached, result, x)
205+
roboptim.core.compute (cached, result2, x)
206+
roboptim.core.gradient (cached, gradient, x, 0)
207+
roboptim.core.gradient (cached, gradient2, x, 0)
208+
209+
self.assertEqual (x, [2.,])
210+
self.assertEqual (result, [4.,])
211+
self.assertEqual (result2, [4.,])
212+
self.assertEqual (gradient, [4.,])
213+
self.assertEqual (gradient2, [4.,])
214+
215+
x = [10.,]
216+
roboptim.core.compute (cached, result, x)
217+
roboptim.core.compute (cached, result2, x)
218+
roboptim.core.gradient (cached, gradient, x, 0)
219+
roboptim.core.gradient (cached, gradient2, x, 0)
220+
221+
self.assertEqual (x, [10.,])
222+
self.assertEqual (result, [100.,])
223+
self.assertEqual (result2, [100.,])
224+
self.assertEqual (gradient, [20.,])
225+
self.assertEqual (gradient2, [20.,])
226+
227+
181228
def test_function_pool(self):
182229
data = numpy.zeros(2)
183230

0 commit comments

Comments
 (0)