2
$\begingroup$

Not sure if this has been asked before, please point me to the post if it has.

Is there a way to treat terms like (b+c), a**(b+c), (b+c)**d and a**(b+c)**d together? By that I mean, is there a pattern like a_(Something or nothing)**(b_+c_)**d_(Something or nothing) and which allows for distributivity to be defined as something like

NCExpand[a_(Something or nothing)**(b_+c_)**d_(Something or nothing)]:= a**b**d+a**c**d

where for the case where a or d is not present they are not present in the expanded version.

Edit

Based on what is said in the comments I've done

But as can be seen from the output it doesn't work when the head is not NonCommutativeMultiply. I would like to be able to include it in the same definition.

$\endgroup$
7
  • 1
    $\begingroup$ Yes, see this. Example: f[a___,b_+c_,d___]:=f[a,b,d]+f[a,c,d]. For a tutorial, see this. $\endgroup$
    – user293787
    Commented Oct 24, 2022 at 9:29
  • $\begingroup$ Could you explain how you would specifically implement it for the example I gave? $\endgroup$
    – JvT
    Commented Oct 27, 2022 at 5:46
  • $\begingroup$ I guess what I mean is if I apply what you gave to my example for NCExpand[b+c] it will just return NCExpand[b+c]. I was wondering if you could somehow include this case in the definition of NCExpand[] without requiring a separate definition NCExpand[b_+c_]:=NCExpand[b]+NCExpand[c]. $\endgroup$
    – JvT
    Commented Oct 27, 2022 at 5:54
  • $\begingroup$ That case is included, if you use 3 underscores for a and d. $\endgroup$
    – user293787
    Commented Oct 27, 2022 at 5:57
  • $\begingroup$ How is it included? $\endgroup$
    – JvT
    Commented Oct 29, 2022 at 1:32

1 Answer 1

1
$\begingroup$

Note: You might want to consider simply using NCExpand[a_] := Distribute[a, Plus, NonCommutativeMultiply]. That said, the following provides a useful way of making NonCommutativeMultiply behave like Times with an identity (which is beneficial even outside of this problem), and also answers the original question.

So, if you want to mirror how e.g. Times works, then what you want to do is exploit the OneIdentity attribute (which NonCommutativeMultiply already has), Default values and Optional patterns, and implement an identity value for NonCommutativeMultiply.


If f has the attribute OneIdentity, that means that f[x], f[f[x]], etc. will match x.

Optional patterns match a pattern to a provided default value if the expression does not appear. But there are two ways to provide a default value: either inline or via a value that's globally associated with the enclosing head. For example, if we have f[a_:0, b_] := {a,b}, then f[2] will be {0,2} thanks to the inline Optional pattern a_:0. But we could also use f[a_., b_] (a_. is Optional[a]) together with a separate evaluation of Default[f] = 0 to get the same result.

This works for pattern matching too: e.g. Default[Times] is 1, and f[Times[x_.,3]] := x means that f[3 y] gives y, while f[3] gives 1. The OneIdentity attribute means that 3 matches Times[3] which matches Times[x_.,3].

To be honest, I'm not 100% sure why b+c doesn't then match NonCommutativeMultiply[a___,b_+c_,d___]. It seems OneIdentity only meshes well with Optional patterns. However, it does match NonCommutativeMultiply[a_., b + c, d_.].

Here's a gotcha to watch out for: the Default value must be set before any definitions involving default patterns are evaluated. I suspect Optional is partially magical.

Making NonCommutativeMultiply behave well

We then also need to actually make our Default value actually behave like an identity for NonCommutativeMultiply. To do this, we add a definition using the intercept trick.

We also need to make NonCommutativeMultiply[a] be a and NonCommutativeMultiply[] be the default value. Due to, I suspect (but possibly not), the magic of Optional, we need to temporarily clear NonCommutativeMultiply's attributes while setting that definition.


So, putting it all together:

Unprotect[NonCommutativeMultiply];

Default[NonCommutativeMultiply] = 1; (* or whatever value you'd like here *)

(* Set unary and nullary behavior: *)
Block[{a = Attributes[NonCommutativeMultiply]},
  Attributes[NonCommutativeMultiply] = {};
  NonCommutativeMultiply[a_.] := a;
  Attributes[NonCommutativeMultiply] = a;
  ];

(* Make Default[NonCommutativeMultiply] behave as an identity
   for NonCommutativeMultiply and avoid infinite recursion
   via the intercept trick: *)
NCMIntercept = True;

NonCommutativeMultiply[a_, b__] :=
 Block[{NCMIntercept = False},
  NonCommutativeMultiply @@ DeleteCases[{a,b}, Default[NonCommutativeMultiply]]
 ] /; NCMIntercept

Protect[NonCommutativeMultiply, NCMIntercept];

NCExpand[a_. ** (b_ + c_) ** d_.] := a ** b ** d + a ** c ** d

(Note that while NCExpand[b + c] works as expected, NCExpand[b] is still undefined! Another definition (NCExpand[a_] := a) is needed.)

It is possible to do this a bit differently if you want NonCommutativeMultiply[a] to not evaluate further (the default behavior), perhaps by using Default[NonCommutativeMultiply] := Sequence[]. I haven't explored this though.

Hope this helps! :)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.