Mathematica: Overriding Listable Plus

I would like to define a pt character to hold a point (and end up caching some data associated with that point):

 pt::"usage" = "pt[{x,y}] represents a point at {x,y}"; 

I would like to be able to use such pt objects as points in the greatest possible amount, especially, I would like to be able to write

 {a0,a1}+pt[{b0,b1}] 

and return pt[{a0+b0,a1+b1}] instead of {a0+pt[{b0,b1}],a1+pt[{b0,b1}]} . My initial idea was to use:

 pt /: Plus[pt[p0_], p1 : {_, _}] = pt[p0 + p1]; 

But this does not work (because Plus is Listable ?). Is there a way to do this without removing the Plus protection?

Update: As Leonid points out, this is not possible without Plus global or local hack, since the Listable attribute Listable considered before any * values. This is indeed very accurately described in the assessment textbook .

+4
source share
2 answers

The Mathematica evaluator does not seem flexible enough to do this easily. UpValues ​​for pt actually apply before DownValues ​​for Plus, but list overflows due to Listability even before that. In this particular case, the following may work for you:

 eval = Function[code,Block[{Plus = Plus, attr = DeleteCases[Attributes[Plus], Listable]}, SetAttributes[Plus, attr]; code], HoldAll] 

To use it, wrap it around a piece of code where you want your pt rule to apply, for example:

 eval[{a0, a1} + pt[{b0, b1}]] 

You can use $ Pre as $Pre = eval to avoid typing eval every time, although normally I would not recommend this. Blocking Plus is a milder way to temporarily disable some or all of its attributes. The advantage of wrt cleaning and setting attributes without a block is that you cannot get into global state with the Listable attribute enabled, even if an exception is thrown or the calculation is canceled.

Since the Listable attribute directly affects the evaluation, and not the pattern matching (the latter, of course, can affect indirectly if some template should correspond to the Plus result skipped over the list), in most cases this should be good. Theoretically, this can lead to some undesirable effects in some cases, especially when it comes to pattern matching. But in practice this can be quite good. A cleaner, but more complex solution would be to create a custom evaluator tailored to your needs.

+6
source

The following is a bit wasteful, but it works. The idea is to simply watch for cases where the Listable Plus attribute puts the same pt in all the elements of the list (i.e. the starting point), and then pull it back. First, define a function to add pt objects:

 SetAttributes[ptPlus, {Orderless}] ptPlus[pt[pa : {_, _}], pt[pb : {_, _}], r___] := ptPlus[pt[pa + pb], r]; ptPlus[p_pt] := p; 

Then we make sure that any Plus that includes pt maps to ptPlus (associate the rule with pt).

 Plus[h___, a_pt, t___] ^:= ptPlus[h, a, t]; 

The above rules mean that: {x0,y0}+pt[{x1,y1}] will be expanded from {x0+pt[{x1,y1}],y0+pt[{x1,y1}]} to {ptPlus[x0,pt[{x1,y1}]],ptPlus[y0,pt[{x1,y1}]]} . Now we just make a rule to convert this to pt[{x0,y0}]+pt[{x1,y1}] (note the deferred condition, which checks that pt are equal):

 {ptPlus[x__], ptPlus[y__]} ^:= Module[{ ptCases = Cases[{{x}, {y}}, _pt, {2}]}, ptCases[[1]] + pt[Plus @@@ DeleteCases[{{x}, {y}}, _pt, {2}]] /; Equal @@ ptCases] 

A more opaque, but slightly more thorough version, which is easier to generalize to higher sizes:

 ptPlus /: p : {_ptPlus, _ptPlus} := Module[{ptCases, rest, lp = ReleaseHold@Apply [List, Hold[p], {2}]}, ptCases = Cases[lp, _pt, {2}]; rest = Plus @@@ DeleteCases[lp, _pt, {2}]; ptCases[[1]] + pt[rest] /; And[Equal @@ ptCases, VectorQ@rest ]] 

This whole approach, of course, will lead to terribly subtle errors when {a+pt[{0,0}],a+pt[{0,b}]} /. {a -> pt[{0,0}]} {a+pt[{0,0}],a+pt[{0,b}]} /. {a -> pt[{0,0}]} will evaluate pt[{0,0}] when c==0 and {pt[{0,0}],pt[{0,c}]} otherwise case ...

HTH, the guy said to himself ...

+1
source

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


All Articles