96
$\begingroup$

WReach has presented here a nice way to represent the Mathematica's evaluation sequence using OpenerView. It is much more clear way to go than using the standard Trace or TracePrint commands. But it could be improved further.

I need straightforward way to represent the real sequence of (sub)evaluations inside Mathematica's main loop for beginners. In particular, it should be obvious when new evaluation subsequence begins and from which expression (it is better to have each subsequence exactly in one Opener). The evaluation (sub)sequence should be identified as easily as possible with the standard evaluation sequence. I mean that the reader should be able to map real evaluation step to one described in the Documentation for the standard evaluation sequence.

Is it possible?

$\endgroup$
1
  • 1
    $\begingroup$ Note the clearest, but with the benefit of not using the front-end, you can TraceToFile. $\endgroup$
    – rhermans
    Commented Feb 13, 2023 at 10:58

6 Answers 6

89
$\begingroup$

The cited OpenerView solution used Trace / TraceOriginal to generate its content. This allowed the definition of show in that response to be defined succinctly, but had the disadvantage of discarding some of the trace information. TraceScan provides more information since it calls a user-specified function at the start and end of every evaluation.

Two functions are defined below that try to format the TraceScan information in (somewhat) readable form.

traceView2 shows each expression as it is evaluated, along with the subevaluations ("steps") that lead to the result of that evaluation. "Drill-down" is provided by OpenerView. The function generates output that looks like this:

traceView2[(a + 1) + 2]

traceView2 screenshot

As one drills deeper into the view, it rapidly crawls off the right-hand side of the page. traceView4 provides an alternative view that does not exhibit the crawling behaviour at the expense of showing much less context for any given evaluation:

traceView4 screenshot

Choose your poison ;)

The definitions of the functions follow...

traceView2

ClearAll@traceView2
traceView2[expr_] :=
  Module[{steps = {}, stack = {}, pre, post, show, dynamic},
    pre[e_] := (stack = {steps, stack}; steps = {})
  ; post[e_, r_] :=
      ( steps = First@stack ~Join~ {show[e, HoldForm[r], steps]}
      ; stack = stack[[2]]
      )
  ; SetAttributes[post, HoldAllComplete]
  ; show[e_, r_, steps_] :=
      Grid[
        steps /. {
          {} -> {{"Expr  ", Row[{e, " ", Style["inert", {Italic, Small}]}]}}
        , _ -> { {"Expr  ", e}
               , {"Steps", steps /.
                   { {} -> Style["no definitions apply", Italic]
                   , _ :> OpenerView[{Length@steps, dynamic@Column[steps]}]}
                 }
               , {"Result", r}
               }
        }
      , Alignment -> Left
      , Frame -> All
      , Background -> {{LightCyan}, None}
      ]
  ; TraceScan[pre, expr, ___, post]
  ; Deploy @ Pane[steps[[1]] /. dynamic -> Dynamic, ImageSize -> 10000]
  ]
SetAttributes[traceView2, {HoldAllComplete}]

traceView4

ClearAll@traceView4
traceView4[expr_] :=
  Module[{steps = {}, stack = {}, pre, post},
    pre[e_] := (stack = {steps, stack}; steps = {})
  ; post[e_, r_] :=
      ( steps = First@stack ~Join~ {{e, steps, HoldForm[r]}}
      ; stack = stack[[2]]
      )
  ; SetAttributes[post, HoldAllComplete]
  ; TraceScan[pre, expr, ___, post]
  ; DynamicModule[{focus, show, substep, enter, exit}
    , focus = steps
    ; substep[{e_, {}, _}, _] := {Null, e, Style["inert", {Italic, Small}]}
    ; substep[{e_, _, r_}, p_] :=
        { Button[Style["show", Small], enter[p]]
        , e
        , Style[Row[{"-> ", r}], Small]
        }
    ; enter[{p_}] := PrependTo[focus, focus[[1, 2, p]]]
    ; exit[] := focus = Drop[focus, 1]
    ; show[{e_, s_, r_}] :=
       Column[
         { Grid[
             { {"Expression", Column@Reverse@focus[[All, 1]]}
             , { Column[
                   { "Steps"
                   , focus /.
                       { {_} :> Sequence[]
                       , _ :> Button["Back", exit[], ImageSize -> Automatic]
                       }
                   }
                 ]
               , Grid[MapIndexed[substep, s], Alignment -> Left]
               }
             , {"Result", Column@focus[[All, 3]]}
             }
           , Alignment -> Left, Frame -> All, Background -> {{LightCyan}}
           ]
         }
       ]
    ; Dynamic @ show @ focus[[1]]
    ]
  ]
