2
$\begingroup$

I would like to evaluate a section several times for different values of some variables. I can evaluate it once with a simple command, but not in a loop.

To evaluate it only once, this code works

aValue = 6; activateB = True;
evaluateSection["mySection"]

where somewhere in the notebook there is a section called "mySection" containing this code

If[activateB , aValue * 100, aValue];
%//Print
Pause[2]

And the code of evaluateSection is

Clear[evaluateSection]
evaluateSection[title_String]:=Module[{titleCell},
    titleCell = SelectFirst[Cells[],
    MatchQ[NotebookRead[#],
    Cell[title,"Title"|"Subtitle"|"Chapter"|"Section"|"Subsection"|"Subsubsection",___]]&];

    SelectionMove[titleCell,All,CellGroup];
    SelectionEvaluate[EvaluationNotebook[]]
]

The code that does not work for me (it ignores the Pause, spitting all the namerun at once (correctly) but does not evaluate the section with the required input values)

Do[
    aValue = $aValue; activateB = $activateB;
  
    evaluateSection["mySection"];
    namerun = StringJoin[{"A", aValue, "B", If[activateB, "1", "0"]} // Map[ToString]];
    Print[namerun];

    ,{$aValue,{0,2,4}}
,{$activateB,{True,False}}
]

EDIT

To be specific about the issue. Something is causing delayed or deferred evaluation of SelectionEvaluate, which does not run at the same time as the Do loop. Maybe (maybe) this should be expected, since the evaluation of the cell with Do, has not finished, and therefore the evaluation of the cells inside mySection cannot be executed until the loop finishes. The instruction of SelectionEvaluate is nonetheless kept in a sort of cue, and when the loop finishes, mySection is indeed evaluated as many times as the loop Do requested, but without changing the variables, only using the last value that the variables had assigned.

Attempts at solutions: If the problem is (close to) that, then something like taking in memory the contents of mySection and executing it inside the Do loop should work. For that (I guessed that) NotebookWrite and NotebookExecute might help. But with no success. Another attempt was to convert the execution of mySection to a (kind of more abstract) TaskObject, and forcing it to execute with TaskExecute[SessionSubmit[SelectionEvaluate[...]]], and TaskWait. No success either.

Another solution if the above is truly impossible: Now I am thinking of changing the approach, and not using a Do loop. But of course I still want to execute mySection, from another cell of the same notebook, iteratively for different values of the variables/flags. Maybe adding something at the end of mySection that, when it has finished running, returns the ball to the cell where the initial execution started (previously with the Doloop), and from there, change the variables and call SelectionEvaluate again to evaluate mySection, until some condition stops it.

Conclusion: I think that managing to do this would improve the (already great) versatility of Mathematica. In my case, I developed a calculation in mySection, that now I need to run 72 times for different values of some parameters. If just adding a little code after mySection allowed me to do that, it would be awesome (for me and for the users of the package I am developing). If there are experts on the front end to help diagnose the problem, or some creative soul willing to give it a try, I would be so grateful.

$\endgroup$
7
  • 1
    $\begingroup$ I think your overall approach is not really a good practice. Nevertheless: The problem you are having is that aValue and activateB are "localized" symbols inside Do loop. They are not the same as the global symbols, which is why your section doesn't see their values. You could do some ugly things like: Do[aValue = $aValue; evaluateSection["mySection"], {$aValue, {0, 2, 4}}] ... But as I've said: Think about rewriting your code in a more standard way. $\endgroup$
    – Domen
    Commented Aug 20 at 15:38
  • $\begingroup$ Thanks for the comment @Domen. This was the first thing I tested, without success. If you have some suggestion or hint of how to do this in a standard way I'll be very interested $\endgroup$
    – Albercoc
    Commented Aug 20 at 15:43
  • $\begingroup$ Well, I don't know what is going on in your code, but generally, you could make a function that accepts aValue and activateB as parameters, and execute your code. Or you could make a separate Wolfram Language script file, and call it with command line arguments ... Also, what exactly do you mean by "without success"? Are you referring to the solution I've posted? It should work ... $\endgroup$
    – Domen
    Commented Aug 20 at 15:48
  • $\begingroup$ About making a function with mySection. It is organized with many subsections inside. About making a separate script with everything else than mySection removed, and having another notebook to call it (or using the command line), ... maybe I will have to resolve to that. But so far MMA has almost always managed to do what I had in mind and thought I would give it a try by asking here. $\endgroup$
    – Albercoc
    Commented Aug 20 at 15:58
  • $\begingroup$ Well, just because it can be done, doesn't mean it should be done :)) Anyhow, does my code work for you? Namely: Do[aValue = $aValue; evaluateSection["mySection"], {$aValue, {0, 2, 4}}] $\endgroup$
    – Domen
    Commented Aug 20 at 16:05

1 Answer 1

1
$\begingroup$

One solution is to bounce back the evaluation between the cell where evaluateSection is executed, and mySection, until all the values for the variables have been processed.

This solution uses variables in the global scope.

Instead of the Do loop (above), use simply

evaluateSectionInLoop["mySection", {aValue, activateB}, {{0, 2, 4}, {True, False}}]

And, inside mySection, write somewhere (does not need to be at the end)

bounceBackEval[]

Source code:

ClearAll[evaluateSectionInLoop]
SetAttributes[evaluateSectionInLoop, HoldAll];

evaluateSectionInLoop[title_String, names_List, vars_List] := (
  
  (*Execute the first time only*)
  If[isFirstExecution || ! ValueQ[isFirstExecution],
     ii = 0;
     isFirstExecution = False;
     tableOfValues = Tuples[vars];
  ];
  
  (*Assign the values to the variables (in the global scope)*)
  If[ii >= Length[tableOfValues], 
     isFirstExecution = True,
     names = tableOfValues[[++ii]];
     Print["The values of the variables are ", names];
     evaluateSection[title]
  ]
  
)


Clear[bounceBackEval]
bounceBackEval[] := Module[{cell},
  Print["Bouncing back evaluation to cell: ", uniqueTag];
  cell = SelectFirst[Cells[], 
    MatchQ[NotebookRead[#], Cell[__ , CellTags -> uniqueTag, __]] &];
  SelectionMove[cell, All, Cell];
  SelectionEvaluate[EvaluationNotebook[]]
]


Clear[evaluateSection]
evaluateSection[title_String] := Module[{titleCell},
  
  (*Tag the cell where this is executed from*)
  uniqueTag = ToString@CreateUUID[]; 
  SetOptions[EvaluationCell[], CellTags -> {uniqueTag}];
  
  (*ID the targeted section and execute it*)
  titleCell = 
   SelectFirst[Cells[], 
    MatchQ[NotebookRead[#], 
      Cell[title, 
       "Title" | "Subtitle" | "Chapter" | "Section" | "Subsection" | 
        "Subsubsection", ___]] &];
  SelectionMove[titleCell, All, CellGroup];
  SelectionEvaluate[EvaluationNotebook[]]
]

Technical explanation:

When executing evaluateSectionInLoop, the cell is tagged with a uniqueTag (global variable). Then, the section title with the given name, "mySection" in the example, is searched and its cells are selected programatically with SelectionMove, and subsequently evaluated with SelectionEvaluate.

Note that SelectionEvaluate does not evaluate the selected cells until the evaluation of the current cell finishes, that is why using a Do loop was not possible.

The section is evaluated and if the evaluation finds a bounceBackEval[], this function will search for the starting cell with the uniqueTag, and evaluate it again with SelectonEvaluate. Since this is not executed until the current evaluation finishes, it does not matter where you place bounceBackEval[] within the section.

Once the evaluation arrives again to evaluateSectionInLoop, the first thing is to check if the iterator iiLoop (global) has arrived to the end of tableOfValues (global). If not, the variables will be assigned with the next set of values, starting all over again. To be able to re-assign the variables inside evaluateSectionInLoop, it needs SetAttributes[.., HoldAll] to ensure they remain unevaluated.

$\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.