# CLPR fails for 3d change of basis (precision / epsilon error ?)

I’m using: SWI-Prolog version 9.0.4

I want the code to:
Implementation of a 3d change of basis.
This is the formula :

``````a_M_c = a_M_b * b_M_c
``````

Where the matrices are 4x4 homogeneous matrices where the first 3x3 is the rotation, the last column is translation.

I want to implement it in a CLP way.
Very basically, I want to write it in a way that one of the 3 matrices can be computed if the other 2 are given.

I don’t only represent this as matrices, but more as “3d transform” having 3 translations and 3 rotation angles.

``````[TransX, TransY, TransZ, Alpha, Beta, Gamma]
``````

For the rotation convention, I use the convention of this formula :

( the second formula, which represents an extrinsic rotation whose (improper) Euler angles are α, β, γ, about axes x, y, z.).
So that i represent the 3x3 rotation only with 3 angles (alpha, beta, gamma).

So my change of basis can also be written as :

``````TransformProduct = TransLeft * TransRight
``````

But what I’m getting is:
My implementation works when some values are given. But not in all case.
When TransLeft and TransRight are given, this works : I get TransformProduct.
When TransLeft and TransformProduct are given, this works : I get TransRight.
When TransLeft,TransRight, TransformProduct are given, this works : I get True (when it holds, otherwise False).

BUT, suprinsingly, this does NOT work when TransRight and TransformProduct are given, it does not compute TransLeft . I get false instead.

I tought this was a problem of precision / epsilon.
So I changed the constraints to introduce a Epsilon constraint.
And yes, it changed something, but still, I cannot “mimize(Eps)”, otherwise it will return False.

My code looks like this:

``````
:- use_module(library(clpr)).