SetAttributes[traceView4, {HoldAllComplete}]
$\endgroup$
16
  • 2
    $\begingroup$ Very nice! Now the sequence of evaluations is really visually clear! One thing that Trace* commands totally loose is applying the Orderless attribute. I was amazed detecting that Plus[a, 1] in really is evaluated again in the form Plus[1, a] after applying the Orderless attribute! It seems to contradict the standard evaluation sequence description. $\endgroup$ Commented Apr 3, 2011 at 7:39
  • $\begingroup$ Nifty. Now code something similar for XML... (if you have nothing better to do) :) +1. $\endgroup$ Commented Apr 3, 2011 at 10:27
  • $\begingroup$ Could you please explain why you only apply Dynamic at the end in traceView2, by substituting every dynamic with Dynamic? For performance reasons? $\endgroup$ Commented Mar 9, 2012 at 1:47
  • $\begingroup$ @IstvánZachar The dynamic substitution is unnecessary -- it is a leftover piece of code from a previous version of the function. It could even be considered harmful as it is preferable to defer the creation of the inner OpenerViews. I'd fix it, but I notice that these functions now perform very poorly for complex traces in versions of Mathematica released since I wrote this response. It seems that some relevant performance characteristic changed in a later microrevision of V7 (possibly V7.0.1). These functions need to be revisited (but I cannot do that right at the moment). $\endgroup$
    – WReach
    Commented Mar 10, 2012 at 18:36
  • 8
    $\begingroup$ Why don't you put this code on GitHub? Right now people may be copying this code from here, and modifying it for their personal use. But the improvements never make it back here for the whole community's benefit. GitHub would encourage people to either contribute the changes back, or at least would make it easier to track down modified version. Why GitHub and not another site? Because GitHub allows people to edit the source in a browser without even needing to install git. This can actually work with short functions like this. $\endgroup$
    – Szabolcs
    Commented Mar 5, 2013 at 18:29
23
$\begingroup$

Just an update on WReach's extremely useful traceView function: more compact view, larger buttons for opening/collapsing hierarchy, saves button-states as well.

ClearAll[traceViewCompact];
SetAttributes[traceViewCompact, {HoldAllComplete}];
traceViewCompact[expr_] := 
  Module[{steps = {}, stack = {}, pre, post, show, default = False},
   pre[e_] := (stack = {steps, stack}; steps = {});
   post[e_, 
     r_] := (steps = First@stack~Join~{show[e, HoldForm@r, steps]}; 
     stack = stack[[2]]);
   SetAttributes[post, HoldAllComplete]; 
   show[e_, r_, steps_] := Module[{open = False},
     Grid[
      steps /. {{} -> {{"Expr  ", 
           Item[e, Background -> [email protected]]}}, _ -> {{"Expr  ", 
           e}, {Toggler[
            Dynamic@
             open, {True -> 
              Button["Steps", Appearance -> {"DialogBox", "Pressed"}],
              False -> Button@"Steps"}], 
           steps /. {{} -> Style["no definitions apply", Italic], _ :>
               Dynamic@
               If[open, Column@steps, 
                Grid@{{Length@steps, "steps"}}]}}, {"Result", r}}}, 
      Alignment -> {Left, Center}, Frame -> All, 
      Spacings -> Automatic, Background -> {{Hue[.65, .1, 1]}, None}]
     ];
   TraceScan[pre, expr, ___, post];
   Deploy@Column@{
      Opener@Dynamic@default,
      Dynamic@Pane[First@steps, ImageSize -> 10000]
      }];


traceViewCompact[(a + 1) + 2]

Mathematica graphics

