14
$\begingroup$

NOTE: The finished code for this project can be found here, under "UPDATE": How do I alter this \$PreRead + \$PrePrint statement so it can be selectively deactivated?

SUMMARY

I'd like to get MMA to automatically print both input and output on the same line, in TraditionalForm, without having to add any extra syntax. Further, I'd like the output to be fully evaluate-able. For example:

int=Integrate[x^2,x]

$\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

int/x  

$\frac{\text{int}}{x}=\frac{x^2}{3}$

EDIT: I've been asked to edit my question to explain how it's different from How to show formula and substitute variable values?. So: The question here is not simply how to get input and output on the same line; rather, it's how to do this without needing to use any extra syntax, i.e., how to automate this functionality. See, for example, Partial Solution 1, below, which requires no extra syntax (except for a single line of code evaluated at the start of the notebook). Partial Solution 2 requires minimal extra syntax (a semicolon at the end of each input, followed by a single command), but even that's more than I want to have to do.

MOTIVATION

I want to automate the production of notebooks that are easily-readable by students and colleagues not familiar with MMA. Once the above is implemented, I would use @R. M.'s neat code-hiding palette (see Is there a way to hide or toggle the visibility of code?) to get rid of the input code, leaving me with just this:

$\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

$\frac{\text{int}}{x}=\frac{x^2}{3}$

Note the purpose here is not to produce publication-quality notebooks, but rather to minimize the effort I need to expend to create easily-readable output.

Aside: Once I get this working, I will then want to figure out a way to merge text, input, and output into a single line (which I understand can be easily done in Maple). I'll save this for a separate question, but wanted to mention it here to give you an overall picture of what I'm trying to accomplish. For example, it would be wonderful if, using input as simple as this, I could get the following output: [UPDATE: We have now accomplished this; see Combined inline printing of input, output, and text, w/ minimal added syntax ]

""We find that"" Integrate[x^2,x] "", as expected.""

$\text{We find that }\int x^2 \, dx=\frac{x^3}{3}\text{, as expected.}$

I currently have two partial solutions. Both are close, but require expertise I don't have to be brought to completion.

PARTIAL SOLUTION 1

This uses a very cool line of code that @Simon Rochester created in reply to a related question I'd asked at Want to return name of last defined variable without explicitly naming it:

