5

I have a notebook which I want to execute multiple times and changing one parameter each time. Intuitively, I want to do something like this:

Table[
        FolderNumber= i; (*  this is the variable inside NoteBookToRun *)
        nb = NotebookOpen[FileNameJoin[{Directory[], NoteBookToRun}]];
        SelectionMove[nb,All,Notebook];
        SelectionEvaluate[nb];

            {ResultsFromNoteBookToRun},
        {i, 1, NumberOfFolders}
     ]

Obviously this won't work because the Table needs to finish before the targeted notebook will execute, so the maximum number I think this can work for is 2.

How can I get my notebook to run multiple times while changing a parameter inside the notebook I want to run?

user27119
  • 2,500
  • 13
  • 34
  • 1
    Take your notebook code and turn it into a bunch of functions. Save those as a package, maybe. This is what packages are built for. Then you can load the package and call some function that just runs the script. – b3m2a1 Feb 04 '20 at 20:32
  • That's certainly an option but at this point I would prefer to have a solution where I can just call the notebook -- if possible. If it isn't then I will close the question and just do it manually – user27119 Feb 04 '20 at 20:45
  • ...why would you willingly make your life harder like that? Turning the script into a bunch of functions will be almost zero work – b3m2a1 Feb 04 '20 at 20:50
  • As I said, that is an alternative to what I am asking. If what I am asking is not possible within M, then your suggestion is the answer. But for now what you suggest is NOT an answer to my question...I may as well just stick a loop around my script...there are reasons I don't want to break my code down. – user27119 Feb 05 '20 at 14:21
  • 1
    Question 67050 and the answers to it may provide some insights. – bbgodfrey Feb 05 '20 at 17:39
  • @bbgodfrey Thanks for this. Although I'm not quite sure how this helps? As I understand it this can just tell me if the notebook has been executed, is that correct? – user27119 Feb 05 '20 at 17:43
  • You can define a context that is shared between the two notebooks in order to share variables between them. After the called notebook returns, store the answer. – bbgodfrey Feb 05 '20 at 17:46
  • @bbgodfrey would you be willing to construct a rough example in answer? – user27119 Feb 05 '20 at 17:47

2 Answers2

5

Starting with your intended code...

Table[
        FolderNumber= i; (*  this is the variable inside NoteBookToRun *)
        nb = NotebookOpen[FileNameJoin[{Directory[], NoteBookToRun}]];
        SelectionMove[nb,All,Notebook];
        SelectionEvaluate[nb];

            {ResultsFromNoteBookToRun},
        {i, 1, NumberOfFolders}
     ]

...as you know, SelectionEvaluate is a poor choice for this because all it does it to queue the evaluation, not perform the evaluation and wait for a response. What you probably don't realize is that the similarly-named NotebookEvaluate function (available since v8) is much more powerful and does something very different. It evaluates the notebook synchronously, not returning until the notebook is fully evaluated -- not unlike evaluating a package.

NotebookEvaluate can insert the results or not depending upon the InsertResults option. And you don't even have to visibly open and close notebook windows. NotebookEvaluate, if given a filename for a notebook which is currently unopened, will invisibly open, evaluate, and close the window (saving if needed to insert results).

Changing your proposed code to use NotebookEvaluate is pretty straightforward:

Table[
        FolderNumber= i; (*  this is the variable inside NoteBookToRun *)
        NotebookEvaluate[FileNameJoin[{Directory[], NoteBookToRun}]];
            {ResultsFromNoteBookToRun},
        {i, 1, NumberOfFolders}
     ]

This version follows the default behavior to not insert the results into the notebooks, but if you wish to change that, simply add InsertResults->True.

John Fultz
  • 12,581
  • 58
  • 73
  • Enjoy that delicious bounty!! Brilliant this is exactly what I was after. I am sure there are more elegant methods using modules but this fits my needs right now perfectly! – user27119 Feb 11 '20 at 10:29
4

I have not used this code in quite a while but I think it still works. The calling notebook could be

Dynamic[{loop, linked`vdtdz}]
distab = {}; Dynamic[distab // TableForm]
Dynamic[return]

Do[linked`vdtdz = 0.1 loop; linked`$callingNotebook = EvaluationNotebook[];
return = NotebookEvaluate[$UserDocumentsDirectory<>"/Mathematica/PSTD_Solve.nb"];
distab = Append[distab, linked`maxai], {loop, 8, 10}]

It passes a variable

linked`vdtdz

and saves the returned variable

linked`maxai

It also temporarily displays a pair of plots produced by the called notebook.

The called notebook begins with

linked = ValueQ[linked`$callingNotebook]; Clear[linked`$callingNotebook]

which determines whether the called notebook actually has been called by another notebook, as opposed to being run on its own. Later, the called notebook uses the passed variable

vdtdz = If[linked, linked`vdtdz, 1.0]; \[CapitalDelta]t0 = vdtdz \[CapitalDelta]z0/vz0

Still later, the called notebook saves plots to disk, depending on whether it actually has been called by another notebook.

If[linked, saveplot = True, (*other code*)]

On its last line of code, the called notebook stores the answer to be returned to the calling notebook and also creates a final pair of plots which automatically are returned.

If[linked, linked`maxai = kout[[1, 4]]; Grid[{{grow3D, growContour}}]]

This may be more than you want. If so, delete what is unnecessary (the plots and the use of Dynamic, for instance).

bbgodfrey
  • 61,439
  • 17
  • 89
  • 156