$\endgroup$
3
  • $\begingroup$ In v10.3, it is easily get frozen and break down mma. You can try to trace this function mathematica.stackexchange.com/a/80173/4742 $\endgroup$
    – matheorem
    Commented Oct 29, 2015 at 1:05
  • 2
    $\begingroup$ To be honest, I've never really used these tracers, not even mine. Does the original traceView of WReach (above) work fine in v10.3? If so, I recommend using that, as I sadly don't have the time to debug debuggers. Could be an awfully recursive problem : ) $\endgroup$ Commented Oct 29, 2015 at 9:09
  • $\begingroup$ Ok, I understand. WReach's function works fine in 10.3 $\endgroup$
    – matheorem
    Commented Oct 29, 2015 at 10:18
13
$\begingroup$

Here's my approach, also based on WReach's OpenerView technique. Its layout is much more compact, though less explicit, than his traceView2, and as far as I can tell, the only information sacrificed is the display of the number of steps hidden inside each OpenerView. Expressions that are unchanged by evaluation are indicated by a disabled OpenerView, though the screenshot that I made doesn't show a difference between a disabled and enabled OpenerView.

SetAttributes[TraceView, HoldFirst]

TraceView[e_, s___, opts : OptionsPattern[Trace]] := 
 Module[{show2},
  show2[{expr_, steps__}] := 
  OpenerView[{expr, Column[show2 /@ {steps}]}]; 
  show2[{HoldForm[x_]}] := Row[{Opener[True, Enabled -> False], HoldForm[x]}]; 
  show2[HoldForm[x_]] := HoldForm[x];
  show2[Trace[Unevaluated[e], s, opts, TraceOriginal -> True]]
 ]

TraceView output

$\endgroup$
2
  • $\begingroup$ It is interesting to note that outputs of TraceScan and Trace with TraceOriginal -> True slightly differs. In the second case evaluation chain for (a+1) is shown with additional step "a+1" before step "1+a". I do not understand what this additional step does represent. Have anybody any ideas? $\endgroup$ Commented Apr 6, 2011 at 5:23
  • 1
    $\begingroup$ I have answered this question in separate thread. $\endgroup$ Commented Mar 20, 2012 at 8:07
8
$\begingroup$

A bit late to the party, but there is now ResourceFunction["TraceView"]. Using the same example as shown in the other questions:

enter image description here

Here I'm showing an example of navigating through a slightly more complex expression:

enter image description here

This version has several advantages/differences compared to the others:

  • Can be easily accessed via ResourceFunction["TraceView"], and is fully documented.
  • It supports all options & pattern filters supported by TraceScan
  • It reduces the clutter by omitting trivial steps where nothing happens (this can be controlled using the "ShowAllSteps" option)
  • The main options can be changed on-the-fly using the toolbar
  • The toolbar also contains a search field, allowing the trace to be quickly searched for a given expression
  • The expressions are shown as they would in normal cells: The inputs are in syntax highlighted InputForm, the outputs in StandardForm
  • The trace also contains timing information, allowing you to track down slow steps (keep in mind that there is some overhead, especially for many fast steps).
  • It is designed to stay limited in size by showing only a certain number of levels at once, and by only showing a limited number of sub-steps at once (both limits can be configured via the "MaxDisplayDepth" and "MaxChildDisplay" options, respectively).
  • It can handle (extremely) large traces without impacting the front-end. This is achieved by limiting the number of expressions shown, and by eliding parts of complex expressions (as can be seen above, the user can manually drill down into elided expressions, there is also the "DefaultExpressionSizeLimit" option). For example, traceView4[Plot[x, {x, 0, 1}]] takes around 30s to display, and each click will take another 30s. ResourceFunction["TraceView"][Plot[x, {x, 0, 1}]] takes around 2s, and navigating also takes ~1-2s per click.
$\endgroup$
2
  • 1
    $\begingroup$ Thanks for this. Very nice. It can also trace functions. Screen shot !Mathematica graphics This will a very useful tool for debugging ! $\endgroup$
    – Nasser
    Commented Jan 17, 2023 at 23:20
  • $\begingroup$ Very nice, Indeed! Thanks for this useful tool, Lukas Lang :-) $\endgroup$ Commented Jan 18, 2023 at 2:27
5
$\begingroup$

Here is another implementation of joebolte's TraceView using the new Tree functionality in 12.3:

SetAttributes[TraceView, HoldFirst];
TraceView[expr_] := Module[{trace, f, h, tree},
  trace = Trace[expr, TraceOriginal -> True];
  f[steps_List] := Rest[steps];
  f[step_] := {};
  h[steps_List] := First[steps];
  h[step_] := step;
  tree = NestTree[f, trace, Infinity, h];
  TreeOutline[tree]]
