Important Changes in Regina 7.0

Regina 7.0 brought an enormous overhaul across the entire codebase—possibly the largest overhaul in almost two decades. There has been signficant work to insulate users from these changes: the bulk of these changes should only be visible to C++ programmers, but there are important behavioural changes that will affect everybody.

Some of these changes break backward compatibility.

If you are migrating from Regina ≤ 6.0.1, you should read the brief summary below so you understand the most important changes.

C++ and Python programmers can find a more fine-grained explanation of the API changes on the Regina website.

A new, more flexible file format

Regina's data file format has changed. Regina 7.0 and above can read your old data files, but Regina 6.0.1 and earlier will not be able to read files from newer versions of Regina.

If you need to export a file so that Regina ≤ 6.0.1 can read it, you use the command-line regconvert utility, or the FileExport menu in the user interface.

A major advantage of the new file format is that different packets no longer depend on each other. In particular, if you create a normal surface, hypersurface or angle structure list from a triangulation, you can later change the triangulation, or move it to a separate location in the tree, or even delete it. The list will make its own copy of the original triangulation if this becomes necessary (which you can access through the header of the normal surface, hypersurface or angle structure viewer). Likewise, the list will bundle this copy of the original triangulation into the data file if necessary.

Dimensions 9–15 removed by default

For several years, Regina has supported triangulations of dimensions 2–15. However, these higher dimensions add significant overhead (even if you are not using them), particularly for Python users.

For this reason, Regina 7.0 and above now only includes dimensions 2–8 by default. In particular, if you open a data file containing triangulations of dimension 9–15 and then save the file again, these triangulations will be lost.

If you need to work with dimensions 9–15, you can make your own custom build of Regina; be sure to pass the option -DHIGHDIM=1 to cmake. Also perhaps drop Ben an email so he knows that there are users who want these features.

Python is a first-class citizen

For the first time, Python users now have access to essentially everything inside Regina's C++ calculation engine. You can use functions that take callbacks (e.g., the exhaustive retriangulation code, census generation, and subgroup enumeration), and you can access low-level algorithms (e.g., the linear programming code, or the double description method and Hilbert basis enumeration).

Python now supports iteration wherever C++ does, using lightweight iterator objects where possible:

>>> for e in Example3.lens(8,3).edges():
...     print(e)
...
Edge 0, internal, degree 4: 0 (01), 0 (20), 1 (10), 0 (13)
Edge 1, internal, degree 4: 0 (03), 1 (02), 1 (31), 0 (21)
Edge 2, internal, degree 4: 0 (23), 1 (30), 1 (23), 1 (12)

Almost all classes now provide a more informative Python representation:

>>> ExampleLink.trefoil().group()
<regina.GroupPresentation: < a b | a^-2 b a b >>

The equality comparisons (== and !=) now compare their contents by value for most classes. The main exceptions are objects whose location in memory defines them (e.g., faces and simplices in triangulations, or crossings in links), and objects whose purpose is to manage an expensive computation (e.g., progress trackers or census managers); these still compare by reference. Each class has an equalityType constant that tells you how its objects are compared.

>>> Example3.lens(8,3).homology() == AbelianGroup(0, [8])
True
>>> AbelianGroup.equalityType
<EqualityType.BY_VALUE: 1>

In line with the C++ changes described below, Regina now throws exceptions upon error in some settings instead of returning None (e.g., Triangulation3.fromIsoSig()); see the API documentation for details on which exceptions each function might throw (if any).

Mathematical objects such as triangulations and links are no longer packets (i.e., they do not live in a packet tree or belong to a data file by default); this is for performance reasons. If you just want to work with a triangulation or link, you can use Triangulation3 or Link as before; however, if you want to insert one of these objects into a packet tree then you will need to use the corresponding classes PacketOfTriangulation3 or PacketOfLink (and so on for other types of object). These packet classes inherit from Triangulation3, Link, etc., and can be used in much the same way. You can use the new make_packet() functions to help you convert between the classes.

>>> ExampleLink.figureEight()
<regina.Link: 4-crossing knot: ++-- ( _0 ^1 _2 ^3 _1 ^0 _3 ^2 )>
>>> make_packet(ExampleLink.figureEight())
<regina.PacketOfLink: 4-crossing knot: ++-- ( _0 ^1 _2 ^3 _1 ^0 _3 ^2 )>