compose_transform_RzRyRx_GammaBetaAlpha_fulldebug(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
[[RxxLeft, RxyLeft, RxzLeft, TransXLeft], [RyxLeft, RyyLeft, RyzLeft, TransYLeft], [RzxLeft, RzyLeft, RzzLeft, TransZLeft], [0.0, 0.0, 0.0, 1.0]],
[[RxxRight, RxyRight, RxzRight, TransXRight], [RyxRight, RyyRight, RyzRight, TransYRight], [RzxRight, RzyRight, RzzRight, TransZRight], [0.0, 0.0, 0.0, 1.0]],
[[RxxProd, RxyProd, RxzProd, TransXProd], [RyxProd, RyyProd, RyzProd, TransYProd], [RzxProd, RzyProd, RzzProd, TransZProd], [0.0, 0.0, 0.0, 1.0]],
Eps):-
{
RxxLeft = cos(BetaLeft) * cos(GammaLeft),
RxyLeft = sin(AlphaLeft) * sin(BetaLeft) * cos(GammaLeft) - cos(AlphaLeft) * sin(GammaLeft),
RxzLeft = cos(AlphaLeft) * sin(BetaLeft) * cos(GammaLeft) + sin(AlphaLeft) * sin(GammaLeft),

RyxLeft = cos(BetaLeft) * sin(GammaLeft),
RyyLeft = sin(AlphaLeft) * sin(BetaLeft) * sin(GammaLeft) + cos(AlphaLeft) * cos(GammaLeft),
RyzLeft = cos(AlphaLeft) * sin(BetaLeft) * sin(GammaLeft) - sin(AlphaLeft) * cos(GammaLeft),

RzxLeft = -sin(BetaLeft),
RzyLeft = sin(AlphaLeft) * cos(BetaLeft),
RzzLeft = cos(AlphaLeft) * cos(BetaLeft),

RxxRight = cos(BetaRight) * cos(GammaRight),
RxyRight = sin(AlphaRight) * sin(BetaRight) * cos(GammaRight) - cos(AlphaRight) * sin(GammaRight),
RxzRight = cos(AlphaRight) * sin(BetaRight) * cos(GammaRight) + sin(AlphaRight) * sin(GammaRight),

RyxRight = cos(BetaRight) * sin(GammaRight),
RyyRight = sin(AlphaRight) * sin(BetaRight) * sin(GammaRight) + cos(AlphaRight) * cos(GammaRight),
RyzRight = cos(AlphaRight) * sin(BetaRight) * sin(GammaRight) - sin(AlphaRight) * cos(GammaRight),

RzxRight = -sin(BetaRight),
RzyRight = sin(AlphaRight) * cos(BetaRight),
RzzRight = cos(AlphaRight) * cos(BetaRight),

RxxProd = RxxLeft * RxxRight + RxyLeft * RyxRight + RxzLeft * RzxRight,
RxyProd = RxxLeft * RxyRight + RxyLeft * RyyRight + RxzLeft * RzyRight,
RxzProd = RxxLeft * RxzRight + RxyLeft * RyzRight + RxzLeft * RzzRight,
TransXProd = RxxLeft * TransXRight + RxyLeft * TransYRight + RxzLeft * TransZRight + TransXLeft,

RyxProd = RyxLeft * RxxRight + RyyLeft * RyxRight + RyzLeft * RzxRight,
RyyProd = RyxLeft * RxyRight + RyyLeft * RyyRight + RyzLeft * RzyRight,
RyzProd = RyxLeft * RxzRight + RyyLeft * RyzRight + RyzLeft * RzzRight,
TransYProd = RyxLeft * TransXRight + RyyLeft * TransYRight + RyzLeft * TransZRight + TransYLeft,

RzxProd = RzxLeft * RxxRight + RzyLeft * RyxRight + RzzLeft * RzxRight,
RzyProd = RzxLeft * RxyRight + RzyLeft * RyyRight + RzzLeft * RzyRight,
RzzProd = RzxLeft * RxzRight + RzyLeft * RyzRight + RzzLeft * RzzRight,
TransZProd = RzxLeft * TransXRight + RzyLeft * TransYRight + RzzLeft * TransZRight + TransZLeft,

Eps >= 0.0,

%We are supposed to have RxxProd = cos(BetaProd) * cos(GammaProd) but I suspect that it sometimes fails because of rounding errors
%Same remark for other values.
RxxProdDiff = RxxProd - cos(BetaProd) * cos(GammaProd),
RxxProdDiff =< Eps,
RxxProdDiff >= - Eps,

RxyProdDiff = RxyProd - (sin(AlphaProd) * sin(BetaProd) * cos(GammaProd) - cos(AlphaProd) * sin(GammaProd)),
RxyProdDiff =< Eps,
RxyProdDiff >= - Eps,

RxzProdDiff = RxzProd - (cos(AlphaProd) * sin(BetaProd) * cos(GammaProd) + sin(AlphaProd) * sin(GammaProd)),
RxzProdDiff =< Eps,
RxzProdDiff >= - Eps,

RyxProdDiff = RyxProd - cos(BetaProd) * sin(GammaProd),
RyxProdDiff =< Eps,
RyxProdDiff >= - Eps,

RyyProdDiff = RyyProd - (sin(AlphaProd) * sin(BetaProd) * sin(GammaProd) + cos(AlphaProd) * cos(GammaProd)),
RyyProdDiff =< Eps,
RyyProdDiff >= - Eps,

RyzProdDiff = RyzProd - (cos(AlphaProd) * sin(BetaProd) * sin(GammaProd) - sin(AlphaProd) * cos(GammaProd)),
RyzProdDiff =< Eps,
RyzProdDiff >= - Eps,

RzxProdDiff = RzxProd + sin(BetaProd), % = RzxProd - (- sin(BetaProd))
RzxProdDiff =< Eps,
RzxProdDiff >= - Eps,

RzyProdDiff = RzyProd - sin(AlphaProd) * cos(BetaProd),
RzyProdDiff =< Eps,
RzyProdDiff >= - Eps,

RzzProdDiff = RzzProd - cos(AlphaProd) * cos(BetaProd),
RzzProdDiff =< Eps,
RzzProdDiff >= - Eps
}.

