5
$\begingroup$

Consider the following discretization of a region object:

region = DiscretizeRegion[Disk[], MaxCellMeasure -> ∞]
Head[region]
InputForm[region]

which returns something like this:

By all appearances, this has created an object region with Head MeshRegion and which contains a bunch of points, then a Polygon object, and other stuff inside.

I am trying to manipulate regions of this kind using standard mess-with-the-code techniques, but they're proving surprisingly ineffective. Consider the following ways to get at the juicy interior:

  • Replace the head with something else, e.g. via notMeshRegion @@ region,

    which doesn't do anything.

  • Attack it via Part, to get at the inner components of the expression,

    doesn't work, and it returns a Part::partd error.

  • Attempt to replace the head or internals of the expression with something else,

    which leaves it untouched.

What's going on? I might expect this kind of thing to happen with objects with HoldFirst or HoldAll attributes (so their internals are a bit weird and they do some nonstandard handling), but MeshRegion's only attribute is Protected, and under InputForm and FullForm region looks exactly like every other expression. How do I get to those juicy internals?


I thought this would be obvious, since we're all grownups, but just to be clear: I'm not asking about WRI's design reasons for making the language behave like it does, which is obviously a question for WRI instead of this site. I'm asking about what features of the language cause some expressions, which can have intricate an intricate internal structure evident through InputForm or FullForm to make that internal structure unavailable via the normal language tools, where they are represented in the documentation, and how I can use the language to tell whether a given expression will behave this way.

$\endgroup$
13
  • 2
    $\begingroup$ check AtomQ @ region $\endgroup$
    – Kuba
    Jun 6, 2017 at 14:38
  • $\begingroup$ @Kuba That's kind of yet another symptom on the same vein - sure, it's reported as an atom, but it plainly has a ton of internal structure that's being intentionally hidden, and I can't tell what it its about MeshRegion that will cause its output to be thus 'atomized'. $\endgroup$ Jun 6, 2017 at 14:53
  • 2
    $\begingroup$ It is WRI which causes it to be atomized, that way you can't mess with content and it can go through internal procedures without too many check points. It is not another symptom, it is the reason and a sign that in order to extract details you need built-in region specific functions. $\endgroup$
    – Kuba
    Jun 6, 2017 at 15:12
  • $\begingroup$ @Kuba Obviously it is WRI that causes it to be atomized, but that doesn't make your comment particularly helpful. What is it about MeshRegion that indicates that behaviour, and how can I tell, via documentation or otherwise, whether SomeOtherFunction will behave in that way? $\endgroup$ Jun 6, 2017 at 15:22
  • $\begingroup$ Sorry, clearly I'm missing the point, but I thought AtomQ is what can tell you that. But I agree an atomic part of the WL could be documented better: comment367234 $\endgroup$
    – Kuba
    Jun 6, 2017 at 15:26

2 Answers 2

6
$\begingroup$

Not an answer to your question how to test for this behaviour, but I thought you might find it useful to have a way to programatically access the internal structure of the MeshRegion you used as an example.

This works as follows:

deAtomized = ToExpression[
                  StringReplace[
                      ToBoxes[InputForm@region][[1, 1]], 
                      "MeshRegion" -> "meshregion"]
             ];

Now you have access to the internal structure, and you can use Part on deAtomized like here:

Graphics[
  {
    FaceForm[], EdgeForm[Black], 
    GraphicsComplex[deAtomized[[1]], deAtomized[[2]]]
  }
]

enter image description here

deAtomized /. meshregion -> MeshRegion 

generates the atomized verion again.

$\endgroup$
2
$\begingroup$

As pointed out in the comments, MeshRegion objects are "atomized", which is not particularly well described in the documentation, as mentioned in an earlier question and more importantly this other one. This can be brought to the fore via

AtomQ[region]
(*True*)

where AtomQ is a function which

yields True if expr is an expression which cannot be divided into subexpressions, and yields False otherwise.

or in more detail

  • You can use AtomQ in a recursive procedure to tell when you have reached the bottom of the tree corresponding to an expression.
  • AtomQ gives True for symbols, numbers, strings, and other raw objects, such as sparse arrays.
  • AtomQ gives True for any object whose subparts cannot be accessed using functions like Map.

This is briefly mentioned in the documentation for MeshRegion, which states

MeshRegion is always converted to an optimized representation and treated as raw by functions like AtomQ for purposes of pattern matching.

That still does not explain why MMA is unwilling to break up the expression when explicitly told to do so (via e.g. Applying a different head to it), though in that sense it does behave like some other 'atomic' expressions, such as

f @@ Rational[2, 3]
(*2/3*)

but not others, such as

f @@ SparseArray[{{1, 1} -> 1, {1, 2} -> 3}]

(which returns, for some reason, an object of the type f[SparseArray[...]], but with dimensions {2} instead of {1,2} as the initial array).

The bottom line, it seems, is that this is for performance reasons which are not particularly well documented.

$\endgroup$
3
  • $\begingroup$ "That still does not explain why MMA is unwilling to break up the expression when explicitly told to do so (via e.g. Applying a different head to it)" --- This is the typical behavior of atomic expressions in Mathematica as I noted in Scan vs. Map vs. Apply. SparseArray is specially overloaded to behave (more like) a normal List expression, including Apply. $\endgroup$
    – Mr.Wizard
    Jun 9, 2017 at 17:04
  • $\begingroup$ @Mr.Wizard Frankly, I don't think the documentation is quite at the level where it can fend off accusations of "doesn't explain". If I run Sjoerd's deAtomized on my region I get a LeafCount of over 600; if such an object is "atomic" then it would frankly need to be the very first thing in the Details section if it wanted to not be misleading. Similarly, region has a Head and the docs for Apply says that it will replace that Head - with no mention at all of atomic objects in the details section. Call it a documentation bug if you want, but this isn't up to scratch, I think. $\endgroup$ Jun 9, 2017 at 17:22
  • $\begingroup$ I do not disagree. The documentation seems increasingly lacking in some fundamental areas such as atomic objects. $\endgroup$
    – Mr.Wizard
    Jun 9, 2017 at 17:43

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