9
$\begingroup$

I'm trying to solve a very tricky issue in my function. The function needs the NDSolve`FEM` framework.

Here is a minimal example:

Quit[]

GetBoundaryMesh[obj_] := Module[{bmesh},
   Needs["NDSolve`FEM`"];
   bmesh = ToBoundaryMesh @ obj;
   MeshPrimitives[MeshRegion @ bmesh, 1]
]

GetBoundaryMesh[Disk[]] 
 MeshPrimitives[MeshRegion[Global`ToBoundaryMesh[Disk[{0,0}]]],1]

Something went wrong, I was expecting a list of Lines which I can get by executing this code manually:

Quit[]

Needs["NDSolve`FEM`"];
bmesh = ToBoundaryMesh @ obj;
MeshPrimitives[MeshRegion @ bmesh, 1]
{Line[{{1.,0.},{0.991575,0.129537}}],<<46>>,Line[{{<<19>>,-<<20>>},{<<1>>}}]}

To me it seems like it isn't importing the NDSolve`FEM package correctly, but that's just my best guess.


Loose thoughts

When I close MMa and open my notebook again, if I do $Packages, FEM`NDSolve isn't there (as expected):

{"GetFEKernelInit`", "StreamingLoader`", "IconizeLoader`", \
"CloudObjectLoader`", "ResourceLocator`", "PacletManager`", \
"System`", "Global`"}

Even after I evaluate the cell that holds all my functions, it's still not in $Packages. At this point, if I run my function, it gives the same problem, because apparently it has not loaded the package (despite it being the first line in the function it runs...?).

However, after I run the function once (and it gives its error), if I check $Packages, it is there! So it seems like it is loading it, but somehow the function isn't seeing that in time when it's called..?

Okay, I managed to solve the problem kind of by explicitly calling ToBoundaryMesh this way:

NDSolve`FEM`ToBoundaryMesh@ashape;

That seems to work on the first try, but why didn't it work without explicitly calling it? Why isn't it in the $Packages list for the function once Needs[] is called?

I was reading here about packages and context but I didn't really see the answer.

$\endgroup$
8
  • 2
    $\begingroup$ To rephrase what @Kuba said, the code of your GetBoundaryMesh is parsed before the call to Needs, and therefore the symbol ToBoundaryMesh is not found anywhere on the $ContextPath and is considered a new symbol. So, it is created in the current working context ("Global`" in this case). If you call Needs before defining GetBoundaryMesh, then by the time the code of GetBoundaryMesh is parsed, the context NDSolve`FEM` is already on the $ContextPath. So, in this case, the symbol is found there and bound to the right one. $\endgroup$ May 24, 2016 at 21:08
  • $\begingroup$ @LeonidShifrin thank you both. I think I almost get it. But why, when the function is called, doesn't it know that it belongs to NDSolveFEM? It seems like that would make the most sense, because it has some definition for NDSolveFEM at that point, but no definition in Global. Anyway, I think my solution (calling it explicitly) should work fine, right? $\endgroup$ May 24, 2016 at 21:13
  • $\begingroup$ @YungHummmma I have a more detailed explanation of package mechanics here and here. Basically, when the loader sees a new symbol, it searches for it in all packages currently on the $ContextPath. If no symbol with this name is found there, the symbol is created in the current context. So, you have to load your dependencies before your code is parsed, so that they are added to the $ContextPath. And yes, your solution will work. $\endgroup$ May 24, 2016 at 22:01
  • $\begingroup$ @LeonidShifrin thanks, seems good then. If you or kuba can make an answer I can choose that as an answer then. Thank you both! $\endgroup$ May 24, 2016 at 22:17
  • $\begingroup$ I will leave it for @Kuba to decide. If he decides not to post, I will convert my comments into one, just so that the question won't be left as unanswered. $\endgroup$ May 24, 2016 at 22:21

1 Answer 1

13
$\begingroup$

tl;dr

You need to call Needs before GetBoundaryMesh definition so it can be parsed (found in correct context) correctly or you have to use the full name of ToBoundaryMesh.


Relevant part of documentation

from SettingUpWolframLanguagePackages

Executing a function like Begin which manipulates contexts changes the way that the Wolfram Language interprets names you type in. However, you should realize that the change is effective only in subsequent expressions that you type in. The point is that the Wolfram Language always reads in a complete input expression, and interprets the names in it, before it executes any part of the expression. As a result, by the time Begin is executed in a particular expression, the names in the expression have already been interpreted, and it is too late for Begin to have an effect.

The fact that context manipulation functions do not have an effect until the next complete expression is read in means that you must be sure to give those functions as separate expressions, typically on separate lines, when you write Wolfram Language packages.


How parsing works, more or less

  1. Input is read

    Wolfram system reads expression by expression (keep in mind that a; b; in one line is a CompundExpression. (alternatively one can say: line by line but not less than expression, e.g. Hold[ is not full expression so more than one line will be read).

  2. Expression is parsed

    e.g. Identity[mySymbol] -> System`Identity[Global`mySymbol]

  3. Expression is evaluated

    System`Identity[Global`mySymbol] -> Global`mySymbol (or its OwnValue if there is any)

!!! This sequence of steps (so parsing too) can also be triggered during evaluation by functions which are converting strings, streams or boxes to expressions, e.g. Get, ToExpression, Read etc. Take a look at example 2.


Example 1

Assuming MySymbol is a new symbol.

With () it is a CompoundExpression so it's read as a whole:

(
 BeginPackage["Test`"];
 MySymbol;
 EndPackage[];
)

It is parsed to

System`CompoundExpression[
    System`BeginPackage["Test`"], 
    Global`MySymbol,  
    System`EndPackage[], 
    System`Null
]

It is Global` because BeginPackage wasn't evaluated yet. (Often it will be Global` but in general it is $Context more about: 2. How symbol lookup actually works

In package (.m), Get reads line by line (there are no ()s) and MySymbol is parsed separately after already evaluated BeginPackage - ergo it will be created in Test` context.

Example 2

Quit the kernel first, we want to forget about MySymbol.

(
 BeginPackage["Test`"];
 ToExpression["MySymbol"];
 EndPackage[];
)

Now we get:

System`CompoundExpression[
    System`BeginPackage["Test`"],    (*1*)
    System`ToExpression["MySymbol"], (*2*)
    System`EndPackage[],             (*3*)
    System`Null
]

Which is evaluated:

  • (*1*) changes current $Context to Test`
  • (*2*) ToExpression parses and evaluates "MySymbol" but in new $Context!(1)
  • (*3*) $Context switched back to Global`

[...] What I meant is that you can't use Begin and End inside of Initialization without forcing a separate parse stage to begin, as would happen with Get or ToExpression. Yes, you should be able to use Begin/End outside of CreatePalette, but only if they are on separate lines of input. I.e., they can't be a part of a CompoundExpression or other expression which contains the CreatePalette command. This means terminating all operators and placing a newline in the input cell, or putting the Begin/End in totally different cells.

– John Fultz Feb 6 '12 at 15:19


Confession

My old comment is partially incorrect

$Context or $ContextPath changes (done by Get/Needs/BeginPackage and friends) are reflected after/between evaluations[...]

Changes of $Context/$ContextPath are done immediately but it can't affect already parsed expression.


[1] palettes with non-trivial functionality

[2] How symbol lookup actually works

[3] FullForm with context for each symbol?

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.