compose_transform_RzRyRx_GammaBetaAlpha(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
Eps):-

compose_transform_RzRyRx_GammaBetaAlpha_fulldebug(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
_, _, _, Eps).

test_compose_transform_RzRyRx_GammaBetaAlpha_1(TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd, Eps):-
compose_transform_RzRyRx_GammaBetaAlpha(10.0, -20.0, 30.0, 0.5, -0.35, 0.25,
-4.5, 23.5, -17.5, -0.45, 0.65, -0.15,
TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
Eps).

test_compose_transform_RzRyRx_GammaBetaAlpha_2(TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd, Mleft, Mright, Mprod, Eps):-
compose_transform_RzRyRx_GammaBetaAlpha_fulldebug(10.0, -20.0, 30.0, 0.5, -0.35, 0.25,
-4.5, 23.5, -17.5, -0.45, 0.65, -0.15,
TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
Mleft, Mright, Mprod,
Eps).

test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, Eps):-
compose_transform_RzRyRx_GammaBetaAlpha(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft,
-4.5, 23.5, -17.5, -0.45, 0.65, -0.15,
0.0855206100177881, 7.4124421153311175, 24.613803224979733, 0.09408983628294156, 0.2864709557112676, 0.44486185098355907,
Eps).

?- test_compose_transform_RzRyRx_GammaBetaAlpha_1(TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd, Eps), minimize(Eps).
%works

?- test_compose_transform_RzRyRx_GammaBetaAlpha_2(TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd, Mleft, Mright, Mprod, Eps), minimize(Eps).
%works

?- test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, Eps), minimize(Eps).
%fails. Because of rounding ?

?- test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, Eps).
%This returns an answer space. So this is likely a proplem of precision ?

?- test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, 0.000000001).
%This returns an answer space. So this is likely a proplem of precision ?

``````

How can I solve this issue ?

And / or how can I get a result inside the returned space of

``````?- test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, 0.000000001).
``````

I can also send you the full code if you want.

According to https://eu.swi-prolog.org/pldoc/man?section=clpqr, `clpr` currently has no maintainer. While I haven’t used it and don’t understand any of its internals, let me take a stab.

Due to rounding and conversion errors, floating point arithmetic is mathematically unsound. It looks like `clpr` doesn’t deal with its illogical behaviour. For example given the set of constraints:

``````{Z = X*Y, X = Z/Y, Y = Z/X}
``````

and any two of the three values, it should derive the third. But:

``````?- {Z = X*Y, X = Z/Y, Y = Z/X}, X=0.1, Y= 0.2.
false.
``````

Using additional variables, you can see why:

``````?- {Z = X*Y, X1 = Z/Y, Y1 = Z/X}, X=0.1, Y= 0.2.
Z = 0.020000000000000004,
X = 0.1,
Y = 0.2,
X1 = 0.10000000000000002,
Y1 = 0.20000000000000004 .
``````

Clearly `X =\= X1` and `Y \= Y1` due to conversion errors and/or unsound floating point arithmetic.

I don’t quite understand what you’re doing with `Eps`. It looks like you’re constraining it be be `>=` to the largest error as the values of the other variables are resolved, and then setting it to the minimum, which should be the largest error. It’s not obvious to me why this last step fails.

It may be worth looking at “minimizing” a total error expression instead, e.g.,

`````` {ErrorF = RxxProdDiff^2 + RxyProdDif^2 + ... + RzzProdDiff^2}
``````

and then `minimize(ErrorF)`. But I’m not sure that will solve the problem and it seems like a lot of extra work to deal with “faulty” arithmetic.