$\endgroup$
1
$\begingroup$

TL;DR: The purpose of the code below is to show only one part of the evaluation sequence (only one subtree) at a time. As a result, the potentially very deep hierarchy in an evaluation does not result in a complex structure of nested parts. Instead, the hierarchy is summarized into a separate row of past evaluations (past nodes in the evaluation tree) which are known as breadcrumbs in tree navigation.


Note: animations shown below in the section: Explanation


As shown by @IanFord, finding the clearest way to visualize the evaluation sequence is equivalent to the clearest way of visualizing a tree with expressions as nodes.

There are many ways to visualize a tree as shown here: https://ux.stackexchange.com/q/18991/165903 and https://ux.stackexchange.com/q/2317.

Below I follow the drill-down and breadcrumb design that has the following benefit:

It focuses on a particular subtree at a time thereby avoiding scrolling and filling the page with a very deep and complicated hierarchical structure.

Here is an example of the drill-down and breadcrumb design:

Description of image below: The rectangle at the bottom shows a subtree in an evaluation (will be explained more later). The row of cells at the top shows clickable button breadcrumbs that represent the path of tree nodes from the evaluation tree's root to the current subtree and allows navigating back to a precedent level in the tree. The key point is that only one subtree is shown rather than the entire hierarchy. The hierarchy is summarized into breadcrumbs. Further details are given later.

enter image description here

The equivalent path using @WReach's traceView2:

enter image description here

My main motivation was to avoid forcing the front end to display very complex structures when the expressions are very long, as this seems to cause the front end to get stuck.

Code

A broad explanation is given in the next section

Note: ⎵=\[UnderBracket]

Note: the code below defines global variables which may conflict with user-defined variables:

  • original⎵tree
  • bread⎵crumbs
  • displayed⎵tree

Note: The functions that output a tree have the same options as Tree.

Creating a tree from the evaluation sequence

This is a modification of WReach's TraceView2 function to create a tree.

ClearAll[Trace⎵Tree];

Options[Trace⎵Tree]=Options[Tree];

Trace⎵Tree[expr_,opts:OptionsPattern[]]:=

Module[{steps={},stack={},pre,post,show,dynamic},

pre[e_]:=(stack={steps,stack};steps={});

post[e_,r_]:=
(steps=First@stack~Join~{show[e,HoldForm[r],steps]}
;
stack=stack[[2]]);

SetAttributes[post,HoldAllComplete];

show[e_,r_,steps_]:=
steps/. 
    {{}->(Grid[{{"Expr",Row[{e," ",Style["inert",{Italic,Small}]}]}},Frame->All]->{})
    ,
    _->(
    Grid[{{"Expr",e},{"Result",r}},Frame->All]->
            (
            steps/.{{}->Style["no definitions apply"
                                    ,Italic]
                    ,
                    _:>steps
                    }
            )
    )
    }
;

TraceScan[pre,expr,___,post];

RulesTree[steps[[1]],opts,Sequence@@Options[Trace⎵Tree]]
];
SetOptions[Trace⎵Tree
        ,
        ImageSize->Full
        ,
        MaxDisplayedChildren->Infinity
        ,
        TreeLayout->Right];
SetAttributes[Trace⎵Tree,{HoldAllComplete}];

Breadcrumb view

The code below takes as input the evaluation tree from Trace⎵Tree and outputs the breadcrumb view from the image above.

Clear[add⎵buttons];
Options[Tree⎵Data⎵Map]=Options[Tree];
Tree⎵Data⎵Map[f_][tree_,opts:OptionsPattern[]]:=
Module[{aux},
aux=TreeMapAt[
    f
    ,
    tree
    ,
    {0}
];
Tree[aux,opts,Sequence@@Options[Tree⎵Data⎵Map]]
];
SetOptions[Tree⎵Data⎵Map,
    ImageSize->Full
    ,
    MaxDisplayedChildren->Infinity
    ,
    TreeLayout->Right];


Options[add⎵buttons]=Options[Tree];

