Search code examples
c++nonlinear-optimizationceres-solver

How to create different solver blocks for the same solution in ceres?


I want to use ceres to calculate triangle coordinates.

For the problem I need to solve for mesh coordinates in a mesh. Each triangle has its own vertices, but structure like triangles (3 vertices) and edges (4 vertices) is available.

Example data (pseudo code):

triangles = [[v1, v2, v3], [v4, v5, v6]]
inner_edges = [[[v1, v4], [v2, v5]]]

The edges [v1, v2] and [v4, v5] are initially the same, which may change during solving.

Now I have two cost functions, one on triangles and one on inner edges

f([v1, v2, v3]) = res_t1
g([v1, v4, v2, v5]) = res_2

There are two simple block structures

  • Two blocks, one with all triangle residuums, one with all edge residdums.
  • One block per triangle and one block per edges.

The first one solves for a vector x with all coordinates (2*|V| as each vertex has two coordinates), as the blocks depend on all vertices. In the second one, the triangle blocks should only depend on three vertices and the edge blocks on four vertices. I want now to use the second one, as I expect better performance and better convergence.

How do I setup ceres to solve for the same coordinates, but only considering a subset of the vertices as relevant for the current residuum?

I tried setting up the problem with size 6 and 8 and a pointer at the right place in x, but ceres does not allow to use the same result pointer with different offset.

Next I tried to use SubsetParameterization for example like this

vector<double> x(mesh.n_faces()*6);
for(int i=0; i < mesh.n_faces(); i++){
    vector<int> const_params;
    for(int j = 0; j < mesh.n_faces(); j++) {
        if(i != j) {
            const_params.push_back(6*j);
            const_params.push_back(6*j+1);
            const_params.push_back(6*j+2);
            const_params.push_back(6*j+3);
            const_params.push_back(6*j+4);
            const_params.push_back(6*j+5);
        }
    }
    //auto *ssp = new ceres::SubsetParameterization(6, const_params); // (1)
    auto *ssp = new ceres::SubsetParameterization(mesh.n_faces() * 6, const_params); // (2)
    problem.AddParameterBlock(x.data(), mesh.n_faces() * 6, ssp);
    problem.AddResidualBlock(face_cost_function, NULL, x.data());
}

But the ceres checks tell me that both variants are wrong.

for (1) I get

local_parameterization.cc:98 Check failed: constant.back() < size Indices indicating constant parameter must be less than the size of the parameter block.

and for (2) I get

problem_impl.cc:135 Check failed: size == existing_size Tried adding a parameter block with the same double pointer, 000002D736397260, twice, but with different block sizes. Original size was 1152 but new size is 6

How do I set ceres up, such that I can split the same problem in overlapping blocks, which only affect a few of the result variables?


Solution

  • I got it. You are allowed to use multiple pointers into the same array, you are just not allowed to have different block sizes for the same pointer. This means your blocks inside the array may not overlap inside the array, but different cost functions are allowed to use the same blocks.

    The solution is to use one block per coordinate pair:

    for(int i = 0; i < mesh.n_faces(); i++) {
            face_cost_functors.push_back(new FaceFunctor());
            ceres::DynamicAutoDiffCostFunctionFaceFunctor> *face_cost_function = new ceres::DynamicAutoDiffCostFunction<FaceFunctor>(face_cost_functors.back());
            face_cost_function->SetNumResiduals(1);
            face_cost_function->AddParameterBlock(2);
            face_cost_function->AddParameterBlock(2);
            face_cost_function->AddParameterBlock(2);
            problem.AddResidualBlock(face_cost_function, NULL, &x.data()[6*i], &x.data()[6*i+2], &x.data()[6*i+4]);
        }
    

    Then you can add more cost functions, as long as they use the same blocks (i.e. starting address and block size are the same). I do not use any SubsetParametrization at all here.

    It did not work for before, because I tried to use a block of size 6 for the triangles and 4 blocks of size 2 for the edge pairs, which overlap the blocks of size 6.

    Now it runs a lot faster than before and converges without problems.