I’m responsible for `pack(clpBNR)` https://eu.swi-prolog.org/pack/list?p=clpBNR which implements mathematically sound arithmetic over reals based on interval arithmetic. So:

``````?- {Z == X*Y, X == Z/Y, Y == Z/X}, X=0.1, Y= 0.2.
X = 0.1,
Y = 0.2,
Z:: 0.0200000000000000... .
``````

A possible downside is you may be left with “fuzzy” values but that’s really the best you can do given that the set of reals is not a finite domain. (There are an infinite number of real values between two adjacent floating point values.)

If you think clpBNR might be a fit for what you’re trying to do, I’ll try to answer any questions you might have.

HTH.

Dear Ridgeworks, thank you very much for your help. Let me give you some feedback.

`Eps` stands for epsilon. This is an admissable rounding error. It must be positive. So that I can enclose every error between `-Eps` and `Eps`. Like `-Eps =< ComputedValue - TheSameValueWithDifferentRounding =< Eps`. In your example, this would be `-Eps =< Y1-Y =< Eps`, which is true for instance for `Eps = 0.000001`. And then I minimize `Eps` so that `Y1`is as close as possible to `Y`. Sorry if this is not orthodox.

I tried your proposal of minimizing the sum of squared errors.
I removed everything dealing with Eps and modified to :

`````` ErrorProd = pow(RxxProdDiff,2) + pow(RxyProdDiff,2) + pow(RxzProdDiff,2) +
pow(RyxProdDiff,2) + pow(RyyProdDiff,2) + pow(RyzProdDiff,2) +
pow(RzxProdDiff,2) + pow(RzyProdDiff,2) + pow(RzzProdDiff,2)

}, minimize(ErrorProd).
``````

And I also tried `RxxProdDiff*RxxProdDiff`rather than `pow(RxxProdDiff,2)`.

Saddly this is even worst, because now NO test succeed …

Any other proposal ?

I might give a try with your librairy but I don’t have the time today.

What do you mean by “fuzzy” values ?
It is something to deal with your
`Z:: 0.0200000000000000...` ?
Is there a way I can turn it to a real at the end ? Like truncated or whatever (I don’t need angstrom precision).

Kind Regards

I think I understand the jist of what you’re trying to do, but I don’t think that’s what’s actually happening with your code. The `clpr` doc for `minimize/1` says:

Minimizes Expression within the current constraint store. This is the same as computing the infimum and equating the expression to that infimum.

so rather than minimizing (which currently fails) why don’t we just calculate the infimum of `Eps`:

