Skip to content

genserkins: use posemath, remove gomath - #4676

Open
grandixximo wants to merge 4 commits into
LinuxCNC:masterfrom
grandixximo:posemath-consolidate
Open

grandixximo wants to merge 4 commits into
LinuxCNC:masterfrom
grandixximo:posemath-consolidate

Conversation

@grandixximo

Copy link
Copy Markdown
Contributor

gomath came in with genserkins in 2009, adapted from Fred Proctor's code, with a TODO in its header: "find the new functions, add them to posemath, convert the rest". genserkins was its only user. This does that conversion and removes gomath.

  • genserkins: use posemath instead of gomath. The pose, quaternion and RPY work goes through posemath. What posemath has no counterpart for stays in genserfuncs.c as static functions: the Jacobian's small matrices and their LU inverse, the Denavit-Hartenberg link pose, and the quaternion to rotation vector conversion (pmQuatRotConvert() zeroes a rotation under 1e-6, and the inverse iterates to 1e-7). The thresholds are gomath's double ones, so the results do not move.
  • libposemath: remove gomath. gomath.c, gomath.h and gotypes.h, about 4800 lines. The headers were never exported, so no module outside the tree could include them.
  • posemath: take the parallel checks over from blendmath. pmCartCartParallel() and pmCartCartAntiParallel() are plain vector helpers, so they move into posemath with the other PmCartesian functions.
  • tests: check the posemath parallel tests as a realtime component. tests/blendmath compiled blendmath.c with -DULAPI, which fails on GCC 14 since tp: make helix curvature independent of arc subdivision #4643 added EXPORT_SYMBOL there, the same failure as tests/tool-frame in Test tests/tool-frame fails locally #4670. It becomes tests/posemath-parallel, a component loaded after tpmod, which exports the two functions.

Fixes #389: genser now uses posemath's RPY conversion, which handles a pitch of minus a quarter turn. With the default geometry and the wrist at -90, gomath reported A -54.7, C -54.7 and the inverse came back with other joints; it now reports A -180, B -90, C 0 and the inverse returns the joints it was given. This supersedes the gomath part of #4623, where @Rimas2200 found and fixed the same branch. The ZYX half of that fix still applies to posemath's pmMatZyxConvert(), which nothing calls today; I left it to #4623.

Testing: I compared genserkins before and after over a grid of 592 joint sets plus the poles. The forward agrees to 1e-12, and the inverse returns the same joints and the same result everywhere except with the wrist exactly straight, where its Jacobian is singular and the iteration can settle on another of the equivalent wrist solutions. The full test suite passes apart from tests/tool-frame, which #4673 fixes.

@BsAtHome this is the consolidation from the #4673 discussion. One choice I am not sure of: tpmod exports the two posemath functions only so the test can reach them. posemath as a module of its own would be the cleaner shape, but every module that links it would then need it loaded first, so I left that out of this PR.

@grandixximo

Copy link
Copy Markdown
Contributor Author

@BsAtHome this is the consolidation from the #4673 discussion. One choice I am not sure of: tpmod exports the two posemath functions only so the test can reach them. posemath as a module of its own would be the cleaner shape, but every module that links it would then need it loaded first, so I left that out of this PR, should it be a follow up?

@BsAtHome

Copy link
Copy Markdown
Contributor

@BsAtHome this is the consolidation from the #4673 discussion. One choice I am not sure of: tpmod exports the two posemath functions only so the test can reach them. posemath as a module of its own would be the cleaner shape, but every module that links it would then need it loaded first, so I left that out of this PR, should it be a follow up?

This cleanup is the right strategy.

Exporting from tpmod,... making posemath a component would be a hassle and suddenly require load order control. That could potentially affect many configs.

What happens when an RT module links to an additional DSO? Your test program could, in principle, link with libposemath.so at link-time. There are no RT/non-RT differences in that DSO afaik. There is no use of either RTAPI or ULAPI defines in posemath. Or would the import of libposemath.so cause name collisions? I think that the load order normally resolves names for runtime linking. maybe worth a test?

@grandixximo

Copy link
Copy Markdown
Contributor Author

I tested it. Linking works, and it shows the realtime component is not needed at all.