$PrePrint = (TraditionalForm@HoldForm[In[line] = #] /. line -> $Line /. DownValues[In]) &;

Here are examples of the output one gets after activating it:

Integrate[x^2, x]

$\int x^2 \, dx=\frac{x^3}{3}$

int=Integrate[x^2, x]

$\left(\text{int}=\int x^2 \, dx\right)=\frac{x^3}{3}$

int/x

$\frac{\text{int}}{x}=\frac{x^2}{3}$

%/x

$\frac{\%}{x}=\frac{x}{3}$

a = 2

$(a=2)=2$

g[x_]:=Sin[x]

[no output]

g[Pi]

$g(\pi )= 0$

This code has the advantage that I only need to activate it once, at the beginning of each notebook; no extra syntax is needed. And all its output is evaluate-able. However, there are three aspects of the output I'd like to change: (1) if there is an assignment, it adds an extra set of parentheses; (2) if the evaluation doesn't transform the input, one ends up with a redundancy in the output; and (3) with delayed assignments, there is no output at all (i.e., it doesn't return the input; contrast this with Partial Solution 2, below).

I don't understand the code, and thus lack the expertise to modify it. The two key mysteries for me are the $PrePrint command (which remains confusing even after reading the MMA documentation, as well as the references to it on MMA Stack Exchange), and the two concatenated ReplaceAll commands (which use only one arrow). I tried expanding the code using FullForm, but I'm still at loss -- if someone could help me understand it better, that would be great:

CompoundExpression
[
    Set
    [
        $PrePrint,Function
        [
            ReplaceAll
            [
                ReplaceAll
                [
                    TraditionalForm
                    [
                        HoldForm					
                        [
                            Set
                            [
                                In[line],Slot[1]
                            ]
                        ]
                    ]
                    , Rule[line,$Line]
                ]
                ,DownValues[In]
            ]
        ]
    ]
    ,Null
]
=Null

PARTIAL SOLUTION 2

This is my own attempt, which incorporates the DownValues[In] command @MichaelE2 used in his answer to the question mentioned above (Want to return name of last defined variable without explicitly naming it):

prettyPrint := Quiet@Block[
{parent = Extract[DownValues[In], {-2, 2}, HoldForm], 
daughter1 = Extract[DownValues[In], {-2, 2, 1}, HoldForm], 
daughter2 = Extract[DownValues[In], {-2, 2, 1}]},
If[
(! StringContainsQ[ToString@daughter1, 
    "=="]) &&
 ((! (Depth[parent] < 5 && 
        StringContainsQ[ToString@parent, "["])
     && (Depth[parent] < 5 || 
       StringContainsQ[ToString@parent, ":="]) 
    || 
    (StringMatchQ[
       ToString@Extract[DownValues[In], {-2, 2, 1, 2}, HoldForm], 
       ToString@Extract[DownValues[In], {-2, 2, 1, 2}]] && 
      StringContainsQ[ToString@daughter1, "="]))),    
Print[daughter1],    
Print[daughter1, " = " , daughter2]]]

And here's what it produces:

Integrate[x^2, x];
prettyPrint[];

$\int x^2 \, dx=\frac{x^3}{3}$

int=Integrate[x^2, x];
prettyPrint[];

$\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

int/x;
prettyPrint[];

$\frac{\text{int}}{x}=\frac{x^2}{3}$

%/x;
prettyPrint[];

$\frac{\%}{x}=\frac{\text{Null}[]}{x^2}$

a = 2;
prettyPrint[];

$a=2$

g[x_]:=Sin[x];
prettyPrint[];

$g(\text{x$\_$}):=\sin (x)$

g[Pi];
prettyPrint[];

$g(\pi )= 0$

[Note that the the output is in TraditionalForm because I've been able to accomplish this by setting output to TraditionalForm in Preferences->Evaluation, not because TraditionalForm is called in the code block.]

This code eliminates the extra parentheses and redundant outputs seen in Partial Solution 1, and properly reproduces delayed evaluations. However, it has two problems. First, it's much more cumbersome to use, requiring that it be called for each input. Second, I don't believe it has the generality of Simon's code, since here I've taken a brute-force approach, manually inspecting the forms of DownValues[In] associated with various commands, and using logic statements to distinguish between those that need to be handled one way vs. another. Thus it fails for certain types of inputs, e.g., those that use "%". The latter is not a significant issue in this application, since it's unlikely I would use % in such a notebook (it makes it harder for a non-MMA user to follow); in addition, I could probably correct this specific flaw (perhaps using DownValues[Out]). Nevertheless, it's an indicator that there could be other inputs on which it might also fail.

If you'd like to see what this code does with some other inputs, evaluate prettyPrint[], above, and then evaluate this cell:

b;
prettyPrint[];
a = 2 + 2 + 7 + 5;
prettyPrint[];
Length[{x1, y1, z1}];
prettyPrint[];
matrix1 = {{f1, f2}, {g1, g2}};
prettyPrint[];
matrix2 = Table[100 i + 10 j + k, {i, 3}, {j, 2}, {k, 4}];
prettyPrint[];
Length[matrix2];
prettyPrint[];
Solve[a x + y == 7 && b x - y == 1, {x, y}];
prettyPrint[];
truncatedQ = 
Sum[Exp[-\[HBar]*\[Omega]/(2*k*T)]*
Exp[-j*\[HBar]*\[Omega]/(k*T)], {j, 0, n}];
prettyPrint[];
rotatingWaveApprox = 
DSolve[{I*h*a2'[t] == 
  a1[t]*\[Alpha]/2*Exp[-I*(\[Omega] - \[Omega]21)*t], 
 I*h*a1'[t] == 
  a2[t]*\[Alpha]/2*Exp[I*(\[Omega] - \[Omega]21)*t]}, {a1[t], 
 a2[t]}, t] // FullSimplify;
prettyPrint[];

REQUESTS

Could you either (a) show me how to modify Partial Solution 1 to eliminate the extra parentheses and redundant output, and show delayed evaluations, or (b) show me how to incorporate Partial Solution 2 into a $PrePrint statement (so I wouldn't have to call it for every input)?

$\endgroup$
5
  • $\begingroup$ When I did this sort of thing before, I just used strings. Like this: Clear[x]; c = HoldForm@Integrate[x^2, x]; str = "We find that " <> ToString[TraditionalForm@c] <> " = " <> ToString@ReleaseHold[TraditionalForm@c] Here is screen shot !Mathematica graphics $\endgroup$
    – Nasser
    Commented Dec 29, 2016 at 1:21
  • $\begingroup$ Thanks @Nasser, I wasn't aware of the the ReleaseHold command. Alas, however, this is exactly the sort of laborious input syntax I am trying to avoid, especially when the notebook has many input lines. Plus I'd have to add more syntax to obtain evaluate-able output (needed for intermediate calculations), e.g., c1=ReleaseHold[c];. But maybe this could be applied to gain a solution to the 2nd part of this, which I'll be addressing as a separate question (see "Aside"). $\endgroup$
    – theorist
    Commented Dec 29, 2016 at 1:51
  • 1
    $\begingroup$ Possible duplicate of How to show formula and substitute variable values? $\endgroup$
    – Edmund
    Commented Dec 29, 2016 at 1:56
  • $\begingroup$ @Nasser: That sounds very intriguing! I'm familiar with LaTeX (I use TeXShop for the Mac). How do you do this? $\endgroup$
    – theorist
    Commented Dec 29, 2016 at 1:57
  • $\begingroup$ @Edmund: Thanks for the link but, as I mentioned to Nasser, the question is not how to do this, it's how to do this without any extra syntax (e.g., Partial Solution 1), or minimal extra syntax (Partial Solution 2). The example you linked works fine for one or two lines, but not (IME) for easily producing a notebook with many lines. $\endgroup$
    – theorist
    Commented Dec 29, 2016 at 2:33

2 Answers 2

8
$\begingroup$

You can fix up Partial Solution 1 with your additional requirements with

$PrePrint = (TraditionalForm@HoldForm[In[line] = #] /. line -> $Line /. DownValues[In] /.
     {HoldPattern[a_ = a_] :> a,
      HoldPattern[a_ = HoldForm[a_]] :> a,
      HoldPattern[(c : (a_ = b_)) = b_] :> c,
      HoldPattern[(a_ = b_) = c_] :> HoldForm[a = b = c]
      }
    ) &;

$Post = (Replace[#, Null -> HoldForm[In[line]] /. line -> $Line /. DownValues[In]]) &;

The first three additional replacement rules in $PrePrint address redundancy in the output, the fourth replacement rule removes the unwanted parentheses, and the $Post command makes it so there is always output, even if there normally would be none.

Now you have

Integrate[x^2, x]

$\int x^2 \, dx=\frac{x^3}{3}$

int = Integrate[x^2, x]

$\text{int}=\int x^2 \, dx=\frac{x^3}{3}$

int/x

$\frac{\text{int}}{x}=\frac{x^2}{3}$

%/x

$\frac{\%}{x}=\frac{x}{3}$

a = 2

$a=2$

g[x_] := Sin[x]

$g(x\_):=\sin(x)$

g[Pi]

$g(\pi)=0$

Note that (a = b) = c has a different meaning than a = b = c -- the latter first assigns c to b, and then assigns b to a, while the former first assigns b to a, and then c to a. That's why the parentheses are put there in the first place. But that should only be an issue for you if you try to copy and paste and then re-evaluate the input.

Some explanations of the code:

Mathematica stores all of the previously entered input in the function In, as values for In[1], In[2], etc. You can get a list of all of these values as a list of replacement rules with Downvalues[In]. Try evaluating Downvalues[In] by itself (after doing some other calculations) to see. So Downvalues[In] already has the correct syntax to be used in a replacement operation.

$PrePrint and $Post are similar -- they both apply a function to every output expression. The main difference between them is that $Post is applied before the value of the output is assigned to the Out variable, and $Preprint is applied after. You can see this in action with (in a freshly started kernel):

$PrePrint = f
f[f] 
a
f[a]
$Post = g
f[g[g]]
a
f[g[a]]
$PrePrint =.
g[Null]
$Post =.

DownValues[Out]
{HoldPattern[%1] :> f, HoldPattern[%2] :> a, HoldPattern[%3] :> g[g], 
 HoldPattern[%4] :> g[a], HoldPattern[%5] :> g[Null], HoldPattern[%6] :> Null}

Pondering on that sequence of input and output should help you understand $PrePrint and $Post. Note that $Post is applied before $PrePrint, and the effects of $Post show up in the downvalues for Out, while the effects of $PrePrint do not.

$\endgroup$
1
  • $\begingroup$ Beautiful! I'll need some time to study this, and will post a follow-up comment if I have further questions. In the meantime, what about the three successive replacements -- why do you only need one arrow instead of three, and why didn't you need to specify what was being replaced after the first instance? For successive replacements, the only syntax with which I'm familiar is: a /. a -> b /. b -> c /. c -> d, but it seems as if you've done something like a /. a -> b /. c /. d, which normally doesn't work, but does work in your code. $\endgroup$
    – theorist
    Commented Dec 29, 2016 at 4:57
5
$\begingroup$

This solution does most of what your are looking for, producing stylized output much like what @Simon Rochester's answer shows. Additionally, this writes to a new notebook window:

$Pre=.; 

If[Notebooks["Output"]=={}, nb= CreateDocument[ Null, {
        WindowSize->Medium, WindowTitle->"Output", WindowMargins->Automatic, 
        StyleDefinitions-> FrontEnd`FileName[{"Report"},"StandardReport.nb",CharacterEncoding->"UTF-8"]
    }] ]; 

Clear[fPrint]; 
SetAttributes[fPrint, HoldAll]; 

fPrint[expr_]:= Block[ { exprPrint, exprHold=HoldForm[expr] },
    NotebookWrite[nb, SelectionMove[nb, Next, Cell]
    ; CellGroupData[
        {
        Cell[BoxData[ToBoxes[HoldForm[expr], StandardForm]], "Input"], 
            ( 
             exprPrint = If[
                MatchQ[exprHold, HoldForm[Set[x_,y_]]/;AtomQ[y]]
                , exprHold
                , exprHold == expr ]  
             ; Cell[BoxData[ToBoxes[exprPrint, TraditionalForm]], "Output", Editable->True] 
            )
        }
        , Open], AutoScroll-> False] 
        ; expr
    ] // Quiet;

$Pre = fPrint; 
$\endgroup$
3
  • 1
    $\begingroup$ Neat! Though it doesn't seem to be workable for me at this point, since: (1) if I edit code in my original notebook and reevaluate the cell, it doesn't change the output in the new notebook, but instead generates a second output there; and (2) it doesn't transfer text cells. I could possibly deal w/ #2 by putting the text into the output document, but #1 precludes that because I would need to delete the output document and re-output it after making any changes, so my text would be lost (and might be lost anyways if I closed the original and then opened and revised it). $\endgroup$
    – theorist
    Commented Dec 30, 2016 at 1:05
  • $\begingroup$ The other issues, which you're probably aware of, are that: (1) when there's an assignment, it adds that extra set of parentheses; and (2) when there's a delayed assignment, it appends an additional = Null to the end. $\endgroup$
    – theorist
    Commented Dec 30, 2016 at 1:06
  • $\begingroup$ @theorist– Yup, you're right... there's no linkage between the source notebook and the Output notebook, so edits to the source notebook's input cells, instead of updating the output window, produce new cells. That's a feature ;-) And as you noted, non-evaluation cells, (e.g., text style cells) will not be transferred because they do not produce output. Some clever coding could work around that problem. $\endgroup$
    – Wodahs
    Commented Dec 30, 2016 at 2:03

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.