cvg / pyceres

Factor graphs with Ceres in Python
Apache License 2.0
247 stars 34 forks source link

Writing cost function in Python #22

Closed nnop closed 11 months ago

nnop commented 1 year ago

Thanks for this awesome wrapping library for ceres. In https://github.com/cvg/pyceres/issues/20, you said:

We don't provide any way to write cost functions in Python.

However, I have noticed that PyCostFunction already provides the corresponding trampoline functionality. The only thing that needs to be added is the encapsulation of set_num_residuals(). Which is done as follows:

      .def("set_num_residuals", &PyCostFunction::set_num_residuals)

After adding it, I tested the helloworld CostFunction in Python. It seems ok.

class HelloworldCost(pyceres.CostFunction):
    def __init__(self):
        pyceres.CostFunction.__init__(self)
        self.set_num_residuals(1)
        self.set_parameter_block_sizes([1])

    def Evaluate(self, parameters, residuals, jacobians):
        # ref: examples/helloworld_analytic_diff.cc
        x = parameters[0][0]
        residuals[0] = 10. - x
        if jacobians is not None:
            jacobians[0][0] = -1.
        return True

def test_python_cost():
    x = np.array(5.0)
    prob = pyceres.Problem()
    cost = HelloworldCost()
    loss = pyceres.TrivialLoss()
    prob.add_residual_block(cost, loss, [x])

    opts = pyceres.SolverOptions()
    opts.minimizer_progress_to_stdout = True
    summary = pyceres.SolverSummary()
    pyceres.solve(opts, prob, summary)
    print(summary.BriefReport())
    print(x)

The output is:

iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  1.250000e+01    0.00e+00    5.00e+00   0.00e+00   0.00e+00  1.00e+04        0    4.41e-05    1.08e-04
   1  1.249750e-07    1.25e+01    5.00e-04   5.00e+00   1.00e+00  3.00e+04        1    6.51e-05    2.06e-04
   2  1.388518e-16    1.25e-07    1.67e-08   5.00e-04   1.00e+00  9.00e+04        1    2.88e-05    2.45e-04
Ceres Solver Report: Iterations: 3, Initial cost: 1.250000e+01, Final cost: 1.388518e-16, Termination: CONVERGENCE
9.999999983335556

I am not sure if this approach has any issues or if it can be scaled to larger problems. Looking forward to your comments.

sarlinpe commented 1 year ago

We actually haven't tried this out - very nice! This will indeed be much slower than C++ cost functions for large problems because the GIL prevents evaluating costs in parallel, but this is nonetheless very useful for debugging! Would you mind submitting a PR with set_num_residuals and a simple test script? Thank you.

nnop commented 1 year ago

Sure, submited.