The checker built as a realtime component with -lposemath (through EXTRA_LDFLAGS; halcompile's --extra-link-args is for userspace components only) gets libposemath.so.0 as a NEEDED entry and loads under uspace with no name collision:

  • loaded alone, its two calls bind to libposemath.so.0;
  • loaded after tpmod (and genserkins), they bind to tpmod.so's exported copy, because rtapi_app opens modules RTLD_GLOBAL and the global scope is searched before the module's own dependencies.

So it works, but which copy the test checks depends on load order. And on RTAI a kernel module cannot link a userspace .so at all; CI only builds there, but runtests on an RTAI machine would fail.

The simpler answer is your first point: posemath has no RTAPI or ULAPI code (rtapi_math.h is math.h in userspace), and the two functions live in posemath now. A plain program linked with -lposemath checks them; I built one with GCC 14 and -Wall -Werror and it runs. The old tests/blendmath broke only because it compiled blendmath.c with -DULAPI; without blendmath.c in the compile line that problem is gone.

So unless you see a reason to keep it in realtime, I'll fold that into this PR: the test becomes a userspace program again, linked with -lposemath, and the two EXPORT_SYMBOL lines leave tpmod.c, so nothing is exported only for a test. OK with you?

@BsAtHome

Copy link
Copy Markdown
Contributor

Well, I thought the only requirement for the extra exported functions was being able to run the test.

Since this works, the real question is whether the actual code is identical between the version in RT compiled libposemath/emcpose.o and libposemath/_posemath.o verses the libposemath.so versions. The compiler may have been run with different flags.

If they are different, then running the test in RT and in non-RT may give different results. That would actually be an "interesting" problem... This needs to be checked before we go either way.

The RTAI problem,... how many tests currently fail on RTAI? It is only tested incidental when someone tries it. However, this does make an argument for just doing the two export symbol lines if the test is to be done in RT. If the test can be done in non-RT, without loss of expression, then we can ignore RTAI and it would be preferable to do the test in non-RT.

@grandixximo

Copy link
Copy Markdown
Contributor Author

I checked it. The flags do differ: the realtime objects add -fno-fast-math -mieee-fp -fno-unsafe-math-optimizations -fno-builtin-sin -fno-builtin-cos -fno-builtin-sincos. The first three are GCC's defaults, and the builtin ones only change calls to sin() and cos().

pmCartCartParallel(), pmCartCartAntiParallel() and the two helpers they call make no such calls, and compile to the same instructions in both builds. The functions that do call sin() and cos() give byte-identical output from both builds too, over 2 million random inputs each.

So a userspace test of these two functions loses nothing against the realtime build; a comment in test.sh says so. I'll push it: tests/posemath-parallel as a plain program linked with -lposemath, and tpmod.c back to master's exports.

Comment thread src/emc/kinematics/genserfuncs.c Outdated
genser_matrix *ab)
{
genser_matrix out;
int row, col, i;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are loop variables constrained to the loop. They should be declared in the for() statement. It prevents scope creep of these locals.

(same comment many other places too in your added code)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Every loop in the code I added (the matrix and LU helpers, compute_jfwd(), link_pose_build(), genser_kin_fwd()) declares its variable in the for(), and the other locals moved into the scope that uses them.

Comment thread src/emc/kinematics/genserfuncs.c Outdated
/* backsubstitute a column with a 1 in it to get the inverse */
matrix_init(&out, n, n);
for (col = 0; col < n; col++) {
memset(v, 0, sizeof(v));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could declare v in the loop and assign {} to zero it, like in:

for (int col = 0; col < n; col++) {
    double v[GENSER_MAX_JOINTS] = {};
    ...
}
...

Makes locals local and does not require the memset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: double v[GENSER_MAX_JOINTS] = {0.0}; inside the loop, no memset(). I used {0.0} since an empty {} is standard C only from C23.

Comment thread src/emc/kinematics/genserfuncs.c Outdated
int row, i;

for (row = 0; row < a->rows; row++) {
out[row] = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out[row] is a double. Then you should assign 0.0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and the other double constants in the added code too.

Comment thread src/emc/kinematics/genserfuncs.c Outdated
static int ludcmp(double a[GENSER_MAX_JOINTS][GENSER_MAX_JOINTS], int n, int *indx)
{
double scale[GENSER_MAX_JOINTS];
double big, dum, sum, temp;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment on locals. Better to be declared within the scope they are required.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: big, sum, the swap value and the pivot reciprocal now live in the loops that use them.

Comment thread src/emc/kinematics/ugenserkins.c Outdated
joints[0],joints[1],joints[2],joints[3],joints[4],joints[5]);
if (0 != retval) {
printf("inv kins error %d <%s>\n", retval,go_result_to_string(retval));
printf("inv kins error %d\n", retval);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error string got removed. What error are possible? Worth looking into to convert to text again? Or are these trivial errors?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two errors are left: GENSER_ERROR, the inverse did not converge, and GENSER_SINGULAR, a Jacobian with no inverse. A small result_string() in ugenserkins.c prints them as text again.

genserkins was the only user of gomath. The pose, quaternion and RPY work now goes through posemath, which has the same routines. What posemath has no counterpart for stays with genser as static functions in genserfuncs.c: the small matrices of the Jacobian and their LU inverse, the Denavit-Hartenberg link pose, and the quaternion to rotation vector conversion, since pmQuatRotConvert() zeroes a rotation under 1e-6 and the inverse iterates to 1e-7.

pmPosePoseMult() writes the result's translation before it reads the first operand's, so products whose result is an operand go through a copy.

The thresholds are gomath's double ones, 1e-7, so the results do not move: over a grid of 592 poses the forward agrees to 1e-12, and the inverse returns the same joints and the same result at every pose except those with the wrist exactly straight, where its Jacobian is singular and the iteration may settle on another of the equivalent wrist solutions.

At a pitch of minus a quarter turn the RPY conversion now takes the branch posemath has for it; gomath tested plus a quarter turn twice (LinuxCNC#389).

genser_kin_jac_inv() and genser_kin_jac_fwd() had no callers and go, with the declarations of functions genserkins.h named but nothing defined.
With genserkins on posemath nothing uses gomath. Its headers were never exported, so no module outside the tree could include them.
pmCartCartParallel() and pmCartCartAntiParallel() are vector helpers with nothing of the planner in them, so they go with the rest of the PmCartesian functions. The debug JSON output they wrote goes with the move, since posemath has no planner debug output.
tests/blendmath compiled blendmath.c into a userspace program with -DULAPI. Since blendmath.c exports pmCircleEffectiveMinRadius() (LinuxCNC#4643), EXPORT_SYMBOL is undefined there, which GCC 13 lets through as an implicit int and GCC 14 rejects. The two functions it checks are posemath's now, and posemath uses neither RTAPI nor ULAPI, so it becomes tests/posemath-parallel: a program linked with libposemath, with no blendmath.c to compile and nothing exported from a module for the test. The checks themselves are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gomath.c error

2 participants