[GiNaC-list] Issue with derivative of "abs"

Pierangelo Masarati pierangelo.masarati at polimi.it
Tue Jun 16 19:24:32 CEST 2020


Dear GiNaC users,

we have been using GiNaC for years now, to provide our multibody 
dynamics solver, MBDyn <http://www.mbdyn.org/>, some symbolic 
differentiation capabilities (perhaps not the most appropriate use, but 
some users seem to like it).

I'll make the story as short as possible. In one of the most interesting 
applications, the user needs to:

- define the variables that represent strains (and strain rates),

- the expression for the configuration-dependent generalized forces

in short, a constitutive law. The expression's partial derivatives with 
respect to the strains and strain rates are then automatically computed 
by GiNaC, and, when needed, the numerical values are substituted and the 
expressions are evaluated.

The related code snippet looks like

>         GiNaC::symbol gEps("v");  // parameter symbols
>         GiNaC::ex gExpr;  // expressions
>         GiNaC::ex gExprDEps;  // derivatives
>
>         GiNaC::lst l;
>
>         l.append(gEps);
>         gExpr = GiNaC::ex("v*abs(v)", l);
>
>         gExprDEps = gExpr.diff(gEps);

One user recently tried something like this:

     var = v

     expr = v*abs(v)    # a turbulent viscous law

the result was

     dexpr/dvar = abs(v)+1/2*v*(v+conjugate(v))*abs(v)^(-1)

which fails when v = 0 because of the division by zero.  I tried with 
"realsymbol" (since our values can only be real); things, however, 
didn't change much:

     dexpr/dvar = v^2*abs(v)^(-1)+abs(v)

I realized that the issue is in the derivative of the abs() function; 
indeed, the proposed formula is rather general (despite failing when 
numerically evaluated with 0 as the argument).  When the argument is 
real, a more robust representation would be "d(abs(v))/dv = sign(v)".  
In the case at hand, "d(v*abs(v))/dv = 2*abs(v)" or, as a second choice, 
"d(v*abs(v))/dv = abs(v) + v*sign(v)".

First, I considered the possibility to implement the "sign()" function 
in GiNaC, but I am not familiar enough with its internals. If it makes 
sense, I would definitely recommend its introduction.

However, I noticed that the "step()" function is implemented (and 
step(0) correctly evaluates to 1/2); the "sign()" function can then be 
obtained as "sign(x) = 2*step(x) - 1" when "x" is real. To this end, I 
modified GiNaC's code as reported here:

> diff --git a/ginac/inifcns.cpp b/ginac/inifcns.cpp
> index d68afbb5..ec74a435 100644
> --- a/ginac/inifcns.cpp
> +++ b/ginac/inifcns.cpp
> @@ -321,6 +321,8 @@ static ex abs_expand(const ex & arg, unsigned options)
>  static ex abs_expl_derivative(const ex & arg, const symbol & s)
>  {
>         ex diff_arg = arg.diff(s);
> +       if (arg.info(info_flags::real))
> +               return diff_arg*(2*step(arg) - 1);
>         return 
> (diff_arg*arg.conjugate()+arg*diff_arg.conjugate())/2/abs(arg);
>  }
which indeed gives the expected result:

     dexpr/dvar = v*(-1+2*step(v))+abs(v)

I wonder whether my fix can be considered correct and, in case, whether 
it could be included in the distribution.

Sincerely, p.

-- 
Pierangelo Masarati
Professore Ordinario di Costruzioni e Strutture Aerospaziali
Dipartimento di Scienze e Tecnologie Aerospaziali
Politecnico di Milano



More information about the GiNaC-list mailing list