8
$\begingroup$

Is there an elegant way to sort the arguments of Times in e.g. TraditionalForm so that factors go to the front and state variables to the back? Are there methods to prevent certain transformations, like moving everything into the numerator as in the example?

par = {p, k, Subscript[k, a], Subscript[k, b]};
expr = -A Subscript[k, b] B p k (1 - p) 1/(B + A) Subscript[k, a]

$-\frac{A \ B \ k \ (1-p) \ p \ k_a \ k_b}{A+B}$

What I would like to see is that symbols are ordered according to their appearance order in par, symbols not specified are sent to the back and fractions are not transformed at all, something like this:

$-(1-p) \ p \ k \ k_a \ k_b \ \frac{1}{A+B} \ A \ B$

There are many problems when manually sorting arguments of Times (here I only did a reversal for sake of simplicity):

expr /. Times[x_, y__] :> ((Inactive@Times) @@ Reverse[{x, y}])

$k_b*k_a*p*(1-p)*k*\frac{1}{A+B}*B*A*-1$

for example unnecessary *-s and numerical factors like -1 explicitly displayed.

$\endgroup$
  • $\begingroup$ Interesting question, but I have the feeling we had one like this before. Can't find it at the moment, though. $\endgroup$ – Sjoerd C. de Vries Jul 21 '15 at 14:02
  • $\begingroup$ @Sjoerd I had the same feeling, and you can imagine that I took the time to look for it. BTW, I find it very strange that a complex typesetting environment like Mathematica does not provide this functionality readily and one has to hack his way like jkuczm did... $\endgroup$ – István Zachar Jul 21 '15 at 18:35
  • $\begingroup$ Related: (9570) $\endgroup$ – Mr.Wizard Jul 21 '15 at 21:36
  • $\begingroup$ Thanks @Mr.Wizard, that is the one I was looking for. Accordingly, mine is a duplicate then. $\endgroup$ – István Zachar Jul 21 '15 at 21:59
  • 1
    $\begingroup$ @Mr.Wizard Wait! On second thought, I ask for more time to evaluate the differences. One of my concerns (not worded above) was that the output must be readily convertible by TeXForm and I'm not sure Row-s in Silvia's answer are ok with TeX. Will check it tomorrow all your answers, have to sleep now. $\endgroup$ – István Zachar Jul 21 '15 at 22:07
9
$\begingroup$

You can create your own ...Form wrapper that will format Times as you want it.

Let's start with ordering function that can be used in SortBy. It puts numeric coefficients in front, expressions present in par are ordered according to their position in par, all other expressions are moved to the end.

ClearAll[par, order]
par = {(1 - p), p, k, Subscript[k, a], Subscript[k, b]};
order[_?NumericQ] := 0
order[x_] := Replace[Position[par, x, {1}, 1], {{i_}} :> i]

Now a small helper "wrapper" that will vanish after conversion to boxes. It will also remove boxes resulting from HoldForm used directly inside wrapper.

ClearAll[vanishingWrapper]
MakeBoxes[vanishingWrapper[expr_], form_] ^:= MakeBoxes[expr, form];
MakeBoxes[vanishingWrapper[HoldForm[expr_]], form_] ^:= 
    First@MakeBoxes[HoldForm[expr], form];

Now the main ...Form function that, when converted to boxes, wraps some sub-expressions with our vanishingWrapper:

ClearAll[istvanForm]
MakeBoxes[istvanForm[expr_], _] ^:=
    With[
        {heldExpr =
            HoldComplete[expr] /. HoldPattern[Times][args__] :>
                With[
                    {eval =
                        With[
                            {heldArgs = 
                                Replace[
                                    SortBy[HoldComplete[args], order], 
                                    pow_Power :> vanishingWrapper[pow],
                                    {1}
                                ]}
                            ,
                            Function[Null,
                                vanishingWrapper@HoldForm@Times[##], 
                                HoldAllComplete
                            ] @@ heldArgs
                        ]}
                    ,
                    eval /; True
                ]}
        ,
        Function[Null, MakeBoxes[#, TraditionalForm], HoldAllComplete] @@
            heldExpr
    ]

When Times function is converted to TraditionalForm boxes, its arguments are sorted, unless Times is inside HoldForm. This sorting is done in addition to ordinary sorting, occurring due to Orderless attribute, when Times is evaluated. To prevent this additional sorting, istvanForm wraps Times with HoldForm and vanishingWrapper. So we get boxes coming from HoldForm[Times[...]], but thanks to vanishingWrapper all additional boxes from HoldForm are gone, so when we copy resulting expression there will be no HoldForm inside it.

When Power expression, with negative numeric exponent, is found directly inside Times, it triggers special formatting rules putting everything in one fraction. To prevent this special formatting, istvanForm wraps all Power expressions on first level of Times with vanishingWrapper. So that, when expression is converted to boxes, special formatting is not triggered.

To make istvanForm behave like proper ...Form wrapper we must add it to $OutputForms:

If[FreeQ[$OutputForms, #, {1}],
Unprotect@$OutputForms;
    PrependTo[$OutputForms, #];
Protect@$OutputForms;
]& @ istvanForm

Now let's see istvanForm in action:

-A Subscript[k, b] B p k (1 - p) 1/(B + A) Subscript[k, a] // istvanForm

istvanForm usage

$\endgroup$
  • $\begingroup$ The only occasion when a user-defined function should start with a capital letter! +1 : ) $\endgroup$ – István Zachar Jul 21 '15 at 18:34
  • $\begingroup$ @IstvánZachar I promise to capitalize your name next time I use it in a function ;) $\endgroup$ – jkuczm Jul 21 '15 at 19:48
2
$\begingroup$

I shall adapt my code from How do I reassign canonical ordering of symbols? with the addition of a rule for the Power[_, -1] form. I assume that any Variables not in par are to be placed at the end.

reorderIstván[expr_, par_List] :=
 Module[{h, rls},
   rls = MapIndexed[x : # :> h[#2, Replace[x, rls, -1]] &, 
     DeleteDuplicates @ Join[par, Variables @ expr] ];
   HoldForm @@ {expr /. rls} //. h[_, x_] :> x /. 
    x_^-1 :> RawBoxes @ FractionBox["1", ToBoxes[x]]
 ]

Now:

par = {p, k, Subscript[k, a], Subscript[k, b]};
expr = -A Subscript[k, b] B p k (1 - p) 1/(B + A) Subscript[k, a];

reorderIstván[expr, par]
-(1 - p) p k Subscript[k, a] Subscript[k, b] A B 1/(A + B)
reorderIstván[expr, par] // TeXForm

$-(1-p) p k k_a k_b A B \frac{1}{A+B}$

$\endgroup$
  • $\begingroup$ I got the time to advance with this problem. My issue with your approach is that due to the HoldForm-s, one cannot do post replacements like reorderIstván[expr, par] /. x_Symbol :> If[Context@x === "Global`", f@x, x]. I haven't figured a way to bypass this yet... any idea? I'd rather avoid including this transformation directly into the reordering function. $\endgroup$ – István Zachar Aug 17 '15 at 10:22
  • $\begingroup$ @IstvánZachar does reorderIstván[expr, par] /. x_Symbol :> RuleCondition@If[Context@x === "Global", f@x, x]` give what you want or are you looking for something else? (See mathematica.stackexchange.com/a/29319/121 if you are unfamiliar with RuleCondition.) $\endgroup$ – Mr.Wizard Aug 17 '15 at 11:08
  • $\begingroup$ Ok, RuleCondition did the job, thanks. I knew (and forgot) about it but never used before. $\endgroup$ – István Zachar Aug 17 '15 at 11:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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