Signature PInvoke lmdif1

I am trying to use the lmdif1 method in cminpack_dll.dll from C # and I am encountering some interesting errors.

The second and third parameters passed to lmdif1 are ints, and for my test, the values ​​in order are 12 and 9. Now that ther are in C code, the value that was 12 is now 9, and the value 9 is between 308000 and 912000. I do not know why.

First of all, I was wondering if the signature I use is really

C # signature:

[DllImport("cminpack_dll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int lmdif1(IntPtr fcn, int m, int n, double[] x, double[] fVec, double tol, int[] iwa, double[] wa, int lwa); 

Signature C:

 int __cminpack_func__(lmdif1)(__cminpack_decl_fcn_mn__ void *p, int m, int n, real *x, real *fvec, real tol, int *iwa, real *wa, int lwa) 

And as I call it:

 //All of the variables passed in match the types in C# signature above. var info =lmdif1( functionPointer, pointsetLength, initialGuessLength, initialGuess, fVec, tol, iwa, wa, lwa); 

Now this is my first time I do PInvoke, and my C is not great, I have never done this before, so any help would be great. My suspicion is that I might need a marshal, but I tried combining ints like I4 and like U4, and it still does the same.

I welcome you for your help.

EDIT: Here is a comment that describes the C function, if that helps:

 /* ********** */ /* subroutine lmdif1 */ /* the purpose of lmdif1 is to minimize the sum of the squares of */ /* m nonlinear functions in n variables by a modification of the */ /* levenberg-marquardt algorithm. this is done by using the more */ /* general least-squares solver lmdif. the user must provide a */ /* subroutine which calculates the functions. the jacobian is */ /* then calculated by a forward-difference approximation. */ /* the subroutine statement is */ /* subroutine lmdif1(fcn,m,n,x,fvec,tol,info,iwa,wa,lwa) */ /* where */ /* fcn is the name of the user-supplied subroutine which */ /* calculates the functions. fcn must be declared */ /* in an external statement in the user calling */ /* program, and should be written as follows. */ /* subroutine fcn(m,n,x,fvec,iflag) */ /* integer m,n,iflag */ /* double precision x(n),fvec(m) */ /* ---------- */ /* calculate the functions at x and */ /* return this vector in fvec. */ /* ---------- */ /* return */ /* end */ /* the value of iflag should not be changed by fcn unless */ /* the user wants to terminate execution of lmdif1. */ /* in this case set iflag to a negative integer. */ /* m is a positive integer input variable set to the number */ /* of functions. */ /* n is a positive integer input variable set to the number */ /* of variables. n must not exceed m. */ /* x is an array of length n. on input x must contain */ /* an initial estimate of the solution vector. on output x */ /* contains the final estimate of the solution vector. */ /* fvec is an output array of length m which contains */ /* the functions evaluated at the output x. */ /* tol is a nonnegative input variable. termination occurs */ /* when the algorithm estimates either that the relative */ /* error in the sum of squares is at most tol or that */ /* the relative error between x and the solution is at */ /* most tol. */ /* info is an integer output variable. if the user has */ /* terminated execution, info is set to the (negative) */ /* value of iflag. see description of fcn. otherwise, */ /* info is set as follows. */ /* info = 0 improper input parameters. */ /* info = 1 algorithm estimates that the relative error */ /* in the sum of squares is at most tol. */ /* info = 2 algorithm estimates that the relative error */ /* between x and the solution is at most tol. */ /* info = 3 conditions for info = 1 and info = 2 both hold. */ /* info = 4 fvec is orthogonal to the columns of the */ /* jacobian to machine precision. */ /* info = 5 number of calls to fcn has reached or */ /* exceeded 200*(n+1). */ /* info = 6 tol is too small. no further reduction in */ /* the sum of squares is possible. */ /* info = 7 tol is too small. no further improvement in */ /* the approximate solution x is possible. */ /* iwa is an integer work array of length n. */ /* wa is a work array of length lwa. */ /* lwa is a positive integer input variable not less than */ /* m*n+5*n+m. */ /* subprograms called */ /* user-supplied ...... fcn */ /* minpack-supplied ... lmdif */ /* argonne national laboratory. minpack project. march 1980. */ /* burton s. garbow, kenneth e. hillstrom, jorge j. more */ /* ********** */ /* check the input parameters for errors. */ 
+6
source share
2 answers

I think the main problem here is that you have no real way to tell what native code is. To do this, you need to understand all the macros.

So here is what I did. I downloaded the library and passed the lmdif1.c file through the C preprocessor and therefore extended the macros. I used one of my mingw compiler. He made the following conclusion:

 int __attribute__((__dllexport__)) lmdif1(cminpack_func_mn fcn_mn, void *p, int m, int n, double *x, double *fvec, double tol, int *iwa, double *wa, int lwa); 

Then, looking at the definition of cminpack_func_mn , we have:

 typedef int (*cminpack_func_mn)(void *p, int m, int n, const double *x, double *fvec, int iflag); 

So there is an extra void pointer that lmdif1 gets. Since the cminpack_func_mn function pointer type also gets a pointer to a void with the same name without a name, I bet that the pointer you pass to lmdif1 goes back to your fcn_mn callback function. This mechanism is commonly used to allow a library consumer to write a callback function that has access to an additional state.

If you don't need this, and of course you won't be with a C # delegate, you can pass IntPtr.Zero and ignore the value in the callback.

To fix this, you need to make three changes:

  • Add a void pointer to the C # lmdif1 .
  • Add a void pointer to the C # delegate declaration of the callback and your function that implements the callback.
  • Pass IntPtr.Zero to the extra void pointer of the lmdif1 parameter.

I'm not sure why you specified your callback parameter as IntPtr . Here you can use the delegate type and also use the UnmanagedFunctionPointer attribute to force the use of Cdecl .

Update:. For completeness, I dug up the lmdif1 implementation and looked at how the callback is called. And yes, the void p pointer goes back to the callback, as described above.

+6
source

Adding to @David Heffernan’s excellent answer, you will notice that using a double[] for the x and fvec arrays will not be sufficient for a callback, since information about the lengths of the arrays is lost in processing. Thus, the final signatures for the C function and the delegate might look something like this:

 [DllImport("cminpack_dll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int lmdif1(CminpackFuncMn fcn, IntPtr p, int m, int n, double[] x, double[] fvec, double tol, int[] iwa, double[] wa, int lwa); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int CminpackFuncMn(IntPtr p, int m, int n, IntPtr x, IntPtr fvec, int iflag); 

In this case, an example of use is the following, in which we compare quadratic data with some test data:

 // Define some test data by 5i + 3i^2. The plan is to let cminpack figure out // the values 5 and 3. var data = Enumerable.Range(0, 20) .Select(i => 5 * i + 3 * Math.Pow(i, 2)) .ToList(); CminpackFuncMn residuals = (p, m, n, x, fvec, iflag) => { unsafe { // Update fvec with the values of the residuals x[0]*i + x[1]*i^2 - data[i]. var fvecPtr = (double*)fvec; var xPtr = (double*)x; for (var i = 0; i < m; i++) *(fvecPtr + i) = *xPtr * i + *(xPtr + 1) * Math.Pow(i, 2) - data[i]; } return 0; }; // Define an initial (bad, but not terrible) guess for the value of the parameters x. double[] parameters = { 2d, 2d }; var numParameters = parameters.Length; var numResiduals = data.Count; var lwa = numResiduals * numParameters + 5 * numParameters + numResiduals; // Call cminpack var info = lmdif1( fcn: residuals, p: IntPtr.Zero, m: numResiduals, n: numParameters, x: parameters, fvec: new double[numResiduals], tol: 0.00001, iwa: new int[numParameters], wa: new double[lwa], lwa: lwa); // info is now 2, and parameters are { 5, 3 }. Console.WriteLine($"Return value: {info}, x: {string.Join(", ", parameters)}"); 
0
source

Source: https://habr.com/ru/post/970394/


All Articles