Hi Daniel,
Hope you'll have a nice time exploring the tool..
The syntax you posted is all about putting all variables, all constraints (g), all bounds, .. etc in one tall vector.
You build up a cell of expressions, which we vertcat in the end, to obtain a tall symbolic vector.
(In fact you may do the same for lbg, ubg, its a matter of style).
To add constraints, simply add lines inside the loop. E.g.:
% Add path constraint on second state
g = {g{:}, Xk(2)};
lbg = [lbg; lbg2];
ubg = [ubg; ubg2];
Alternatively you could say:
% Add path constraint on second state
lbg = [lbg; -inf; lbg2];
ubg = [ubg; inf;ubg2];
Now you added both states in the constraints, but ignore the first one by choosing inf.
The terminal state (if you're looking at direct_single_shooting.m) is in fact not enforced; the solver just decided it was good to end close to 0.
You may add a terminal constraint by a simple bound (lbw/lbu), or a full constraint.
E.g. you could just add this after the loop:
lbg = [lbg; state_final];
ubg = [ubg; state_final];
From CasADi's point of view, there's no problem to enforce different consistent constraints on one node. Of course, you may create an LICQ violation by doing this, and not all solvers may handle such violation.
Best regards,
Joris