``````?- test_compose_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft, Eps), inf(Eps,EpsInf).
EpsInf = 0.0,
{_A=<0.0, _B>=0.0, _= -0.5*_B-0.5*_A, _C=<0.0, _=_C+_B-_A, _C+_B-_A>=0.0, _= -_C-0.5*_B+0.5*_A, _D=<0.0,
....
``````

so the “calculated infinium” of Eps is `0.0`. Given any errors in the calculation at all, setting `Eps` to `0.0`, as would be done in `minimize/1`, must fail, no?

I would guess that the reason the infinium of `Eps` is `0.0` is that the only active constraint after `test_compose_transform_RzRyRx_GammaBetaAlpha_3` is `{ Eps >= 0 }`; all the others are passive constraints waiting for some condition (see isolation axioms) to set variables to values. But I’m not an expert on `clpr` so there well may be some other explanation.

Many ways, e.g., set the “fuzzy” value to it’s midpoint:

``````?- {Z == X*Y, X == Z/Y, Y == Z/X}, X=0.1, Y= 0.2, Z is midpoint(Z).
Z = 0.020000000000000004,
X = 0.1,
Y = 0.2.
``````

But you wouldn’t want to set it to a float until “the end”; otherwise you’re back in unsound arithmetic land (as the floating point result indicates).

Also don’t equate “real” to “floating point” - the first is mathematics, the second is computer science. The set of IEEE floating point numbers is a (vanishingly small) subset of real numbers.

I think you are running up against two limitations of clpr:

1. Non-linear constraints are passive until another constraint is applied to make it linear.

2. Floating point arithmetic is unsound so two expressions which are mathematically equivalent do not evaluate as equal.

It’s not clear to me how to overcome these limitations for your problem. Perhaps someone more familiar with `clpr` will provide some suggestions.

For what it’s worth, I implemented a solution in `clpbnr` both as an example for my own testing purposes and to verify there were no hidden "gotcha"s in the math as written. This was fairly straight forward but exposed a couple of issues. My version of the predicate has the signature:

``````constrain_transform_RzRyRx_GammaBetaAlpha(
TransXLeft,  TransYLeft,  TransZLeft,  AlphaLeft,  BetaLeft,  GammaLeft,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
TransXProd,  TransYProd,  TransZProd,  AlphaProd,  BetaProd,  GammaProd
)
``````

The first issue was that if the angles are not constrained to be in the range `(-pi,pi)` there is more than one solution. Also clpbnr reals include `±inf`, so the `Trans`* values must be constrained to be finite. (I used the default values of `±1e16`.)

``````?- test_constrain_transform_RzRyRx_GammaBetaAlpha_1(TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd).
TransXProd:: 0.0855206100178...,
TransYProd:: 7.412442115331...,
TransZProd:: 24.6138032249797...,
AlphaProd:: 0.09408983628294...,
BetaProd:: 0.28647095571126...,
GammaProd:: 0.44486185098356... .

?- test_constrain_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft).
TransXLeft::real(8.259960687502137, 11.76514674311086),
TransYLeft::real(-23.3238902059514, -16.745825674489407),
TransZLeft::real(28.755923227723528, 31.225460265239974),
AlphaLeft:: 0.5...,
BetaLeft:: -0.3...,
GammaLeft::real(0.17167301509126393, 0.3309015813718558).
``````

You’ve probably noticed that the answers for the second test case (`test_constrain_transform_RzRyRx_GammaBetaAlpha_3`) contain the expected result but the intervals are wide (i.e., not precise). For this particular problem constraint propagation was slow, activating an internal throttling mechanism so as to not give the appearance of non-termination. This mechanism is a heuristic with a user definable threshold parameter. So one way of getting a more precise result is by increasing the threshold, e.g.,

``````?- set_prolog_flag(clpBNR_iteration_limit,20000).
true.

?- test_constrain_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft).
TransXLeft:: 10.000...,
TransYLeft:: -20.000...,
TransZLeft:: 30.0000...,
AlphaLeft:: 0.50000...,
BetaLeft:: -0.35000...,
GammaLeft:: 0.25000... .

``````

Another way is to “search” for a better result using `solve/1`, analogous to labeling in `clpfd`:

``````?- test_constrain_transform_RzRyRx_GammaBetaAlpha_3(TransXLeft, TransYLeft, TransZLeft, AlphaLeft, BetaLeft, GammaLeft), solve([AlphaLeft, BetaLeft, GammaLeft]).
TransXLeft:: 10.000000000000...,
TransYLeft:: -20.000000000000...,
TransZLeft:: 30.000000000000...,
AlphaLeft:: 0.5000000000000...,
BetaLeft:: -0.3500000000000...,
GammaLeft:: 0.2500000000000... ;
false.
``````

producing a fairly precise result. I think it’s effective for this problem since the search fairly quickly prunes away parts of the solution space where constraint propagation is slow.

I also added a third test case calculating the `Trans*Right` from `Trans*Left` and `Trans*Prod`:

``````test_constrain_transform_RzRyRx_GammaBetaAlpha_4(TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight):-
constrain_transform_RzRyRx_GammaBetaAlpha(
10.0, -20.0, 30.0, 0.5, -0.35, 0.25,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
0.0855206100177881, 7.4124421153311175, 24.613803224979733, 0.09408983628294156, 0.2864709557112676, 0.44486185098355907 % TransXProd, TransYProd, TransZProd, AlphaProd, BetaProd, GammaProd,
).
``````