add⎵buttons[tree_,toggle_:False,root⎵position_:{},opts:OptionsPattern[]]:=
Module[{root⎵tree,root⎵tree⎵with⎵button⎵leaves
,tree⎵data,bread⎵crumb,bread⎵crumb⎵position,global},
(*view root node plus children (toggle=False)
or root node plus children and their children(toggle=True)*)
root⎵tree=RootTree[tree,1+Boole[toggle]]
;

(* add buttons to children
where the button action changes 
the display to the subtree of the child *)
root⎵tree⎵with⎵button⎵leaves=
TreeReplacePart[
        root⎵tree
        ,
        {i_}
        :>
        
        If[toggle
        ,
        (* If the children of the children are shown,
         then add buttons to the children at the first level *)
            Module[{sub⎵tree},
                sub⎵tree=TreeExtract[root⎵tree,i]
                ;
                Tree⎵Data⎵Map[s|->Button[s
                                        ,
                                        add⎵buttons[
                                                    sub⎵tree
                                                    ,
                                                    toggle
                                                    ,
                                                    Append[root⎵position,i]
                                                    ,
                                                    opts
                                                    ,
                                                    Sequence@@Options[add⎵buttons]
                                                    ]
                                        ,
                                        Appearance->"Frameless"
                                    ]
                ][sub⎵tree
                ,
                opts
                ,
                Sequence@@Options[add⎵buttons]
                ]
            ]
        ,
            Button[TreeExtract[root⎵tree,{i}]//TreeData
                    ,
                    add⎵buttons[
                                TreeExtract[tree,{i}]
                                ,
                                toggle
                                ,
                                Append[root⎵position,i]
                                ,
                                opts
                                ,
                                Sequence@@Options[add⎵buttons]
                                ]
                    ,
                    Appearance->"Frameless"
            ]
        ]
        
]
;
tree⎵data=TreeData[tree]
;
bread⎵crumb=
Button[
        Column[
            {Short[tree⎵data]
            ,
            root⎵position}
            ,
            Dividers->Center
            ]
        ,
        If[
            root⎵position==={}
            ,
            bread⎵crumbs={bread⎵crumbs[[1]]}
            ;
            displayed⎵tree=
            add⎵buttons[
                        original⎵tree                   
                        ,
                        toggle
                        ,
                        root⎵position
                        ,
                        opts
                        ,
                        Sequence@@Options[add⎵buttons]
            ]
            ,
            bread⎵crumb⎵position=
            LengthWhile[bread⎵crumbs,Not[#[[1,1,2]]===root⎵position]&]+1
            ;
            bread⎵crumbs=bread⎵crumbs[[1;;bread⎵crumb⎵position]]
            ;
            displayed⎵tree=
            add⎵buttons[
                        TreeExtract[original⎵tree
                                    ,
                                    root⎵position
                            ]                   
                        ,
                        toggle
                        ,
                        root⎵position
                        ,
                        opts
                        ,
                        Sequence@@Options[add⎵buttons]
            ]
    ]
    ,
    Appearance->"Palette"
]
;
(* check if the breadcrumb was already added
 at the prior step. If not add the breadcrumb*)
If[bread⎵crumbs==={}
     ∨ 
    Not[bread⎵crumb[[1,1,2]]===bread⎵crumbs[[-1,1,1,2]]] (* compare tree positions*)
,
    AppendTo[bread⎵crumbs,bread⎵crumb]
]
;

(* add button to root to view the children of the children
 if clicked *)
displayed⎵tree=
Tree⎵Data⎵Map[
    s|->Button[s
            ,
            add⎵buttons[
            tree
            ,
            Not[toggle]
            ,
            root⎵position
            ,
            opts
            ,
            Sequence@@Options[add⎵buttons]]
            ,
            Appearance->"Frameless"
        ]
][root⎵tree⎵with⎵button⎵leaves
,
opts
,
Sequence@@Options[add⎵buttons]
]
];
SetOptions[add⎵buttons,
    ImageSize->Full
    ,
    MaxDisplayedChildren->Infinity
    ,
    TreeLayout->Right];

ClearAll[Trace⎵Local⎵Tree];

Options[Trace⎵Local⎵Tree] = Options[Tree];

Trace⎵Local⎵Tree[expr_, 
   opts : OptionsPattern[]] :=
  Module[{tree},
   tree = Trace⎵Tree[expr, opts];
   bread⎵crumbs = {};
   original⎵tree = tree;
   add⎵buttons[tree, False, {}, opts, 
    Sequence @@ 
     Options[Trace⎵Local⎵Tree]]];

SetOptions[Trace⎵Local⎵Tree
        ,
        ImageSize -> Full
        ,
        MaxDisplayedChildren -> Infinity
        ,
        TreeLayout -> Right];

SetAttributes[Trace⎵Local⎵Tree, HoldAll];

Usage:

Below we consider the evaluation sequence of (2+3)^2

Trace⎵Local⎵Tree[(2+3)^2];

Dynamic[Row@bread⎵crumbs]

Dynamic[Panel@TreeOutline[displayed⎵tree, {___}]]

Explanation

Consider the example of the Fibonnaci sequence:

fib[1] = fib[2] = 1; fib[n_] := fib[n - 1] + fib[n - 2];

Here is an example of the output of the code above for fib[3]:

Trace⎵Local⎵Tree[fib[3]];

Dynamic[Row@bread⎵crumbs]

Dynamic[Panel@TreeOutline[displayed⎵tree, {___}]]

enter image description here

The gif above shows:

  • Clicking on a leaf of the the subtree changes the displayed tree to the subtree of that node. This is similar to the openerview mechanism but it's purpose is to avoid displaying all opened subtrees in the front end.

  • Clicking on a breadcrumb changes the displayed subtree to a subtree where the root is the same as the displayed expression in the button breadcrumb

The list displayed on each bread crumb button at the bottom represents its position in the tree, if the reader is unfamiliar with tree positions, the documentation of TreeExtract might help.

The rectangle at the bottom shows the current subtree as explained before. The elements are ordered as in TraceView2 except that the "result" element from the evaluation of a subtree evaluation sequence was added to the first element (rather than the last element as in TraceView2). The purpose of adding the "result" element to the first part was to better visualize the underlying tree structure when using the the optional tree view of the next section.

If we click on the root of a subtree we can peek one level down in the tree:

enter image description here

Clicking again on the root returns to the previous view

Optional: TreeView

Alternatively, we may also view directly the underlying tree structure. The buttons work as before.

Trace⎵Local⎵Tree[fib[3], 
  ImageSize -> Medium];

Dynamic[Row@bread⎵crumbs]

Dynamic[displayed⎵tree]

enter image description here

The full tree can be seen with Trace⎵Tree, for example Trace⎵Tree[fib[3]] for the evaluation sequence of fib[3]. One advantage of the tree view is that it allows more customization and might be easier to read when peaking one level down by clicking on the root of the subtree as explained before.

$\endgroup$
10
  • $\begingroup$ I suppose that with the global variables in that code, I can not send that function to the resource function repository ? $\endgroup$ Commented Apr 15, 2023 at 14:40
  • 1
    $\begingroup$ AFAIK, when your function will be converted to a ResourceFunction all your local variables will be isolated in a ResourceFunction's context. So you can safely use as many local variables as you wish. $\endgroup$ Commented Apr 15, 2023 at 16:30
  • $\begingroup$ @AlexeyPopkov Thank you, I suppose you meant global variables. Is there some sort of testing step that lets you try using ResourceFunction before it is publicly available? To see if everything works after wrapping the global variables in another context. $\endgroup$ Commented Apr 15, 2023 at 17:21
  • 1
    $\begingroup$ @userrandrand You can fill out the resource function definition notebook and then press "Deploy > Locally on this computer" in the notebooks toolbar to "install" the resource function locally. After that, you can verify that the locally installed version behaves as expected. Nice approach with the breadcrumbs by the way, that is a very interesting alternative way of displaying the "path" to a given node compared to what I am doing in ResourceFunction["TraceView"]! $\endgroup$
    – Lukas Lang
    Commented Apr 15, 2023 at 19:15
  • 1
    $\begingroup$ ResourceFunction["TraceView"] had similar design goals to what you mention, and should be able to handle large traces without issues. I have also tried to reduce the overhead of the acquisition of timing information as much as possible (in order to keep timing information accurate). That being said, your approach should also keep the front-end alive, as long as the depth of the call tree is not too excessive. One thing to watch out for is Break[] and related functions, because they tend to mess with TraceScan-based solutions if you are not careful (as I had to learn the hard way) $\endgroup$
    – Lukas Lang
    Commented Apr 16, 2023 at 13:11

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.