22
$\begingroup$

Below is some code I am working on; being a beginner. How can I increase the readability of the part behind Manipulate?

I can't read my own code anymore, because it is simply too long and unordered. I don't know where a function ends and where the next one begins. What are tips to increase the readability of code? Are there some general principles or tricks in Mathematica I can use?

Please also share obvious tips, since I am just a beginner and still have to learn much...

f[y_] = 1/4 y (3 - y);
solution = DSolve[{y'[t] == f[y[t]], y[0] == c}, y[t], t][[1]];
ϕ[t_] = y[t] /. solution;

Manipulate[
 Row[{Show[
    VectorPlot[{1, f[y]}, {t, -8, 8}, {y, -4, 4},
     VectorStyle -> {Thick, Red}, VectorScale -> {.04, 1.5, None},
     VectorPoints -> 20],
    Plot[ϕ[t] /. c -> y0, {t, -8, b},
     PlotStyle -> {Thick, Blue}], Frame -> False, Axes -> True,
    AxesStyle -> Thick,
    AxesLabel -> {Text[Style["t", Black, Large, Italic]],
      Text[Style["y", Black, Large, Italic]]},
    TicksStyle -> {{Large, Black}, {Large, Black}},
    ImageSize -> Medium],
   Show[Plot[f[y], {y, -1, 4}, PlotStyle -> {Thick, Orange},
     AxesLabel -> {Text[Style["y", Large, Italic]],
       Text[Style["f(y)", Large, Italic]]}, ImageSize -> Medium],
    Graphics[{Red, PointSize[.03],
      Point[{ϕ[b] /. c -> y0, 0}]}]]}], {b, -7.99,
  8}, {{y0, 2}, -1, 4}, LabelStyle -> Large]

With special thanks to Bill Kinney: Using Mathematica for Ordinary Differential Equations (ODEs)

$\endgroup$
  • 1
    $\begingroup$ That is good question but it may be too "opinion based" for SE. You can take a look here: 89792 to see what I'm usually doing, also, writing in "Code" cells allows you to indent lines more consistently. $\endgroup$ – Kuba Jun 22 '16 at 11:23
  • $\begingroup$ It's hard to tell why you find the code unreadable without seeing how you formatted it originally. $\endgroup$ – Szabolcs Jun 22 '16 at 11:40
  • 1
    $\begingroup$ p.s. keep in mind that VectorPlot and the second Plot can be evaluated outside Manipulate and inserted in Show, otherwise you are recreating them each time. $\endgroup$ – Kuba Jun 22 '16 at 11:47
  • 2
    $\begingroup$ I agree with @kuba, but I think this is a very important question. In my experience, a major barrier on the Mathematica learning curve is code readability, which is further compounded by the automatic indenting/formatting performed by the Notebook front end. General code-readability strategies, resisting use of greek symbols and sub/superscripts are all topics that pop up here, but to my knowledge they have not been localized in one Q&A. $\endgroup$ – bobthechemist Jun 22 '16 at 12:00
  • 2
    $\begingroup$ related: Granular vs terse coding p.s. @ImreVégh to be more precise, it should be outside of manipulate body, unless you use nested Dynamic to prevent unnecessary evaluation. Moreover, completely outside may not be the best place because one may need to have this definition preserved across FE sessions. Possible solutions are: SaveDefinitions or defining it in Initialization or injecting that graphics with With. $\endgroup$ – Kuba Jun 22 '16 at 13:28
19
$\begingroup$

Description

In software engineering, it is a good practice to comment your code. I would advise you to utilise comments to partition your code in a following way. Additionally, if you have ever worked with any other programming languages, you could employ indentation. Alternatively, you could modularize your application as to develop it in smaller, more manageable "chunks". Please see sample below

Example #1 - Indentation + Comments (Full Program)

Code

f[y_] = 1/4 y (3 - y);
solution = DSolve[{y'[t] == f[y[t]], y[0] == 1}, y[t], t][[1]];
ϕ[t_] = y[t] /. solution;

Manipulate[
 Grid[
  Show[
   (*Show Component Declaration*)

   VectorPlot[
    (*Vector Plot Declaration*)
    {1, f[y]}, {t, -8, 8}, {y, -4, 4},
    (*Vector Plot Options*)
    VectorStyle -> {Thick, Red},
    VectorScale -> {.04, 1.5, None},
    VectorPoints -> 20
    ],

   Plot[
    (*Plot Declaration*)
    ϕ[t] /. c -> y0, {t, -8, b},
    (*Plot Option Declaration*)
    PlotStyle -> {Thick, Blue}
    ],

   (*Show Option Declaration*)
   Frame -> False,
   Axes -> True,
   AxesStyle -> Thick, 
   AxesLabel -> {Text[Style["t", Black, Large, Italic]], 
     Text[Style["y", Black, Large, Italic]]}, 
   TicksStyle -> {{Large, Black}, {Large, Black}}, ImageSize -> Medium
   ],

  Show[
   (*Show Component Declaration*)
   Plot[
    (*Plot Declaration*)
    f[y], {y, -1, 4},
    (*Plot Option Declaration*)
    PlotStyle -> {Thick, Orange}, 
    AxesLabel -> {Text[Style["y", Large, Italic]], 
      Text[Style["f(y)", Large, Italic]]}, ImageSize -> Medium],

   Graphics[
    (*Graphics Component Declaration*)
    {Red, PointSize[.03], Point[{ϕ[b] /. c -> y0, 0}]}
    ]
   ]
  ],
 (*Manipulate Control Declaration*)
 {b, -7.99, 8}, 
 {{y0, 2}, -1, 4},
 (*Manipulate Option Declaration*)
 LabelStyle -> Large
 ]

Example #2 - Indentation + Comments + Modularization (Sample)

Code

DynamicModule[
 {
  c, y, f,
  y0 = 2,
  b = 1,
  solution, \[Phi],
  plotVector, plotPlot
  },

 (*Input/Output Description*)
 f[y_] := 1/4 y (3 - y);

 (*Input/Output Description*)
 solution = DSolve[{y'[t] == f[y[t]], y[0] == c}, y[t], t][[1]];

 (*Input/Output Description*)
 \[Phi][t_] := y[t] /. solution;

 (*Main Column*)
 Panel @ Column[{
    (*Output Description*)
    Row @ {
      Dynamic @ Show[{
         (*Output Description*)
         plotVector = VectorPlot[{1, f[y]}, {t, -8, 8}, {y, -4, 4}, VectorStyle -> {Thick, Red}, VectorScale -> {.04, 1.5, None}, VectorPoints -> 20];
         (*Output Description*)
         plotPlot = Plot[\[Phi][t] /. c -> y0, {t, -8, b}, PlotStyle -> {Thick, Blue}];

         plotVector,
         plotPlot
         },
        (*Show Options*)
        Frame -> False, Axes -> True, AxesStyle -> Thick, 
        AxesLabel -> {Text[Style["t", Black, Large, Italic]], Text[Style["y", Black, Large, Italic]]}, TicksStyle -> {{Large, Black}, {Large, Black}}, 
        ImageSize -> Medium
        ]
      },

    (*Output Description*)
    Column @ {
      Row @ {Style["y0 ", 16], Slider[Dynamic @ y0, {-1, 4}]},
      Row @ {Style["b", 16], Slider[Dynamic @ b, { -7.99, 8}]}
      }
    },
   (*Main Column Options*)
   Alignment -> Center]
 ]

Notes

N.1 It is always a good idea to give your variables a descriptive name. Given that I am not aware what you are trying to solve, I left variable names as in OP and any additional variables been given dummy names which SHOULD be changed

N.2 I wanted to move both plotVector and plotPlot1 definition outside Show, but couldn't figure out how to introduce Dynamic updating of plotPlot1 in this configuration*)

$\endgroup$
  • $\begingroup$ I will edit this post in an hour or so $\endgroup$ – e.doroskevic Jun 22 '16 at 11:46
  • 6
    $\begingroup$ I wouldn't completely agree on your first statement: there is quite some debate about how much and which kind of comments are useful in code. To my understanding one of the current "mainstream" opinions to the subject is that your code should as much as possible document itself, no documentation is better than outdated documentation and you should not "state the obvious". Plese don't get it the wrong way, I like your style (less dense than most alternatives) but I find most of your comments to be of the "state the obvious" category which probably doesn't help to understand the code very much. $\endgroup$ – Albert Retey Jun 22 '16 at 13:20
  • $\begingroup$ Just to mention: I also completely agree on what you say about indentation and modularization... $\endgroup$ – Albert Retey Jun 22 '16 at 13:21
  • 1
    $\begingroup$ @AlbertRetey Also, perhaps, a better word for the comments above would be a "tag". I use them in such a way in order to identify the scope I work in $\endgroup$ – e.doroskevic Jun 22 '16 at 15:30
  • 2
    $\begingroup$ @AlbertRetey, I also would like to mention that if one really, really want to put in a comment (and one should try hard not to) then it should answer the question of 'why' something was done not 'what' was done - One has to assume that the reader of the code can read the language. A better alternative to comments are asserts - as they are part of the program. $\endgroup$ – user21 Jun 22 '16 at 21:24
19
$\begingroup$

I appear to be in the minority but I never write big blocks of Mathematica code, it's just too difficult to read. The way I look at it you have to consider how a reader will understand your app. So I make the main block very small, like this:

Manipulate[
 Row[{
   vectorPlotAndTrajectory[y0, b],
   Show[fy, position[y0, b]]
   }]
 , {b, -7.99, 8}
 , {{y0, 2}, -1, 4}
 , LabelStyle -> Large
 ]

The idea is that from this block the reader will be able to understand what the app is. There is a row with two figures, the left one is a vector plot with a trajectory and the right one is a function curve with something marked on it. If they want to understand further they can look up the individual definitions. So they go and look up vectorPlotAndTrajectory and they see

vectorPlotAndTrajectory[y0_, b_] := Show[
  vectorPlot, trajectory[y0, b]
  , Frame -> False, Axes -> True
  , AxesStyle -> Thick
  , AxesLabel -> {
    Text[Style["t", Black, Large, Italic]],
    Text[Style["y", Black, Large, Italic]]
    }
  , TicksStyle -> {{Large, Black}, {Large, Black}}
  , ImageSize -> Medium
  ]

Ah, so this is just the combination of a vector plot vectorPlot and a trajectory trajectory. So in order to understand even better they go to the definition of vectorPlot:

vectorPlot = VectorPlot[
   {1, f[y]}, {t, -8, 8}, {y, -4, 4}
   , VectorStyle -> {Thick, Red}
   , VectorScale -> {.04, 1.5, None}
   , VectorPoints -> 20
   ];

You get the idea. No one so far has suggested that you put the commas in the beginning of the line either, which I think really helps with the readability if there are three or more arguments.

If you really need it to be a single code block in the end there are meta programming techniques that can be used to generate such a code block; just because you need such an end result does not mean you have to look at the bestiality while developing the app. Just like when web developers write CSS they usually write it in LESS and the compile it to CSS, they don't have to look at the actual CSS!

$\endgroup$
  • 4
    $\begingroup$ Commas in the beginning also help to quickly comment in and out parts of the code on one line. $\endgroup$ – user21 Jun 22 '16 at 21:28
  • 8
    $\begingroup$ Commas at the beginning of the line makes me want to scratch my eyes out, but I can see the logic. $\endgroup$ – Simon Woods Jun 22 '16 at 21:40
  • 1
    $\begingroup$ If I run your code it doesn't work... $\endgroup$ – GambitSquared Jun 22 '16 at 21:44
  • 1
    $\begingroup$ @ImreVégh You don't have all the definitions, you don't have the definitions for trajectory, fy and position for example. It was not necessary to post the entire program to get my point across. It might be good practice to write the rest! $\endgroup$ – C. E. Jun 22 '16 at 21:47
  • 2
    $\begingroup$ @C.E. Incidentally I only saw this Q&A now but you basically wrote the answer I would have had I seen it sooner. $\endgroup$ – Mr.Wizard Jul 18 '16 at 12:57
13
$\begingroup$

Personally, I use lots of newlines and let the Front End indent. There doesn't seem to be anything special in your code, other than a lot of nesting that is in fact necessary in this case.

enter image description here

You were using Grid incorrectly. Grid[a,b] is wrong. Grid[{{a,b}}] or Grid[{{a},{b}}] are correct. I guess you wanted Column[{a,b}], so I changed that.

Using Text in AxesLabel is unusual and unnecessary. You can remove that.

$\endgroup$
7
$\begingroup$

Here is the way it would look if it were my code.My formatting style is much the same as Szabolcs' (including replacing your grid with a column), but I don't like his dangling ], and you won't see those. Also, I always start control specifications on a new line, and I mostly start option specifications on a new line, too.

code

Note: I have redefined ϕ to accept a parameter c. This is to make sense of your 2nd control y0, but I am not sure I have got it right. That is, I am not entirely sure my c is the parameter you intended to vary with the y0 control.

$\endgroup$
3
$\begingroup$

Is this a "standard" Manipulate which will not grow larger? Ok, use one of already posted answers.

Here is an alternative in case where Manipulate is supposed to be generated from a package function or is a part of a bigger code.

ClearAll["Test`*"];

BeginPackage["Test`"]

    myManipulate::usage = "myManipulate[] generates a demo Manipulate";

Begin["`Private`"]

    myManipulate[]:= Panel @ DynamicModule[{ϕ, b = -.7, y0 = 2}, 
        fiGeneration[ϕ, 2];
        Column[{
            Row[{ leftShow[b, ϕ], rightShow[b, ϕ] }], 

            LabeledSlider[Dynamic[b], {-7.99, 8}], 
            LabeledSlider[Dynamic[y0, (y0=#;fiGeneration[ϕ, y0]) &], {-1, 4}]
        }]
    ];

    SetAttributes[fiGeneration, HoldFirst];
    fiGeneration[ϕ_, y0_]:= Block[{solution, t, y},
        solution =  DSolve[{y'[t] == f[y[t]], y[0] == y0}, y[t], t][[1]];
        ϕ[t_] = y[t] /. solution;

    ];

    SetAttributes[{leftShow,rightShow}, HoldAll];

    leftShow[b_, ϕ_]:= With[{
        vectorPlot = fVectorPlot[]
        },
        Dynamic @ Show[
            vectorPlot , 
            fiPlot[b, ϕ] , 
            Frame -> False, Axes -> True, AxesStyle -> Thick, 
            AxesLabel -> {
                Text[Style["t", Black, Large, Italic]], 
                Text[Style["y", Black, Large, Italic]]
            }, 
            TicksStyle -> {{Large, Black}, {Large, Black}}, 
            ImageSize -> Medium, Background->White
    ]];

    SetAttributes[fiPlot,HoldAll];
    fiPlot[b_, ϕ_]:=Plot[ϕ[t] , {t, -8, b}, PlotStyle -> {Thick, Blue}];



    rightShow[b_, ϕ_]:= With[{plot =  fPlot[]},
        Show[
            plot, 
            Graphics[{Red, PointSize[.03], Dynamic @ Point[{ϕ[b], 0}]}]
        ]
    ];

    f[y_]:= 1/4 y (3 - y);

    fPlot[]:=Plot[ f[y], {y, -1, 4}, 
        PlotStyle -> {Thick, Orange},   
        AxesLabel -> {
            Text[Style["y", Large, Italic]], 
            Text[Style["f(y)", Large, Italic]]
        }, 
        ImageSize -> Medium
    ];

    fVectorPlot[]:=VectorPlot[
        {1, f[y]}, {t, -8, 8}, {y, -4, 4}, 
        VectorStyle -> {Thick, Red}, 
        VectorScale -> {.04, 1.5, None}, 
        VectorPoints -> 20
    ];



End[];
EndPackage[];

I can create two instances of myManipulate[] and they won't interfere even though they share definitions of leftShow etc. That's because what is important is scoped in DynamicModule:

myManipulate[]

myManipulate[]

enter image description here

It's not better approach but sometimes it is useful. You may want to add SaveDefinitions because some of definitions are not going to be stored. But I didn't because the package may be loaded by Initialization preventing duplication of definitions in many instances of DynamicModule. What to do really depends of the case.

$\endgroup$

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.