with the following results:

``````?- test_constrain_transform_RzRyRx_GammaBetaAlpha_4(TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight).
TransXRight:: -4.500000000000...,
TransYRight:: 23.500000000000...,
TransZRight:: -17.500000000000...,
AlphaRight::real(-0.6124042349089082, -0.32394880165368584),
BetaRight::real(0.5961825023418276, 0.7054558659262122),
GammaRight::real(-0.21794118794715925, -0.09054657629573598).

?- set_prolog_flag(clpBNR_iteration_limit,20000).
true.

?- test_constrain_transform_RzRyRx_GammaBetaAlpha_4(TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight).
TransXRight:: -4.500000000000...,
TransYRight:: 23.500000000000...,
TransZRight:: -17.500000000000...,
AlphaRight:: -0.4500...,
BetaRight:: 0.65000...,
GammaRight:: -0.15000... .

?- test_constrain_transform_RzRyRx_GammaBetaAlpha_4(TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight), solve([AlphaRight, BetaRight, GammaRight]).
TransXRight:: -4.500000000000...,
TransYRight:: 23.500000000000...,
TransZRight:: -17.500000000000...,
AlphaRight:: -0.4500000000000...,
BetaRight:: 0.6500000000000...,
GammaRight:: -0.1500000000000... ;
false.
``````

I did wonder about constraints that didn’t appear “symmetrical” with respect to `Right` and `Left`, e.g.,

``````  TransXProd == RxxLeft * TransXRight + RxyLeft * TransYRight + RxzLeft * TransZRight + TransXLeft
``````

but don’t understand enough about the problem to comment further. Sometimes adding redundant constraints can improve the performance of low level constraint propagation.