Finally, although there are far fewer changes to the Python API than for C++, there is one change that will likely affect most people: to enumerate normal surfaces or angle structures, you no longer use the old enumerate() functions. Instead, just create a new NormalSurfaces or AngleStructures object:

>>> t = Example3.poincare()
>>> s = NormalSurfaces(t, NS_STANDARD)
>>> print(s)
7 embedded, vertex surfaces (Standard normal (tri-quad))

C++ embraces modern standards and value semantics

Regina began in the late 1990s, before C++ was ever standardised. Since then, Regina's API has slowly evolved as the language changed, incrementally making use of new language features, and removing old workarounds as they became no longer necessary.

One of the most important changes to the C++ language was the much improved value semantics that came with C++11. When used with the new C++11 move operations, this made it both easy and efficient to pass large objects around by value, not by pointer. The resulting code reads more naturally, is typically less error-prone, avoids the need for manual memory management, and still retains the performance of the old pointer-based code.

In Regina 7.0, the C++ API underwent a complete overhaul from head to toe. The API now uses the C++17 standard. Widespread changes include:

  • Regina now uses value semantics across the board. So, for example, Triangulation<3>::fromIsoSig() now returns a Triangulation<3> by value. Most of your objects should now be able to live on the stack; in general there should no need for manual memory management via new and delete. The main exceptions are objects that are defined by their locations in memory, such as faces and simplices in triangulations, or crossings in links: these are still passed by pointer, but their memory is safely managed by their enclosing triangulations or links.

  • Likewise, functions that used to return newly-allocated pointers now return by value. If they used to return null when unexpected errors occurred, they typically now throw exceptions; if they used to return null in ordinary scenarios where there is no solution, they typically now return a std::optional.

  • When returning polymorphic objects (such as Packet or StandardTriangulation), you cannot avoid returning by pointer since the exact type is not known at compile time. In such cases, Regina now returns a smart pointer (typically std::shared_ptr for packets and std::unique_ptr for other types of object).

  • Functions that used to take arguments by pointer now typically take them either by value or by const reference. Many functions that used to return multiple values via reference arguments now return a std::tuple instead.

  • The class hierarchies have been simplified considerably. Hopefully one side-effect of this is that the API documentation is easier to navigate.

  • Functions that take callbacks (such as census generation or exhaustive retriangulation) are now more flexible, and can work with lambdas and arbitrary lists of properly-typed arguments. They no longer take function pointers with untyped (void*) arguments.

  • Packets are now managed exclusively by shared pointers. You should never be holding on to to a raw pointer to a packet type. The large mathematical classes such as Triangulation<dim> and Link are no longer packet types, since this frees them from considerable overhead; if you need to insert them into a packet tree then you should use PacketOf<Triangulation<3>>, PacketOf<Link>, and so on. There is a new suite of make_packet() functions to help you with this.

Since Regina 7.0 was a “big release” that broke backward compatibility, this was a good time to clean up and simplify many other unwieldy parts of the API. These changes range from simple renamings (e.g., StandardTriangulation::isStandardTriangulation becomes StandardTriangulation::recognise) through to major semantic changes (e.g., each normal surface now stores an encoding, which is to some degree independent of the coordinate system that was used to create it).

If you are writing C++ code that links against Regina, you will need to change your code. Generally the issues should be flagged by the compiler (not discovered at runtime), and the necesssary changes should be obvious. Some old functions have been kept but marked as deprecated to ease the transition; your compiler should warn you also if you are still using these.

TONS removed temporarily

For a long time, Regina has offered “experimental” support for transversely oriented normal surfaces (TONS). This always came with large caveats and promises that things would break, and needed to be explicitly enabled before it could be used.

Now that the normal surface classes have undergone a major redesign, TONS no longer fits cleanly into the normal surface model (since in some fundamental sense they are not normal surfaces), and so this support has been removed completely from Regina.

The intention is to reimplement TONS “properly” in a future release, using their own separate suite of classes that more accurately reflect what they are and how they behave.