Lesson C: Static range-and-bearing localization
In this lessson, we still focus on the perception of landmarks clearly identified: each observation is related to a known position. The problem is similar to the localization of the previous lesson, but now we also consider the bearing, i.e. the angular position of the rock with respect to the heading of the robot.
We will see how to handle this constraint. From this example, we will understand the principles of decomposition of constraints and the polar equations.
Formalism
For the moment, the map is made of one landmark. The localization problem corresponds to the following observation function:
where \(\mathbf{x}\) is the unknown state vector: the robot is located at \(\left(x_{1},x_{2}\right)^\intercal\) with a heading \(x_{3}\). The output measurement vector is \(\mathbf{y}\), made of the range \(y_{1}\) and the bearing \(y_{2}\), as illustrated by the figure. It is assumed that the measurement is bounded: \(\mathbf{y}\in\left[\mathbf{y}\right]\), contrary to \(\mathbf{x}\) that is completely unknown: \(\mathbf{x}\in[\mathbf{x}]=[-\infty,\infty]^2\). We say that \([\mathbf{x}]\) is an unbounded set.
Often, the observation equation also appears in the following implicit form:
In our range-and-bearing case, the observation equation is then defined as:
\[g(\mathbf{x},y)=\left(x_{1}-m_1\right)^{2}+\left(x_{2}-m_2\right)^{2}-y^{2}.\]
In a set-membership approach, the uncertainties related to the state \(\mathbf{x}\), the measurement \(\mathbf{y}\) and the landmark \(\mathbf{m}\) will be implicitly represented by interval-vectors (boxes): \(\mathbf{x}\in[\mathbf{x}]\), \(\mathbf{y}\in[\mathbf{y}]\) and \(\mathbf{m}\in[\mathbf{m}]\).
We will solve this problem using the constraint programming approach.
Decomposition of constraints
Problems involving complex equations can be broken down into a set of primitive constraints. Here, primitive means that the constraints cannot be decomposed anymore. For instance, in a range-only localization problem (see Lessons A and B), the observation constraint \(\mathcal{L}_{\textrm{dist}}\) can be broken down into:
where \(a,b,\dots,e\) are intermediate variables used for the decomposition. This constitutes a network made of the \(\mathcal{L}_{-}\), \(\mathcal{L}_{+}\), \(\mathcal{L}_{(\cdot)^2}\), and \(\mathcal{L}_{\sqrt{\cdot}}\) elementary constraints.
ctc_dist = CtcFunction(Function("x[2]", "b[2]", "y",
"sqrt((x[0]-b[0])^2+(x[1]-b[1]))-y"))
CtcFunction ctc_dist(Function("x[2]", "b[2]", "y",
"sqrt((x[0]-b[0])^2+(x[1]-b[1]))-y"));
Optimality of contractors
Depending on the properties of the equation, the resulting contractor can be optimal. It means that the contracted box will perfectly enclose the set of feasible solutions without pessimism. This enables an efficient resolution.
In other cases, for instance because of dependencies between the variables, the resulting operator may not be optimal. For instance, looking at the equation depicting the above problem of range-and-bearing localization, the formula
could be implemented by
ctc_g = CtcFunction(Function("x[3]", "y[2]", "m[2]",
"(x[0]+y[0]*cos(x[2]+y[1])-m[0] ; x[1]+y[0]*sin(x[2]+y[1])-m[1])"))
CtcFunction ctc_g(Function("x[3]", "y[2]", "m[2]",
"(x[0]+y[0]*cos(x[2]+y[1])-m[0] ; x[1]+y[0]*sin(x[2]+y[1])-m[1])"));
However, this involves a multi-occurrence of variables which leads to pessimism. For instance, the sum \((x_3+y_2)\) appears twice in functions \(\cos\) and \(\sin\), which is hardly handled by a classical decomposition.
We may instead use a dedicated contractor that deals with the polar constraint, appearing in the above function \(\mathbf{g}\). This will allow us to avoid multi-occurrences and then, pessimism.
The polar constraint
The range-and-bearing problem is usually related to the polar constraint that links Cartesian coordinates to polar ones. This constraint is often encountered in robotics and it is important to be able to deal with it in an optimal way.
This simple and elementary constraint is expressed by:
CtcPolar
.Technical documentation
The CtcPolar page of the user manual provides more details about the contractor CtcPolar
and the related publication.
This constraint appears in the expression of \(\mathbf{g}\).
Exercise
C.1. On a sheet of paper, write a decomposition of function \(\mathbf{g}\) that involves \(\mathcal{L}_\textrm{polar}\), other constraints and intermediate variables.
Initialization
A robot depicted by the state \(\mathbf{x}=\left(2,1,\pi/6\right)^\intercal\) is perceiving a landmark \(\mathbf{m}=\left(5,6.2\right)^\intercal\) at a range \(y_1=6\) and a bearing \(y_2=\pi/6\). We assume that the position of the robot is not known and that the uncertainties related to the measurement and the landmark are:
Measurement. For \(y_1\): \([-0.3,0.3]\), for \(y_2\): \([-0.1,0.1]\).
Landmark. For each component of the 2d dimension: \([-0.2,0.2]\).
Exercise
C.2. Create a new project (Python or C++) and create the vectors x_truth
, y_truth
, m_truth
representing the actual but unknown values of \(\mathbf{x}=\left(2,1,\pi/6\right)^\intercal\), \(\mathbf{y}=\left(6,\pi/6\right)^\intercal\) and \(\mathbf{m}=\left(5,6.2\right)^\intercal\).
# We recall that in Python, a mathematical vector (not an IntervalVector)
# can be represented with native objects, for instance:
x_truth = (2,1,math.pi/6) # actual state vector (pose)
// We recall that in C++, we can use the Vector class for
// representing mathematical vectors. For instance:
Vector x_truth({2.,1.,M_PI/6}); // actual state vector (pose)
C.3. Create the bounded sets related to the state, the measurement and the landmark position: \([\mathbf{x}]\in\mathbb{IR}^3\), \([\mathbf{y}]\in\mathbb{IR}^2\), \([\mathbf{m}]\in\mathbb{IR}^2\). We can for instance use the .inflate(float radius)
method on intervals or boxes. The heading of the robot is assumed precisely known (for instance thanks to a compass); the actual heading \(x_3\) is represented by x_truth[2]
.
C.4. Display the vehicle and the landmark with:
beginDrawing()
fig_map = VIBesFigMap("Map")
fig_map.set_properties(100,100,500,500)
fig_map.axis_limits(0,7,0,7)
fig_map.draw_vehicle(x_truth, size=1)
fig_map.draw_box(m, "red")
fig_map.draw_box(x.subvector(0,1)) # does not display anything if unbounded
fig_map.show()
endDrawing()
vibes::beginDrawing();
VIBesFigMap fig_map("Map");
fig_map.set_properties(100,100,500,500);
fig_map.axis_limits(0,7,0,7);
fig_map.draw_vehicle(x_truth, 1);
fig_map.draw_box(m, "red");
fig_map.draw_box(x.subvector(0,1)); // does not display anything if unbounded
fig_map.show();
vibes::endDrawing();
C.5. Display the range-and-bearing measurement with its uncertainties. For this, we will use the fig_map.draw_pie(x,y,interval_rho,interval_theta)
to display a portion of a ring \([\rho]\times[\theta]\) centered on \((x,y)^\intercal\). Here, we add in \([\theta]\) the robot heading \(x_3\) and the bounded bearing \([y_2]\).
You should obtain this figure:
As one can see, intervals are not limited to axis-aligned boxes: we sometimes perform rotational mapping to better fit the set to represent. This polar constraint is a case in point.
State estimation
We will implement the decomposition of Question C.1 using the ContractorNetwork
, that will manage the constraint propagation for us.
Exercise
The following must be made before the graphical part.
C.6. Create the contractors related to the decomposition of Question C.1.
- The contractor \(\mathcal{C}_\textrm{polar}\) is given by the class
CtcPolar
. Note that you do not need to create an object of this class, since one is already instantiated in the library (it is namedctc.polar
in Python,ctc::polar
in C++). The other contractors can be built with several
CtcFunction
objects, as we did in the previous Lessons A and B. We recall that these constraints have to be expressed in the form \(f(\mathbf{x})=0\). See more.
ContractorNetwork
(CN) to solve the problem by adding the contractors and the domains from the decomposition.cn = ContractorNetwork()
cn.add(<ctc_object>, [<dom1>,<dom2>,...])
ContractorNetwork cn;
cn.add(<ctc_object>, {<dom1>,<dom2>,...});
Interval
and IntervalVector
objects, as for the other variables.C.9. Use cn.contract()
to solve the problem. You should obtain this figure:
The black box \([\mathbf{x}]\) cumulates all the uncertainties of the problem:
uncertainties of the measurement
uncertainties of the position of the landmark
If we remove the uncertainties related to the measurement \([\mathbf{y}]\), then the width of \([\mathbf{x}]\) should be exactly the same as the one of \([\mathbf{m}]\), because we used optimal contractors. The width of a box is given by the .diam()
method.