`clpbnr` version (refactors some common code into `rotate_matrix/4`:
``````constrain_transform_RzRyRx_GammaBetaAlpha(
TransXLeft,  TransYLeft,  TransZLeft,  AlphaLeft,  BetaLeft,  GammaLeft,
TransXRight, TransYRight, TransZRight, AlphaRight, BetaRight, GammaRight,
TransXProd,  TransYProd,  TransZProd,  AlphaProd,  BetaProd,  GammaProd
) :-

[TransXLeft,  TransYLeft,  TransZLeft,
TransXRight, TransYRight, TransZRight,
TransXProd,  TransYProd,  TransZProd
]::real,      % ensure finite

rotate_matrix([[RxxLeft, RxyLeft, RxzLeft], [RyxLeft, RyyLeft, RyzLeft], [RzxLeft, RzyLeft, RzzLeft]],
[AlphaLeft, BetaLeft, GammaLeft]
),

rotate_matrix([[RxxRight, RxyRight, RxzRight], [RyxRight, RyyRight, RyzRight], [RzxRight, RzyRight, RzzRight]],
[AlphaRight, BetaRight, GammaRight]
),

rotate_matrix([[RxxProd, RxyProd, RxzProd], [RyxProd, RyyProd, RyzProd], [RzxProd, RzyProd, RzzProd]],
[AlphaProd, BetaProd, GammaProd]
),

{ RxxProd == RxxLeft * RxxRight + RxyLeft * RyxRight + RxzLeft * RzxRight,
RxyProd == RxxLeft * RxyRight + RxyLeft * RyyRight + RxzLeft * RzyRight,
RxzProd == RxxLeft * RxzRight + RxyLeft * RyzRight + RxzLeft * RzzRight,
TransXProd == RxxLeft * TransXRight + RxyLeft * TransYRight + RxzLeft * TransZRight + TransXLeft,

RyxProd == RyxLeft * RxxRight + RyyLeft * RyxRight + RyzLeft * RzxRight,
RyyProd == RyxLeft * RxyRight + RyyLeft * RyyRight + RyzLeft * RzyRight,
RyzProd == RyxLeft * RxzRight + RyyLeft * RyzRight + RyzLeft * RzzRight,
TransYProd == RyxLeft * TransXRight + RyyLeft * TransYRight + RyzLeft * TransZRight + TransYLeft,

RzxProd == RzxLeft * RxxRight + RzyLeft * RyxRight + RzzLeft * RzxRight,
RzyProd == RzxLeft * RxyRight + RzyLeft * RyyRight + RzzLeft * RzyRight,
RzzProd == RzxLeft * RxzRight + RzyLeft * RyzRight + RzzLeft * RzzRight,
TransZProd == RzxLeft * TransXRight + RzyLeft * TransYRight + RzzLeft * TransZRight + TransZLeft
}.

rotate_matrix([[Rxx, Rxy, Rxz], [Ryx, Ryy, Ryz], [Rzx, Rzy, Rzz]],
[Alpha, Beta, Gamma]
):-
[Alpha, Beta, Gamma]::real(-pi/2,pi/2),
{ Rxx == cos(Beta) * cos(Gamma),
Rxy == sin(Alpha) * sin(Beta) * cos(Gamma) - cos(Alpha) * sin(Gamma),
Rxz == cos(Alpha) * sin(Beta) * cos(Gamma) + sin(Alpha) * sin(Gamma),

Ryx == cos(Beta) * sin(Gamma),
Ryy == sin(Alpha) * sin(Beta) * sin(Gamma) + cos(Alpha) * cos(Gamma),
Ryz == cos(Alpha) * sin(Beta) * sin(Gamma) - sin(Alpha) * cos(Gamma),

Rzx == -sin(Beta),
Rzy == sin(Alpha) * cos(Beta),
Rzz == cos(Alpha) * cos(Beta)
}.
``````
2 Likes

Thank you Ridgeworks. I tried myself this morning (before your post) and arrived to the almost the same solution / same results as you, using `solve` (but your code is better factorized and clean, I am still very new to Prolog…). I did not noticed the `set_prolog_flag(clpBNR_iteration_limit,20000).` . When should I use the `solve` or the `clpBNR_iteration_limit`?

Remark : I think you have restricted the angles to much. I changed to

``````   %[Alpha, Beta, Gamma]::real(-pi/2,pi/2), %<- proposal from Ridgeworks. But this is too limiting IMHO. BB 20230620
[Alpha, Gamma]::real(-pi,pi), %Looking at : https://en.wikipedia.org/wiki/Euler_angles#Signs,_ranges_and_conventions
Beta::real(-pi/2,pi/2), %Looking at : https://en.wikipedia.org/wiki/Euler_angles#Signs,_ranges_and_conventions
``````

I was expecting then more solutions, but no… Maybe I should find and try particular examples with particular angles …

I wish I had a good answer. The rate at which intervals narrow is dependant on the particular constraints being applied so it’s generally impossible to predict. It’s a fixed point iteration so it can take a while for a 64 bit floating point bound to stop changing. In many situations constraints are applied incrementally so you don’t want to work too hard at any particular point if a future constraint makes the whole job easier. So I think it’s probably best to use solve (with the default iteration limit) in most situations.

I think you’re right. When I set all three angles to `[-pi,pi]`, I was getting two solutions with `solve`. `[-pi/2,pi/2]` for all yielded in just one, but I agree that’s too restrictive (see Wikipedia page). A range of `[-pi,pi]` for `Alpha` and `Gamma` is correct, but appears to require the use of `solve` (still 1 solution), whereas a range of, for example, `[-0.999*pi,0.999*pi]` will also narrow via fixed point convergence. This just reinforces what I said above about how difficult it is to predict which approach (solve vs. extended iteration) will be the most effective in any particular situation.

Anyway, I think you’ve gotten enough working that you can sort out the details.